neoctl 0.2.46 → 0.2.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +59 -59
  2. package/dist/agents/agent-activity.js +1 -1
  3. package/dist/agents/agent-activity.js.map +1 -1
  4. package/dist/agents/agent-tool.js +2 -1
  5. package/dist/agents/agent-tool.js.map +1 -1
  6. package/dist/context/compaction.d.ts +1 -0
  7. package/dist/context/compaction.js +6 -2
  8. package/dist/context/compaction.js.map +1 -1
  9. package/dist/context/prompts.js +3 -4
  10. package/dist/context/prompts.js.map +1 -1
  11. package/dist/context/system.md +16 -16
  12. package/dist/core/abortable-stream.d.ts +2 -0
  13. package/dist/core/abortable-stream.js +17 -0
  14. package/dist/core/abortable-stream.js.map +1 -1
  15. package/dist/core/compaction-progress.d.ts +1 -1
  16. package/dist/core/compaction-progress.js +7 -5
  17. package/dist/core/compaction-progress.js.map +1 -1
  18. package/dist/core/execution-state.d.ts +2 -0
  19. package/dist/core/execution-state.js +2 -1
  20. package/dist/core/execution-state.js.map +1 -1
  21. package/dist/core/message-pipeline.js +2 -2
  22. package/dist/core/message-pipeline.js.map +1 -1
  23. package/dist/core/query-engine.d.ts +12 -1
  24. package/dist/core/query-engine.js +125 -21
  25. package/dist/core/query-engine.js.map +1 -1
  26. package/dist/core/query.js +46 -6
  27. package/dist/core/query.js.map +1 -1
  28. package/dist/execution/filesystem.js +22 -22
  29. package/dist/execution/process.js +44 -44
  30. package/dist/model/model-gateway.d.ts +4 -0
  31. package/dist/model/model-gateway.js.map +1 -1
  32. package/dist/model/model-metadata.json +137 -137
  33. package/dist/model/openai-chat-mapper.js +2 -0
  34. package/dist/model/openai-chat-mapper.js.map +1 -1
  35. package/dist/model/openai-responses-mapper.js +3 -0
  36. package/dist/model/openai-responses-mapper.js.map +1 -1
  37. package/dist/model/tool-input-stream.d.ts +31 -0
  38. package/dist/model/tool-input-stream.js +226 -0
  39. package/dist/model/tool-input-stream.js.map +1 -0
  40. package/dist/repl/index.js +30 -30
  41. package/dist/repl/index.js.map +1 -1
  42. package/dist/session/session-store.js +2 -0
  43. package/dist/session/session-store.js.map +1 -1
  44. package/dist/session/tool-result-memory.js +2 -2
  45. package/dist/session/tool-result-memory.js.map +1 -1
  46. package/dist/tasks/subagent-tools.js +1 -1
  47. package/dist/tasks/subagent-tools.js.map +1 -1
  48. package/dist/tools/builtins/exec-process-manager.d.ts +2 -1
  49. package/dist/tools/builtins/exec-process-manager.js +2 -1
  50. package/dist/tools/builtins/exec-process-manager.js.map +1 -1
  51. package/dist/tools/builtins/exec-tool.js +1 -1
  52. package/dist/tools/builtins/exec-tool.js.map +1 -1
  53. package/dist/tools/builtins/image-capabilities.d.ts +2 -2
  54. package/dist/tools/builtins/image-capabilities.js +3 -3
  55. package/dist/tools/builtins/image-capabilities.js.map +1 -1
  56. package/dist/tools/builtins/image-generation-tool.js +1 -1
  57. package/dist/tools/builtins/image-generation-tool.js.map +1 -1
  58. package/dist/tools/builtins/image-loader-tool.js +2 -1
  59. package/dist/tools/builtins/image-loader-tool.js.map +1 -1
  60. package/dist/tools/run-tool-use.js +2 -1
  61. package/dist/tools/run-tool-use.js.map +1 -1
  62. package/dist/tools/terminal-output-store.d.ts +1 -0
  63. package/dist/tools/terminal-output-store.js +13 -1
  64. package/dist/tools/terminal-output-store.js.map +1 -1
  65. package/dist/types/events.d.ts +11 -0
  66. package/dist/web/demo-tools.js +1 -1
  67. package/dist/web/demo-tools.js.map +1 -1
  68. package/dist/web/html.js +5 -5
  69. package/dist/web/image-result-metadata.d.ts +6 -0
  70. package/dist/web/image-result-metadata.js +3 -0
  71. package/dist/web/image-result-metadata.js.map +1 -1
  72. package/dist/web/index.d.ts +15 -1
  73. package/dist/web/index.js +134 -20
  74. package/dist/web/index.js.map +1 -1
  75. package/dist/web/terminal-presentation.js +1 -1
  76. package/dist/web/terminal-presentation.js.map +1 -1
  77. package/dist/web/tool-activity.d.ts +9 -0
  78. package/dist/web/tool-activity.js +19 -0
  79. package/dist/web/tool-activity.js.map +1 -0
  80. package/dist/web/tool-observation-result.d.ts +1 -0
  81. package/dist/web/tool-observation-result.js +35 -0
  82. package/dist/web/tool-observation-result.js.map +1 -0
  83. package/docs/prompt-config.md +86 -86
  84. package/package.json +1 -1
  85. package/scripts/copy-prompt-resources.mjs +5 -5
