Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -289,34 +289,75 @@ export function ResearchReportSummary({analysis: a}: {analysis: Controller}) {
onExpand={origin => {a.trigger.current = origin; a.show();}} onCitation={a.readCitation}/></div> : null;
}

function CitedToolText({text, zh}: {text: string; zh: boolean}) {
// Decode only the saved direct shell-result envelope, never recursively
// reinterpret arbitrary JSON or infer successful work from its text.
try {
const value: unknown = JSON.parse(text);
if (value && typeof value === "object" && !Array.isArray(value)
&& "chunk_id" in value && typeof value.chunk_id === "string"
&& "wall_time_seconds" in value && typeof value.wall_time_seconds === "number"
&& Number.isFinite(value.wall_time_seconds) && value.wall_time_seconds >= 0
&& "exit_code" in value && (value.exit_code === null || (typeof value.exit_code === "number" && Number.isSafeInteger(value.exit_code)))
&& "output" in value && typeof value.output === "string") {
return <div className="analysis-source-tool-text">
<div className="analysis-source-command-meta">
<span>{value.exit_code === null ? (zh ? "尚无退出码" : "No exit code yet") : `${zh ? "退出码" : "Exit code"} ${value.exit_code}`}</span>
<span>{value.wall_time_seconds} s</span>
</div>
<CitedOutput text={value.output} zh={zh}/>
</div>;
}
} catch { /* Plain text and unknown envelopes stay literal. */ }
return <CitedOutput text={text} zh={zh}/>;
}

function CitedOutput({text, zh}: {text: string; zh: boolean}) {
return text ? <pre className="analysis-source-output">{text}</pre>
: <p className="analysis-source-empty">{zh ? "输出为空" : "Empty output"}</p>;
}