package/dist/web/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { applyToolInputChanges } from "../model/tool-input-stream.js";
3
+ import { toolActivity } from "./tool-activity.js";
4
+ import { toolObservationResult } from "./tool-observation-result.js";
2
5
  import { DemoMode, DEMO_MARKER, DEMO_PROMPT } from "./demo-mode.js";
3
6
  import { DemoTools } from "./demo-tools.js";
4
7
  import { createTextMessage } from "../types/messages.js";
@@ -224,6 +227,7 @@ export async function createWebRuntime(options = {}) {
224
227
  taskNotificationSource: createTaskNotificationSource(taskStore),
225
228
  commands: replCommandDefinitions.map((command) => command.usage),
226
229
  plugins: [...(options.plugins ?? []), ...activePlugins.map((plugin) => plugin.id)],
230
+ workspacePaths: options.workspacePaths,
227
231
  cwdTransitionPaths: options.cwdTransitionPaths,
228
232
  onCwdTransitionConsumed: options.onCwdTransitionConsumed,
229
233
  session: {
@@ -562,6 +566,7 @@ export class WebRepl {
562
566
  lineId = 0;
563
567
  assistantLineId;
564
568
  thinkingLineId;
569
+ previousTurnToolId;
565
570
  finalizedThinkingLineId;
566
571
  liveToolLineIds = new Map();
567
572
  activeAbortController;
@@ -723,7 +728,7 @@ export class WebRepl {
723
728
  redact: value => this.runtime.engine.redactDisplayValue(value),
724
729
  }, request);
725
730
  }
726
- terminalOutput(sessionId, runId, stream, offset, limitBytes) {
731
+ terminalOutput(sessionId, runId, stream, offset, limitBytes, tail = false) {
727
732
  const session = this.runtime.engine.snapshot().session;
728
733
  if (!session?.sessionDir || !sessionId || sessionId !== session.sessionId || !runId || !["stdout", "stderr"].includes(stream))
729
734
  return undefined;
@@ -733,7 +738,7 @@ export class WebRepl {
733
738
  const registered = manager.registerOwnerSession(sessionId, session.sessionDir);
734
739
  if (!registered.ok)
735
740
  return undefined;
736
- const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes });
741
+ const page = manager.readOutput(sessionId, runId, { stream: stream, offset, limitBytes, tail });
737
742
  // Cursor addresses stored bytes, not the possibly additionally redacted response length.
738
743
  return page.ok ? this.runtime.engine.redactDisplayValue({ sessionId, runId, ...page.value }) : undefined;
739
744
  }
@@ -1138,6 +1143,7 @@ export class WebRepl {
1138
1143
  this.runtime.usage.reset();
1139
1144
  this.status = initialStatus(this.runtime, metrics);
1140
1145
  const lineId = { current: 0 };
1146
+ this.previousTurnToolId = undefined;
1141
1147
  this.lines = initialLines(this.runtime, lineId);
1142
1148
  this.lineId = lineId.current;
1143
1149
  this.assistantLineId = undefined;
@@ -1317,6 +1323,9 @@ export class WebRepl {
1317
1323
  return actionFailure("COMPACTION_IN_PROGRESS", "Context compaction is already running");
1318
1324
  const showProgress = !this.busy;
1319
1325
  const runToken = this.foregroundRunToken;
1326
+ const controller = showProgress ? new AbortController() : undefined;
1327
+ if (controller)
1328
+ this.activeAbortController = controller;
1320
1329
  let finishForeground;
1321
1330
  const foreground = showProgress ? new Promise(resolve => { finishForeground = resolve; }) : undefined;
1322
1331
  if (foreground)
@@ -1328,7 +1337,7 @@ export class WebRepl {
1328
1337
  }
1329
1338
  try {
1330
1339
  // An idle click executes now through the settings barrier, not the chat/input queue.
1331
- const result = await engine.updateSessionSettings({ compact: true });
1340
+ const result = await engine.updateSessionSettings({ compact: true }, { abortSignal: controller?.signal });
1332
1341
  if (this.runtime.engine === engine && this.foregroundRunToken === runToken) {
1333
1342
  if (result.compaction?.report)
1334
1343
  this.appendCompaction(result.compaction.report);
@@ -1342,6 +1351,8 @@ export class WebRepl {
1342
1351
  return actionFailure("COMPACTION_FAILED", error instanceof Error ? error.message : String(error));
1343
1352
  }
1344
1353
  finally {
1354
+ if (controller && this.activeAbortController === controller)
1355
+ this.activeAbortController = undefined;
1345
1356
  if (this.compactingSession === engine)
1346
1357
  this.compactingSession = undefined;
1347
1358
  if (this.foregroundRun === foreground)
@@ -1483,6 +1494,10 @@ export class WebRepl {
1483
1494
  append(line, streaming = false) {
1484
1495
  const id = ++this.lineId;
1485
1496
  const nextLine = { id, ...line };
1497
+ if (line.activity && line.toolName && line.toolUseId && !line.image) {
1498
+ nextLine.connectPreviousTool = this.previousTurnToolId !== undefined;
1499
+ this.previousTurnToolId = id;
1500
+ }
1486
1501
  this.lines.push(nextLine);
1487
1502
  if (streaming)
1488
1503
  this.broadcastDelta({ type: "line.append", line: nextLine });
@@ -1505,6 +1520,9 @@ export class WebRepl {
1505
1520
  this.broadcastSync();
1506
1521
  }
1507
1522
  replaceLine(id, patch) {
1523
+ const previous = this.lines.find(line => line.id === id);
1524
+ if (previous?.activity && patch.titleStatus)
1525
+ patch.activity = { ...previous.activity, phase: patch.titleStatus === "success" ? "completed" : "failed" };
1508
1526
  this.lines = this.lines.map((line) => line.id === id ? { ...line, ...patch } : line);
1509
1527
  this.broadcastSync();
1510
1528
  }
@@ -1653,6 +1671,7 @@ export class WebRepl {
1653
1671
  signal: terminal.signal,
1654
1672
  terminationReason: terminal.termination_reason,
1655
1673
  outputAvailability: terminal.output_ref.availability,
1674
+ outputRevision: terminal.output_ref.streams,
1656
1675
  }));
1657
1676
  const sessionTasks = [...this.backgroundSessionRuns.values()].map((run) => ({
1658
1677
  kind: "session",
@@ -1742,6 +1761,12 @@ export class WebRepl {
1742
1761
  finalizeLiveLine(id) {
1743
1762
  if (id === undefined)
1744
1763
  return;
1764
+ const previous = this.lines.find(line => line.id === id);
1765
+ if (previous?.activity && ["generating", "queued", "running"].includes(previous.activity.phase)) {
1766
+ const activity = { ...previous.activity, phase: previous.titleStatus === "success" ? "completed" : previous.titleStatus === "failure" ? "failed" : "interrupted" };
1767
+ this.lines = this.lines.map(line => line.id === id ? { ...line, activity } : line);
1768
+ this.queueDeltaOperation({ type: "line.patch", id, patch: { activity } });
1769
+ }
1745
1770
  this.lines = this.lines.map((line) => line.id === id ? { ...line, live: false } : line);
1746
1771
  this.queueDeltaOperation({ type: "line.patch", id, patch: { live: false } });
1747
1772
  }
@@ -1761,6 +1786,8 @@ export class WebRepl {
1761
1786
  cancelLiveToolLines(reason) {
1762
1787
  for (const id of this.liveToolLineIds.values()) {
1763
1788
  const line = this.lines.find((item) => item.id === id);
1789
+ if (line?.activity)
1790
+ this.patchLiveToolLine(line.toolUseId, { activity: { ...line.activity, phase: "interrupted" } });
1764
1791
  const steps = (line?.toolStream?.steps ?? []).map((step) => step.status === "running" ? { ...step, status: "failed", phase: "cancelled" } : step);
1765
1792
  this.lines = this.lines.map((item) => item.id === id ? { ...item, live: false, kind: "error", titleStatus: "failure", bodyTitle: reason, toolStream: { ...item.toolStream, phase: "cancelled", message: reason, steps } } : item);
1766
1793
  this.queueDeltaOperation({ type: "line.patch", id, patch: { live: false, kind: "error", titleStatus: "failure", bodyTitle: reason, toolStream: { ...line?.toolStream, phase: "cancelled", message: reason, steps } } });
@@ -1861,7 +1888,12 @@ export class WebRepl {
1861
1888
  else {
1862
1889
  stream.message = progress.message;
1863
1890
  }
1864
- this.patchLiveToolLine(event.toolUse.id, { toolStream: stream });
1891
+ const data = progress.data;
1892
+ const observation = data && typeof data.task_id === "string" && Number.isSafeInteger(data.run_generation)
1893
+ ? { ...line.observation, task_id: data.task_id, run_generation: data.run_generation }
1894
+ : data && typeof data.session_id === "string" && ["terminal.started", "terminal.output.delta"].includes(String(data.type)) && ["terminal_run", "terminal_control"].includes(event.toolUse.name)
1895
+ ? { ...line.observation, session_id: data.session_id, status: "running", command: typeof data.command === "string" ? data.command : line.observation?.command } : line.observation;
1896
+ this.patchLiveToolLine(event.toolUse.id, { toolStream: stream, observation });
1865
1897
  }
1866
1898
  applyAvailableToolResult(event) {
1867
1899
  const liveLineId = this.liveToolLineIds.get(event.toolUse.id);
@@ -1893,19 +1925,28 @@ export class WebRepl {
1893
1925
  }
1894
1926
  }
1895
1927
  handleEvent(event) {
1928
+ // Model lifecycle defines membership only. The frontend owns the fixed
1929
+ // two-second playback and must not be interrupted by the next request.
1930
+ if (event.type === "state" && event.phase === "calling_model")
1931
+ this.previousTurnToolId = undefined;
1896
1932
  this.reduce(event);
1897
1933
  if (event.type === "timing.updated") {
1898
1934
  if (event.timing.kind === "tool") {
1899
1935
  const line = this.lines.find(item => item.timing?.id === event.timing.id)
1900
1936
  ?? this.lines.find(item => item.id === this.liveToolLineIds.get(event.timing.toolUseId));
1901
1937
  if (line) {
1902
- this.lines = this.lines.map(item => item.id === line.id ? { ...item, timing: event.timing } : item);
1903
- this.queueDeltaOperation({ type: "line.patch", id: line.id, patch: { timing: event.timing } });
1938
+ const activity = line.activity && event.timing.status === "running" ? { ...line.activity, phase: "running" } : line.activity;
1939
+ this.lines = this.lines.map(item => item.id === line.id ? { ...item, timing: event.timing, activity } : item);
1940
+ this.queueDeltaOperation({ type: "line.patch", id: line.id, patch: { timing: event.timing, activity } });
1904
1941
  }
1905
1942
  }
1906
1943
  this.queueDeltaStatus();
1907
1944
  return;
1908
1945
  }
1946
+ if (event.type === "message" && event.message.metadata?.loadedImages === true && typeof event.message.metadata.sourceToolUseId === "string") {
1947
+ renderObservedImages(event.message, line => this.append(line));
1948
+ return;
1949
+ }
1909
1950
  if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
1910
1951
  const messageId = this.pendingUserImageEchoMessageId;
1911
1952
  this.pendingUserImageEchoIds = undefined;
@@ -1914,7 +1955,35 @@ export class WebRepl {
1914
1955
  this.broadcastSync();
1915
1956
  return;
1916
1957
  }
1917
- if (event.type === "tool_call.started" || event.type === "tool_call.delta") {
1958
+ if (event.type === "tool_call.started" || event.type === "tool_call.input") {
1959
+ let id = this.liveToolLineIds.get(event.callId);
1960
+ let previous = this.lines.find(line => line.id === id);
1961
+ const name = event.name || previous?.toolName || "tool";
1962
+ if (id === undefined) {
1963
+ const activity = { phase: "generating", input: {} };
1964
+ id = this.append({ ...formatToolUse({ id: event.callId, name, input: {} }), activity, live: true });
1965
+ this.liveToolLineIds.set(event.callId, id);
1966
+ previous = this.lines.find(line => line.id === id);
1967
+ }
1968
+ if (previous?.toolName !== name && name !== "tool")
1969
+ this.patchLiveToolLine(event.callId, { toolName: name, title: toolTitle(name, "running"), toolPresentation: toolPresentationForSource(name) });
1970
+ if (event.type === "tool_call.input") {
1971
+ const activity = { phase: "generating", input: applyToolInputChanges(previous?.activity?.input || {}, event.changes), truncated: event.truncated };
1972
+ this.lines = this.lines.map(line => line.id === id ? { ...line, activity } : line);
1973
+ this.queueDeltaOperation({ type: "line.activity.input", id, changes: event.changes, truncated: event.truncated });
1974
+ }
1975
+ this.queueDeltaStatus();
1976
+ return;
1977
+ }
1978
+ if (event.type === "tool_call.ready") {
1979
+ const patch = { ...formatToolUse(event.toolUse), activity: toolActivity(event.toolUse.input, "queued"), live: true };
1980
+ if (this.liveToolLineIds.has(event.toolUse.id))
1981
+ this.patchLiveToolLine(event.toolUse.id, patch);
1982
+ else
1983
+ this.liveToolLineIds.set(event.toolUse.id, this.append(patch));
1984
+ return;
1985
+ }
1986
+ if (event.type === "tool_call.delta") {
1918
1987
  this.queueDeltaStatus();
1919
1988
  return;
1920
1989
  }
@@ -1926,6 +1995,10 @@ export class WebRepl {
1926
1995
  this.broadcastSync();
1927
1996
  return;
1928
1997
  }
1998
+ if (event.type === "assistant.output.done") {
1999
+ this.queueDeltaStatus();
2000
+ return;
2001
+ }
1929
2002
  if (event.type === "assistant.delta") {
1930
2003
  this.finalizeThinkingLine();
1931
2004
  const id = this.assistantLineId ?? this.append({ kind: "assistant", text: "", live: true }, true);
@@ -2018,8 +2091,12 @@ export class WebRepl {
2018
2091
  this.finalizeLiveLine(this.assistantLineId);
2019
2092
  this.finalizeThinkingLine();
2020
2093
  this.assistantLineId = undefined;
2021
- const id = this.append({ ...formatToolUse(event.toolUse), live: true });
2022
- this.liveToolLineIds.set(event.toolUse.id, id);
2094
+ const patch = { ...formatToolUse(event.toolUse), activity: toolActivity(event.toolUse.input, "queued"), live: true };
2095
+ const existing = this.liveToolLineIds.get(event.toolUse.id);
2096
+ if (existing === undefined)
2097
+ this.liveToolLineIds.set(event.toolUse.id, this.append(patch));
2098
+ else
2099
+ this.patchLiveToolLine(event.toolUse.id, patch);
2023
2100
  return;
2024
2101
  }
2025
2102
  if (event.type === "tool.finished") {
@@ -2409,11 +2486,16 @@ export function compactLineOperations(operations) {
2409
2486
  previous.text += operation.text;
2410
2487
  continue;
2411
2488
  }
2489
+ if (operation.type === "line.activity.input" && previous?.type === "line.activity.input" && previous.id === operation.id) {
2490
+ previous.changes.push(...operation.changes);
2491
+ previous.truncated = operation.truncated;
2492
+ continue;
2493
+ }
2412
2494
  if (operation.type === "line.patch" && previous?.type === "line.patch" && previous.id === operation.id) {
2413
2495
  previous.patch = { ...previous.patch, ...operation.patch };
2414
2496
  continue;
2415
2497
  }
2416
- compacted.push(operation.type === "line.patch" ? { ...operation, patch: { ...operation.patch } } : { ...operation });
2498
+ compacted.push(operation.type === "line.patch" ? { ...operation, patch: { ...operation.patch } } : operation.type === "line.activity.input" ? { ...operation, changes: [...operation.changes] } : { ...operation });
2417
2499
  }
2418
2500
  return compacted;
2419
2501
  }
@@ -2471,7 +2553,7 @@ export async function handleWebRequest(req, res, router) {
2471
2553
  }
2472
2554
  if (req.method === "GET" && url.pathname === "/api/terminal-output") {
2473
2555
  res.setHeader("Cache-Control", "no-store");
2474
- const page = repl.terminalOutput(url.searchParams.get("sessionId") ?? "", url.searchParams.get("runId") ?? "", url.searchParams.get("stream") ?? "stdout", Number(url.searchParams.get("offset") ?? 0), Number(url.searchParams.get("limitBytes") ?? 65536));
2556
+ const page = repl.terminalOutput(url.searchParams.get("sessionId") ?? "", url.searchParams.get("runId") ?? "", url.searchParams.get("stream") ?? "stdout", Number(url.searchParams.get("offset") ?? 0), Number(url.searchParams.get("limitBytes") ?? 65536), url.searchParams.get("tail") === "true");
2475
2557
  return page ? sendJson(res, page) : sendJson(res, { error: "终端输出不可获取或无权访问" }, 404);
2476
2558
  }
2477
2559
  if (req.method === "GET" && url.pathname === "/api/state")
@@ -2479,6 +2561,15 @@ export async function handleWebRequest(req, res, router) {
2479
2561
  if (req.method === "GET" && url.pathname === "/api/runtime-context")
2480
2562
  return sendJson(res, await repl.runtimeContext());
2481
2563
  const workspaceRepl = repl;
2564
+ if (req.method === "GET" && url.pathname === "/api/cwd/scope") {
2565
+ return sendJson(res, workspaceRepl.workspaceScope?.() ?? actionFailure("CWD_NOT_CONFIGURED", "Workspace scope is not configured"));
2566
+ }
2567
+ if (req.method === "POST" && ["/api/cwd/add", "/api/cwd/remove"].includes(url.pathname)) {
2568
+ const body = await readJsonBody(req);
2569
+ return sendJson(res, workspaceRepl.updateWorkspaceScope
2570
+ ? await workspaceRepl.updateWorkspaceScope(body.path, url.pathname.endsWith("/remove"))
2571
+ : actionFailure("CWD_NOT_CONFIGURED", "Workspace scope is not configured"));
2572
+ }
2482
2573
  if (req.method === "GET" && url.pathname === "/api/cwd") {
2483
2574
  return sendJson(res, workspaceRepl.browseWorkspace
2484
2575
  ? await workspaceRepl.browseWorkspace(url.searchParams.get("path") ?? undefined)
@@ -2737,7 +2828,7 @@ function restoredHistoryLines(runtime) {
2737
2828
  if (line.toolUseId && line.titleStatus && historyToolLineIndexes.has(line.toolUseId)) {
2738
2829
  const index = historyToolLineIndexes.get(line.toolUseId);
2739
2830
  const started = lines[index];
2740
- lines[index] = { ...line, presentationLevel: mergePresentationLevel(line.presentationLevel, started.presentationLevel), toolDisplay: mergeToolDisplays(line.toolDisplay, started.toolDisplay) };
2831
+ lines[index] = { ...line, activity: started.activity ? { ...started.activity, phase: line.titleStatus === "success" ? "completed" : "failed" } : undefined, presentationLevel: mergePresentationLevel(line.presentationLevel, started.presentationLevel), toolDisplay: mergeToolDisplays(line.toolDisplay, started.toolDisplay) };
2741
2832
  return index + 1;
2742
2833
  }
2743
2834
  lines.push(line);
@@ -2763,6 +2854,10 @@ function restoredHistoryLines(runtime) {
2763
2854
  }
2764
2855
  for (const [entryIndex, entry] of entries.entries()) {
2765
2856
  if (entry.type === "message") {
2857
+ if (entry.message.metadata?.loadedImages === true && typeof entry.message.metadata.sourceToolUseId === "string") {
2858
+ renderObservedImages(entry.message, append);
2859
+ continue;
2860
+ }
2766
2861
  renderMessage(entry.message, append, undefined, {
2767
2862
  includeToolUseBlocks: true,
2768
2863
  includeThinkingBlocks: true,
@@ -2778,6 +2873,8 @@ function restoredHistoryLines(runtime) {
2778
2873
  const matches = line.toolUseId ? toolTimings.get(line.toolUseId) : undefined;
2779
2874
  if (matches?.length === 1)
2780
2875
  line.timing = matches[0];
2876
+ if (line.activity && !line.titleStatus)
2877
+ line.activity = { ...line.activity, phase: "interrupted" };
2781
2878
  }
2782
2879
  return lines;
2783
2880
  }
@@ -2933,6 +3030,12 @@ function reduceStatus(status, event) {
2933
3030
  return { ...status, metrics: event.metrics, inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt, activityTick: status.activityTick + 1 };
2934
3031
  if (event.type === "usage")
2935
3032
  return { ...status, usage: event.usage, inputTokenUpdatedAt: event.usage.inputTokens !== undefined ? Date.now() : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.usage.outputTokens !== undefined ? Date.now() : status.outputTokenUpdatedAt, activityTick: status.activityTick + 1 };
3033
+ if (event.type === "assistant.output.done")
3034
+ return status.modelOutput?.kind === "text" ? { ...status, modelOutput: undefined, activityTick: status.activityTick + 1 } : status;
3035
+ if (event.type === "message" && event.message.role === "assistant" && event.message.blocks.some(block => block.type === "text") && status.modelOutput?.kind === "text")
3036
+ return { ...status, modelOutput: undefined, activityTick: status.activityTick + 1 };
3037
+ if (event.type === "assistant.delta" && !event.text)
3038
+ return status;
2936
3039
  if (event.type === "assistant.delta")
2937
3040
  return { ...status, phase: "calling_model", modelOutput: { kind: "text" }, streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2938
3041
  if (event.type === "thinking.delta")
@@ -3323,6 +3426,15 @@ function renderMessageImages(message, append, messageId = message.id) {
3323
3426
  }
3324
3427
  return rendered;
3325
3428
  }
3429
+ function renderObservedImages(message, append) {
3430
+ for (const [index, block] of message.blocks.entries()) {
3431
+ if (block.type !== "image")
3432
+ continue;
3433
+ const line = imageLineForBlock("tool_result", block, message.id, index);
3434
+ if (line)
3435
+ append({ ...line, observationOnly: true, messageId: message.id, parentToolName: "image_inspect", parentToolUseId: message.metadata?.sourceToolUseId });
3436
+ }
3437
+ }
3326
3438
  function imageLineForBlock(role, block, messageId, blockIndex) {
3327
3439
  const kind = kindForRole(role);
3328
3440
  if (kind === "meta")
@@ -3450,14 +3562,15 @@ function thinkingLine(text, live = false) {
3450
3562
  return { kind: "thinking", title: titleForKind("thinking"), text, previewStyle: "summary", summaryMaxLines: THINKING_SUMMARY_MAX_LINES, live };
3451
3563
  }
3452
3564
  function formatToolUse(toolUse) {
3565
+ const activity = toolActivity(toolUse.input, "running");
3453
3566
  const toolPresentation = toolPresentationForSource(toolUse.name);
3454
3567
  const presentationLevel = toolPresentationLevel(toolUse.name, toolUse.input);
3455
3568
  if (toolUse.name === "plan_update" && isWebPlanPayload(toolUse.input))
3456
- return { kind: "tool", toolName: toolUse.name, toolPresentation, title: toolPresentation.label, bodyTitle: webPlanBodyTitle(toolUse.input), text: serializeWebPlanPayload(toolUse.input), presentationLevel, collapsible: true };
3569
+ return { kind: "tool", activity, toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: webPlanBodyTitle(toolUse.input), text: serializeWebPlanPayload(toolUse.input), presentationLevel, collapsible: true };
3457
3570
  if (toolUse.name === "image_create")
3458
- return { kind: "tool", toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", presentationLevel: "primary", collapsible: true };
3571
+ return { kind: "tool", activity, toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", presentationLevel: "primary", collapsible: true };
3459
3572
  const summary = summarizeToolUse(toolUse.name, toolUse.input);
3460
- return { kind: "tool", toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: summary.bodyTitle, text: summary.text, toolDisplay: buildToolUseDisplay(toolUse.name, toolUse.input), previewStyle: "summary", presentationLevel, collapsible: true };
3573
+ return { kind: "tool", activity, toolName: toolUse.name, toolPresentation, toolUseId: toolUse.id, title: toolPresentation.label, bodyTitle: summary.bodyTitle, text: summary.text, toolDisplay: buildToolUseDisplay(toolUse.name, toolUse.input), previewStyle: "summary", presentationLevel, collapsible: true };
3461
3574
  }
3462
3575
  function formatToolResultLine(toolName, output, ok, presentToolResult) {
3463
3576
  // Hidden fallback reports in historical agent payloads must not enter line.text
@@ -3472,6 +3585,7 @@ function formatToolResultLine(toolName, output, ok, presentToolResult) {
3472
3585
  : formatToolResult(toolName, toolName.startsWith("subagent_") ? redactToolDetail(output) : output, ok);
3473
3586
  return {
3474
3587
  kind: ok ? "tool" : "error",
3588
+ observation: toolObservationResult(toolName, output),
3475
3589
  toolError: ok ? undefined : toolErrorText(output, ok).slice(0, 2000),
3476
3590
  toolName,
3477
3591
  toolPresentation: toolPresentationForSource(toolName),
@@ -3982,13 +4096,13 @@ function formatImageGenerationToolResult(output, ok) {
3982
4096
  const lines = [`${mode === "edit" ? "edited" : "generated"} ${returnedImages ?? 0} image${returnedImages === 1 ? "" : "s"}`];
3983
4097
  const details = [provider, model, size, quality && quality !== "auto" ? quality : undefined, format].filter((value) => Boolean(value));
3984
4098
  if (details.length > 0)
3985
- lines.push(`${output.requested ? "requested: " : ""}${details.join(" · ")}`);
4099
+ lines.push(`${output.requested ? "requested: " : ""}${details.join(" ")}`);
3986
4100
  if (isRecord(output.actual)) {
3987
4101
  lines.push(`upstream-reported quality: ${String(output.actual.quality ?? "unreported")}`);
3988
4102
  if (Array.isArray(output.images))
3989
4103
  for (const image of output.images.filter(isRecord)) {
3990
4104
  if (typeof image.width === "number" && typeof image.height === "number")
3991
- lines.push(`actual image: ${image.width}x${image.height} · ${String(image.mimeType ?? "")}`);
4105
+ lines.push(`actual image: ${image.width}x${image.height} ${String(image.mimeType ?? "")}`);
3992
4106
  }
3993
4107
  }
3994
4108
  if (Array.isArray(output.warnings))
@@ -4033,7 +4147,7 @@ function formatListToolResult(output, ok) {
4033
4147
  lines.push(`type: ${typeValue}`);
4034
4148
  const counts = [returnedEntries !== undefined ? `${returnedEntries} shown` : undefined, totalFiles !== undefined ? `${totalFiles} files` : undefined, totalDirectories !== undefined ? `${totalDirectories} dirs` : undefined].filter((value) => Boolean(value));
4035
4149
  if (counts.length > 0)
4036
- lines.push(`entries: ${counts.join(" · ")}`);
4150
+ lines.push(`entries: ${counts.join(" ")}`);
4037
4151
  if (names.length > 0)
4038
4152
  lines.push("sample:", ...names.map((name) => ` ${name}`));
4039
4153
  return lines.join("\n");
@@ -4079,7 +4193,7 @@ function formatWebSearchToolResult(output, ok) {
4079
4193
  return;
4080
4194
  const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Untitled";
4081
4195
  const url = typeof item.url === "string" ? item.url : "";
4082
- const published = typeof item.published === "string" ? ` · ${item.published}` : "";
4196
+ const published = typeof item.published === "string" ? ` ${item.published}` : "";
4083
4197
  lines.push(`[${index + 1}] ${title}${published}`);
4084
4198
  if (url)
4085
4199
  lines.push(url);
@@ -4106,7 +4220,7 @@ function formatGrepToolResult(output, ok) {
4106
4220
  if (grepPath !== undefined)
4107
4221
  lines.push(`path: ${grepPath}`);
4108
4222
  const countParts = [`${returnedMatches ?? matches.length} shown`, totalMatchesKnown !== undefined ? `${totalMatchesKnown} known` : undefined, truncated ? "truncated" : undefined].filter((value) => Boolean(value));
4109
- lines.push(`matches: ${countParts.join(" · ")}`);
4223
+ lines.push(`matches: ${countParts.join(" ")}`);
4110
4224
  if (matches.length === 0)
4111
4225
  return [...lines, "no matches"].join("\n");
4112
4226
  lines.push("results:");