function CitationContent({citation, zh}: {citation: Citation; zh: boolean}) {
// A reading projection of the native completed-command envelope, not a
// source verifier. Unknown or mixed formats retain the complete literal.
const commands: {output: string; argv: string[]; exitCode: number; status: string}[] = [];
// Display projections of explicitly typed records only. Unknown, mixed or
// malformed records retain the complete citation, including neighboring lines.
const records: ({output: string; argv: string[]; exitCode: number; status: string} | {texts: string[]})[] = [];
try {
for (const line of citation.text.split("\n")) {
const event: {type?: unknown; payload?: {type?: unknown; item?: Record<string, unknown>}} | null = JSON.parse(line);
const event: {type?: unknown; payload?: {type?: unknown; output?: unknown; item?: Record<string, unknown>}} | null = JSON.parse(line);
const item = event?.payload?.item;
if (event?.type !== "event_msg" || event.payload?.type !== "item_completed"
|| item?.type !== "CommandExecution" || typeof item.aggregated_output !== "string"
|| !Array.isArray(item.command) || !item.command.every((arg: unknown) => typeof arg === "string")
|| typeof item.exit_code !== "number" || !Number.isSafeInteger(item.exit_code) || typeof item.status !== "string") {
if (event?.type === "event_msg" && event.payload?.type === "item_completed"
&& item?.type === "CommandExecution" && typeof item.aggregated_output === "string"
&& Array.isArray(item.command) && item.command.every((arg: unknown) => typeof arg === "string")
&& typeof item.exit_code === "number" && Number.isSafeInteger(item.exit_code) && typeof item.status === "string") {
records.push({output: item.aggregated_output, argv: item.command, exitCode: item.exit_code, status: item.status});
} else if (event?.type === "response_item"
&& (event.payload?.type === "custom_tool_call_output" || event.payload?.type === "function_call_output")) {
const output = event.payload.output;
if (typeof output === "string") records.push({texts: [output]});
else if (Array.isArray(output) && output.length > 0 && output.every(part => part && typeof part === "object"
&& !Array.isArray(part) && part.type === "input_text" && typeof part.text === "string")) {
records.push({texts: output.map(part => part.text as string)});
} else return <pre>{citation.text}</pre>;
} else {
return <pre>{citation.text}</pre>;
}
commands.push({output: item.aggregated_output, argv: item.command, exitCode: item.exit_code, status: item.status});
}
} catch {
return <pre>{citation.text}</pre>;
}
return <div className="analysis-source-content">
{commands.map((command, index) => <div className="analysis-source-command" key={index}>
<div className="analysis-source-command-meta">
{commands.length > 1 ? <span>{zh ? "行" : "Line"} {citation.first_line + index}</span> : null}
<span>{zh ? "退出码" : "Exit code"} {command.exitCode}</span><span>{command.status}</span>
</div>
{command.output ? <pre className="analysis-source-output">{command.output}</pre>
: <p className="analysis-source-empty">{zh ? "输出为空" : "Empty output"}</p>}
<details><summary>{zh ? "命令参数" : "Command arguments"}</summary><pre>{JSON.stringify(command.argv, null, 2)}</pre></details>
{records.map((record, index) => <div className="analysis-source-command" key={index}>
{"texts" in record ? <>
{records.length > 1 ? <div className="analysis-source-command-meta">{zh ? "行" : "Line"} {citation.first_line + index}</div> : null}
{record.texts.map((text, part) => <CitedToolText key={part} text={text} zh={zh}/>)}
</> : <>
<div className="analysis-source-command-meta">
{records.length > 1 ? <span>{zh ? "行" : "Line"} {citation.first_line + index}</span> : null}
<span>{zh ? "退出码" : "Exit code"} {record.exitCode}</span><span>{record.status}</span>
</div>
<CitedOutput text={record.output} zh={zh}/>
<details><summary>{zh ? "命令参数" : "Command arguments"}</summary><pre>{JSON.stringify(record.argv, null, 2)}</pre></details>
</>}
</div>)}
<details className="analysis-source-raw"><summary>{zh ? "完整原始记录" : "Complete raw record"}</summary><pre>{citation.text}</pre></details>
</div>;
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/trajectory-assessment.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ the history action returns to the complete loaded research history without
changing the selected evidence pack. Reading either layer, switching report
tabs or opening an original never executes an analysis or experiment.

The source reader displays saved completed-command output and text-only
`function_call_output` / `custom_tool_call_output` records in source order.
Direct shell-result envelopes show their literal output and observed exit code;
a missing exit code remains unknown. Command arguments and the complete original
record stay available on demand. Mixed, malformed or unsupported content falls
back to the complete literal citation. Unknown nested transport stays text;
reading never recursively interprets it or turns an exit code into acceptance.

The presentation draws on composable inspection in
[Distill's interpretability article](https://distill.pub/2018/building-blocks/)
and responsive comparison layouts in the
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 39 additions & 9 deletions examples/personal-workspace-browser/research-native-reading.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const researchNativeReadingScenario = {
expected: [0.0000001234567890123456, 0.0000001234567890123456, 0.0000001123456789012345, 0.0000001123456789012345], display: ["1.235E-7", "1.235E-7", "1.123E-7", "1.123E-7"], gain: "-1.111E-8"},
{id: "large", values: [123456789.12345679], higher: true, retained: 0,
expected: [123456789.12345679, 123456789.12345679, 123456789.12345679, 123456789.12345679], display: ["1.235E8", "1.235E8", "1.235E8", "1.235E8"], gain: "0"},
...["command", "mixed", "damaged", "empty-output", "unsafe-exit"].map(source => ({id: `${source}-source`, source,
...["command", "mixed", "damaged", "empty-output", "unsafe-exit", "tool-text", "tool-shell", "tool-pending",
"tool-function", "tool-mixed", "tool-damaged", "tool-unsafe-exit", "tool-empty"].map(source => ({id: `${source}-source`, source,
values: [2, 1], higher: false, retained: 0, expected: [1, 2, 1, 2]})),
];
const root = await mkdtemp(resolve(tmpdir(), "loopx-native-reading-"));
Expand All @@ -37,6 +38,9 @@ export const researchNativeReadingScenario = {
fixture.analyses = cases.map(c => {
const a = structuredClone(original), native = JSON.parse(a.artifacts["native.json"]);
a.title = c.id;
// These supplied outputs use the legacy assessment format, independent
// of the current registration default for newly generated reports.
a.config = {model: "fixture", effort: "max", service_tier: "fast", research_synthesis: false};
a.input.scope.run_id = c.id;
native.scope = {...a.input.scope};
native.complete = !c.partial;
Expand All @@ -62,6 +66,20 @@ export const researchNativeReadingScenario = {
? JSON.stringify({type: "unknown_event", text: "Unrecognized source MUST_REMAIN_LITERAL"})
: c.source === "damaged" ? "{malformed MUST_REMAIN_LITERAL"
: command(c.source === "empty-output" ? "" : longFailure, c.source === "unsafe-exit" ? Number.MAX_SAFE_INTEGER + 1 : 17)].join("\n");
if (c.source.startsWith("tool-")) {
const shell = (text, exit_code) => JSON.stringify({chunk_id: "synthetic", wall_time_seconds: 0.25, exit_code, output: text});
const tool = output => JSON.stringify({type: "response_item", payload: {
type: c.source === "tool-function" ? "function_call_output" : "custom_tool_call_output", output}});
const part = text => ({type: "input_text", text});
const first = ["tool-text", "tool-function"].includes(c.source) ? output : c.source === "tool-empty" ? ""
: [part("Recorded tool response\n"), part(c.source === "tool-unsafe-exit"
? '{"chunk_id":"synthetic","wall_time_seconds":0.25,"exit_code":9007199254740993,"output":"MUST_REMAIN_LITERAL"}'
: shell(output, c.source === "tool-pending" ? null : 0))];
const second = c.source === "tool-mixed" ? [part(longFailure), {type: "input_image", image_url: "synthetic"}]
: c.source === "tool-damaged" ? [part(longFailure), {type: "input_text", text: 12}]
: c.source === "tool-empty" ? "" : [part(shell(longFailure, 17))];
text = [tool(first), tool(second)].join("\n");
}
native.samples[0].citation.last_line = 2;
}
a.artifacts["facts.txt"] = text;
Expand All @@ -73,7 +91,7 @@ export const researchNativeReadingScenario = {
await writeFile(path, JSON.stringify(fixture));
// Same public synthetic data for reproducible before/after inspection.
await writeFile(resolve(outputDir, "research-citation-reader-fixture.json"), JSON.stringify({...fixture,
analyses: fixture.analyses.filter(a => a.title === "command-source")}));
analyses: fixture.analyses.filter(a => ["command-source", "tool-shell-source"].includes(a.title))}));
owner = await startHistoryOwner(path);
page = await browser.newPage({viewport: {width: 1280, height: 900}, hasTouch: true});
const errors = [];
Expand Down Expand Up @@ -139,25 +157,36 @@ export const researchNativeReadingScenario = {
const citation = page.getByRole("region", {name: "引用原文", exact: true});
await citation.waitFor();
const literal = fixture.analyses.find(a => a.title === c.id).artifacts["facts.txt"];
if (["mixed", "damaged", "unsafe-exit"].includes(c.source)) {
if (["mixed", "damaged", "unsafe-exit", "tool-mixed", "tool-damaged"].includes(c.source)) {
await citation.locator("pre").waitFor();
assert.equal(await citation.locator("pre").textContent(), literal, "Never discard unsupported or malformed neighboring evidence");
assert.equal(await citation.locator("details").count(), 0);
} else {
await citation.locator(".analysis-source-content").waitFor();
assert.equal(await citation.locator(".analysis-source-command").count(), 2);
await citation.getByText("行 2", {exact: true}).waitFor();
await citation.getByText("退出码 17", {exact: true}).waitFor();
if (c.source !== "tool-empty") await citation.getByText("退出码 17", {exact: true}).waitFor();
if (c.source === "tool-pending") await citation.getByText("尚无退出码", {exact: true}).waitFor();
const raw = citation.locator(".analysis-source-raw");
assert.equal(await raw.getAttribute("open"), null);
await raw.locator("summary").tap();
assert.equal(await raw.locator("pre").textContent(), literal, "Raw evidence remains byte-for-byte available");
await raw.locator("summary").tap();
if (c.source === "empty-output") assert.equal(await citation.getByText("输出为空", {exact: true}).count(), 2);
if (["empty-output", "tool-empty"].includes(c.source)) assert.equal(await citation.getByText("输出为空", {exact: true}).count(), 2);
else {
const records = literal.split("\n").map(line => JSON.parse(line).payload.item);
for (const [i, record] of records.entries()) {
assert.equal(await citation.locator(".analysis-source-output").nth(i).textContent(), record.aggregated_output);
const records = literal.split("\n").map(line => JSON.parse(line).payload);
const outputs = await citation.locator(".analysis-source-output").allTextContents();
if (c.source.startsWith("tool-")) {
const main = JSON.stringify({cases: 8, improved: 3, regressed: 2, unchanged: 3, complete_cost: null}, null, 2);
assert(outputs.includes(c.source === "tool-unsafe-exit" ? records[0].output[1].text : main));
if (c.source === "tool-unsafe-exit") {
assert.equal(await citation.getByText("退出码 9007199254740992", {exact: true}).count(), 0, "Unsafe numeric metadata must not be rounded into a display fact");
assert(outputs.some(text => text.includes("9007199254740993")));
}
if (["tool-shell", "tool-pending", "tool-unsafe-exit"].includes(c.source)) assert.equal(outputs[0], "Recorded tool response\n", "Preserve text blocks and order, including surrounding caveats");
assert.equal(outputs.at(-1), JSON.parse(records[1].output[0].text).output);
} else {
for (const [i, record] of records.entries()) assert.equal(outputs[i], record.item.aggregated_output);
}
assert.equal(await citation.locator("script").count(), 0);
assert.equal(await page.evaluate(() => window.sourceExecuted), undefined);
Expand Down Expand Up @@ -191,12 +220,13 @@ export const researchNativeReadingScenario = {
await page.getByRole("button", {name: "About native measurements", exact: true}).focus();
await page.getByRole("tooltip").waitFor({state: "visible"});
await page.keyboard.press("Escape");
await selector.selectOption({label: "command-source"});
await selector.selectOption({label: "tool-pending-source"});
const selected = await selector.inputValue();
await page.locator(`.research-progress-scope[data-pack-id="${selected}"]`).waitFor();
await page.getByRole("region", {name: "Native measurements", exact: true}).getByRole("button", {name: /Native retained result.*Read original evidence/}).click();
const originalPanel = page.getByRole("region", {name: "Cited original", exact: true});
await originalPanel.getByText("Exit code 17", {exact: true}).waitFor();
await originalPanel.getByText("No exit code yet", {exact: true}).waitFor();
await originalPanel.getByText("Complete raw record", {exact: true}).waitFor();
await originalPanel.press("Escape");
const readback = await (await page.request.get(`${owner.base}/fixture/readback`)).json();
Expand Down
Loading