neoctl 0.2.17 → 0.2.23

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 (52) hide show
  1. package/README.md +11 -9
  2. package/dist/app/app-prompt.d.ts +2 -0
  3. package/dist/app/app-prompt.js +2 -0
  4. package/dist/app/app-prompt.js.map +1 -1
  5. package/dist/context/compaction.d.ts +12 -0
  6. package/dist/context/compaction.js +22 -0
  7. package/dist/context/compaction.js.map +1 -1
  8. package/dist/context/smoke-context.js +30 -3
  9. package/dist/context/smoke-context.js.map +1 -1
  10. package/dist/core/query.js +8 -4
  11. package/dist/core/query.js.map +1 -1
  12. package/dist/model/config.js +1 -1
  13. package/dist/model/env.js +1 -1
  14. package/dist/model/model-metadata.json +70 -6
  15. package/dist/repl/commands.js +1 -1
  16. package/dist/repl/index.js +2 -2
  17. package/dist/tools/builtins/ripgrep-binary.js +1 -1
  18. package/dist/tools/builtins/ripgrep-binary.js.map +1 -1
  19. package/dist/web/html.js +22 -3
  20. package/dist/web/html.js.map +1 -1
  21. package/dist/web/index.d.ts +35 -1
  22. package/dist/web/index.js +160 -33
  23. package/dist/web/index.js.map +1 -1
  24. package/package.json +5 -3
  25. package/scripts/copy-model-metadata.mjs +4 -4
  26. package/scripts/install-ripgrep.cjs +210 -196
  27. package/scripts/release-local.mjs +190 -190
  28. package/scripts/verify-ripgrep.cjs +86 -0
  29. package/vendor/ripgrep/darwin-arm64/COPYING +3 -3
  30. package/vendor/ripgrep/darwin-arm64/LICENSE-MIT +21 -21
  31. package/vendor/ripgrep/darwin-arm64/UNLICENSE +24 -24
  32. package/vendor/ripgrep/darwin-arm64/manifest.json +7 -7
  33. package/vendor/ripgrep/darwin-x64/COPYING +3 -3
  34. package/vendor/ripgrep/darwin-x64/LICENSE-MIT +21 -21
  35. package/vendor/ripgrep/darwin-x64/UNLICENSE +24 -24
  36. package/vendor/ripgrep/darwin-x64/manifest.json +7 -7
  37. package/vendor/ripgrep/linux-arm64/COPYING +3 -3
  38. package/vendor/ripgrep/linux-arm64/LICENSE-MIT +21 -21
  39. package/vendor/ripgrep/linux-arm64/UNLICENSE +24 -24
  40. package/vendor/ripgrep/linux-arm64/manifest.json +7 -7
  41. package/vendor/ripgrep/linux-x64/COPYING +3 -3
  42. package/vendor/ripgrep/linux-x64/LICENSE-MIT +21 -21
  43. package/vendor/ripgrep/linux-x64/UNLICENSE +24 -24
  44. package/vendor/ripgrep/linux-x64/manifest.json +7 -7
  45. package/vendor/ripgrep/win32-arm64/COPYING +3 -3
  46. package/vendor/ripgrep/win32-arm64/LICENSE-MIT +21 -21
  47. package/vendor/ripgrep/win32-arm64/UNLICENSE +24 -24
  48. package/vendor/ripgrep/win32-arm64/manifest.json +7 -7
  49. package/vendor/ripgrep/win32-x64/COPYING +3 -3
  50. package/vendor/ripgrep/win32-x64/LICENSE-MIT +21 -21
  51. package/vendor/ripgrep/win32-x64/UNLICENSE +24 -24
  52. package/vendor/ripgrep/win32-x64/manifest.json +7 -7
@@ -1,5 +1,5 @@
1
1
  {
2
- "updatedAt": "2026-05-10",
2
+ "updatedAt": "2026-08-04",
3
3
  "notes": [
4
4
  "Static model metadata used for local REPL telemetry. Values are best-effort and should be refreshed when providers update model cards.",
5
5
  "MODEL_CONTEXT_WINDOW_TOKENS or OPENAI_CONTEXT_WINDOW_TOKENS overrides contextWindowTokens at runtime.",
@@ -8,7 +8,7 @@
8
8
  "reasoningEfforts lists the exact reasoning.effort values the /model command may set; omitted/empty means no supported effort enum is configured for that model."
9
9
  ],
10
10
  "sources": {
11
- "openai-models": "https://platform.openai.com/docs/models",
11
+ "openai-models": "https://developers.openai.com/api/docs/models",
12
12
  "anthropic-models": "https://docs.anthropic.com/en/docs/about-claude/models/overview",
13
13
  "anthropic-context-windows": "https://docs.anthropic.com/en/docs/build-with-claude/context-windows",
14
14
  "gemini-models": "https://ai.google.dev/gemini-api/docs/models",
@@ -16,10 +16,74 @@
16
16
  "anthropic-vision": "https://docs.anthropic.com/en/docs/build-with-claude/vision",
17
17
  "mistral-vision": "https://docs.mistral.ai/capabilities/vision",
18
18
  "openai-reasoning": "https://developers.openai.com/api/docs/guides/reasoning"
19
- },
20
- "models": [
21
- {
22
- "id": "gpt-5.5",
19
+ },
20
+ "models": [
21
+ {
22
+ "id": "gpt-5.6-sol",
23
+ "provider": "openai",
24
+ "modelIds": [
25
+ "gpt-5.6",
26
+ "gpt-5.6-sol"
27
+ ],
28
+ "contextWindowTokens": 1050000,
29
+ "maxOutputTokens": 128000,
30
+ "knowledgeCutoff": "2026-02-16",
31
+ "reasoning": true,
32
+ "imageInput": true,
33
+ "source": "openai-models",
34
+ "reasoningEfforts": [
35
+ "none",
36
+ "low",
37
+ "medium",
38
+ "high",
39
+ "xhigh",
40
+ "max"
41
+ ]
42
+ },
43
+ {
44
+ "id": "gpt-5.6-terra",
45
+ "provider": "openai",
46
+ "modelIds": [
47
+ "gpt-5.6-terra"
48
+ ],
49
+ "contextWindowTokens": 1050000,
50
+ "maxOutputTokens": 128000,
51
+ "knowledgeCutoff": "2026-02-16",
52
+ "reasoning": true,
53
+ "imageInput": true,
54
+ "source": "openai-models",
55
+ "reasoningEfforts": [
56
+ "none",
57
+ "low",
58
+ "medium",
59
+ "high",
60
+ "xhigh",
61
+ "max"
62
+ ]
63
+ },
64
+ {
65
+ "id": "gpt-5.6-luna",
66
+ "provider": "openai",
67
+ "modelIds": [
68
+ "gpt-5.6-luna"
69
+ ],
70
+ "contextWindowTokens": 1050000,
71
+ "maxOutputTokens": 128000,
72
+ "knowledgeCutoff": "2026-02-16",
73
+ "reasoning": true,
74
+ "imageInput": true,
75
+ "source": "openai-models",
76
+ "reasoningEfforts": [
77
+ "none",
78
+ "low",
79
+ "medium",
80
+ "high",
81
+ "xhigh",
82
+ "max"
83
+ ]
84
+ },
85
+ {
86
+ "id": "gpt-5.5",
23
87
  "provider": "openai",
24
88
  "modelIds": [
25
89
  "gpt-5.5"
@@ -195,7 +195,7 @@ export function cliHelpText(binaryName = "neo") {
195
195
  ` ${binaryName} -web`,
196
196
  ` ${binaryName} -web --port 3001`,
197
197
  ` ${binaryName} -model`,
198
- ` ${binaryName} -model gpt-5.5 high`,
198
+ ` ${binaryName} -model gpt-5.6 high`,
199
199
  ` ${binaryName} -new`,
200
200
  "",
201
201
  "Inside the REPL, use the original slash form such as /help or /model.",
@@ -3090,7 +3090,7 @@ const LOGIN_FIELD_DEFINITIONS = {
3090
3090
  openai: [
3091
3091
  { key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
3092
3092
  { key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
3093
- { key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.5" },
3093
+ { key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.6" },
3094
3094
  { key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
3095
3095
  { key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
3096
3096
  ...SHARED_LOGIN_FIELDS,
@@ -3430,7 +3430,7 @@ function defaultBaseUrlForLoginProvider(provider) {
3430
3430
  function defaultModelForLoginProvider(provider) {
3431
3431
  if (provider === "anthropic")
3432
3432
  return "claude-sonnet-4-6";
3433
- return "gpt-5.5";
3433
+ return "gpt-5.6";
3434
3434
  }
3435
3435
  function loginFormViewHeight(state) {
3436
3436
  return state.step === "provider" ? state.providers.length + 3 : LOGIN_FIELD_DEFINITIONS[state.provider].length + 4;
@@ -16,7 +16,7 @@ export function resolveBundledRipgrepBinary(env = process.env) {
16
16
  if (fs.existsSync(executablePath))
17
17
  return { executablePath, platformKey };
18
18
  }
19
- throw new Error(`Bundled ripgrep binary not found for ${platformKey}. Run npm run vendor:rg before using the grep tool.`);
19
+ throw new Error(`Required bundled ripgrep binary not found for ${platformKey}. Reinstall neoctl or restore the Git-managed vendor/ripgrep resources.`);
20
20
  }
21
21
  export function ripgrepPlatformKey() {
22
22
  const key = `${process.platform}-${process.arch}`;
@@ -1 +1 @@
1
- {"version":3,"file":"ripgrep-binary.js","sourceRoot":"","sources":["../../../src/tools/builtins/ripgrep-binary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAOzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,UAAU,2BAA2B,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC9E,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC;IAEnC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9C,sBAAsB,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;QACxD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QACrF,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;YAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IAC5E,CAAC;IAED,MAAM,IAAI,KAAK,CACb,wCAAwC,WAAW,qDAAqD,CACzG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAClD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa,CAAC;QACnB,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO,GAAG,CAAC;QACb;YACE,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAsB;IAC5C,MAAM,eAAe,GAAG,OAAsD,CAAC;IAC/E,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,gBAAgB;QACpB,OAAO,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC;QACnC,eAAe,CAAC,aAAa;QAC7B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;KAC/B,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAErD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,sBAAsB,CAAC,cAAsB,EAAE,MAAc;IACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,wCAAwC,cAAc,EAAE,CAAC,CAAC;IACrF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"ripgrep-binary.js","sourceRoot":"","sources":["../../../src/tools/builtins/ripgrep-binary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAOzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,UAAU,2BAA2B,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC9E,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC;IAEnC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9C,sBAAsB,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;QACxD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;QACrF,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC;YAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;IAC5E,CAAC;IAED,MAAM,IAAI,KAAK,CACb,iDAAiD,WAAW,yEAAyE,CACtI,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAClD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa,CAAC;QACnB,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO,GAAG,CAAC;QACb;YACE,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAsB;IAC5C,MAAM,eAAe,GAAG,OAAsD,CAAC;IAC/E,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,gBAAgB;QACpB,OAAO,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC;QACnC,eAAe,CAAC,aAAa;QAC7B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;KAC/B,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAErD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,sBAAsB,CAAC,cAAsB,EAAE,MAAc;IACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,wCAAwC,cAAc,EAAE,CAAC,CAAC;IACrF,CAAC;AACH,CAAC"}
package/dist/web/html.js CHANGED
@@ -231,7 +231,7 @@ es.addEventListener('error', () => {
231
231
  connection.hidden = false;
232
232
  connection.textContent = 'reconnecting…';
233
233
  });
234
- es.addEventListener('sync', (event) => {
234
+ es.addEventListener('sync', (event) => {
235
235
  const payload = JSON.parse(event.data);
236
236
  state.lines = payload.lines || [];
237
237
  state.status = payload.status || state.status;
@@ -245,8 +245,27 @@ es.addEventListener('sync', (event) => {
245
245
  if (payload.catalog) state.catalog = payload.catalog;
246
246
  if (payload.interactive) state.interactive = payload.interactive;
247
247
  updateInputPlaceholder();
248
- scheduleRender();
249
- });
248
+ scheduleRender();
249
+ });
250
+ es.addEventListener('delta', (event) => {
251
+ const payload = JSON.parse(event.data);
252
+ for (const operation of payload.operations || []) {
253
+ if (operation.type === 'line.append') {
254
+ if (!state.lines.some((line) => String(line.id) === String(operation.line && operation.line.id))) state.lines.push(operation.line);
255
+ continue;
256
+ }
257
+ const index = state.lines.findIndex((line) => String(line.id) === String(operation.id));
258
+ if (index < 0) {
259
+ location.reload();
260
+ return;
261
+ }
262
+ const line = state.lines[index];
263
+ if (operation.type === 'line.text.append') state.lines[index] = { ...line, text: String(line.text || '') + String(operation.text || '') };
264
+ else if (operation.type === 'line.patch') state.lines[index] = { ...line, ...(operation.patch || {}) };
265
+ }
266
+ if (payload.status) state.status = payload.status;
267
+ scheduleRender();
268
+ });
250
269
 
251
270
  function scheduleRender() {
252
271
  if (renderPending) return;
@@ -1 +1 @@
1
- {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAq1B1B,CAAC"}
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAw2B1B,CAAC"}
@@ -49,6 +49,8 @@ interface UiLine {
49
49
  id: number;
50
50
  kind: "system" | "user" | "assistant" | "thinking" | "tool" | "error" | "meta";
51
51
  text: string;
52
+ toolName?: string;
53
+ parentToolName?: string;
52
54
  title?: string;
53
55
  bodyTitle?: string;
54
56
  titleStatus?: "success" | "failure";
@@ -62,6 +64,12 @@ interface UiLine {
62
64
  interface UiStatus {
63
65
  phase: string;
64
66
  detail?: string;
67
+ currentTool?: {
68
+ id: string;
69
+ name: string;
70
+ input: unknown;
71
+ startedAt: number;
72
+ };
65
73
  metrics?: ContextMetrics;
66
74
  usage?: ModelUsage;
67
75
  streamedOutputTokens: number;
@@ -126,9 +134,22 @@ interface WebSetAppPromptPayload {
126
134
  content?: string;
127
135
  id?: string;
128
136
  title?: string;
137
+ usage?: string;
129
138
  source?: string;
130
139
  clear?: boolean;
131
140
  }
141
+ type WebLineOperation = {
142
+ type: "line.append";
143
+ line: UiLine;
144
+ } | {
145
+ type: "line.text.append";
146
+ id: number;
147
+ text: string;
148
+ } | {
149
+ type: "line.patch";
150
+ id: number;
151
+ patch: Partial<UiLine>;
152
+ };
132
153
  export declare function runWebServer(argv?: string[], runtimeOptions?: RunWebServerOptions): Promise<void>;
133
154
  export declare function createWebRuntime(options?: CreateWebRuntimeOptions): Promise<WebRuntime>;
134
155
  export declare class WebRuntimeRouter {
@@ -144,10 +165,15 @@ export declare function createWebRuntimeRouter(options?: WebRuntimeRouterOptions
144
165
  export declare class WebRepl {
145
166
  private runtime;
146
167
  private readonly subscribers;
168
+ private eventSequence;
169
+ private pendingDeltaOperations;
170
+ private pendingDeltaStatus;
171
+ private pendingDeltaTimer;
147
172
  private lineId;
148
173
  private assistantLineId;
149
174
  private thinkingLineId;
150
175
  private finalizedThinkingLineId;
176
+ private readonly liveToolLineIds;
151
177
  private activeAbortController;
152
178
  private interruptArmed;
153
179
  private lines;
@@ -254,7 +280,7 @@ export declare class WebRepl {
254
280
  cancelled: boolean;
255
281
  };
256
282
  private append;
257
- private updateLine;
283
+ private appendLineText;
258
284
  private replaceLineText;
259
285
  private replaceLine;
260
286
  private setBusy;
@@ -267,11 +293,19 @@ export declare class WebRepl {
267
293
  private reduce;
268
294
  private finalizeLiveLine;
269
295
  private finalizeThinkingLine;
296
+ private finalizeLiveToolLines;
270
297
  private handleEvent;
271
298
  private matchesPendingUserImageEcho;
272
299
  private handleCommandOrPrompt;
273
300
  private runCompaction;
274
301
  private send;
302
+ private broadcastDelta;
303
+ private queueDeltaOperation;
304
+ private queueDeltaStatus;
305
+ private scheduleDeltaFlush;
306
+ private flushDeltaOperations;
307
+ private discardPendingDeltaOperations;
275
308
  private broadcastSync;
276
309
  }
310
+ export declare function compactLineOperations(operations: WebLineOperation[]): WebLineOperation[];
277
311
  export {};
package/dist/web/index.js CHANGED
@@ -249,10 +249,15 @@ function webRuntimeScopeKey(scope) {
249
249
  export class WebRepl {
250
250
  runtime;
251
251
  subscribers = new Set();
252
+ eventSequence = 0;
253
+ pendingDeltaOperations = [];
254
+ pendingDeltaStatus = false;
255
+ pendingDeltaTimer;
252
256
  lineId = 0;
253
257
  assistantLineId;
254
258
  thinkingLineId;
255
259
  finalizedThinkingLineId;
260
+ liveToolLineIds = new Map();
256
261
  activeAbortController;
257
262
  interruptArmed = false;
258
263
  lines;
@@ -278,16 +283,17 @@ export class WebRepl {
278
283
  runtime.engine.onSessionTitleChange(() => this.broadcastSync());
279
284
  }
280
285
  subscribe(res) {
281
- this.subscribers.add(res);
286
+ const subscriber = { response: res, blocked: false, needsSync: false };
287
+ this.subscribers.add(subscriber);
282
288
  res.writeHead(200, {
283
289
  "Content-Type": "text/event-stream; charset=utf-8",
284
290
  "Cache-Control": "no-cache, no-transform",
285
291
  Connection: "keep-alive",
286
292
  "X-Accel-Buffering": "no",
287
293
  });
288
- this.send(res, "sync", this.snapshot(true));
294
+ this.send(subscriber, "sync", this.snapshot(true));
289
295
  reqKeepAlive(res);
290
- res.on("close", () => this.subscribers.delete(res));
296
+ res.on("close", () => this.subscribers.delete(subscriber));
291
297
  }
292
298
  snapshot(includeCatalog = false) {
293
299
  return {
@@ -394,6 +400,7 @@ export class WebRepl {
394
400
  this.assistantLineId = undefined;
395
401
  this.thinkingLineId = undefined;
396
402
  this.finalizedThinkingLineId = undefined;
403
+ this.liveToolLineIds.clear();
397
404
  if (line)
398
405
  this.append(line);
399
406
  this.broadcastSync();
@@ -426,8 +433,18 @@ export class WebRepl {
426
433
  content: payload.content ?? "",
427
434
  id: payload.id,
428
435
  title: payload.title,
436
+ usage: payload.usage,
429
437
  source: payload.source,
430
438
  });
439
+ const usage = payload.usage?.trim();
440
+ if (!shouldClear && usage) {
441
+ this.append({
442
+ kind: "meta",
443
+ title: "提示词用法",
444
+ text: usage,
445
+ format: "markdown",
446
+ });
447
+ }
431
448
  this.broadcastSync();
432
449
  return { ok: true, appPrompt };
433
450
  }
@@ -493,15 +510,19 @@ export class WebRepl {
493
510
  this.broadcastSync();
494
511
  return { ok: true, cancelled: had };
495
512
  }
496
- append(line) {
513
+ append(line, streaming = false) {
497
514
  const id = ++this.lineId;
498
- this.lines.push({ id, ...line });
499
- this.broadcastSync();
515
+ const nextLine = { id, ...line };
516
+ this.lines.push(nextLine);
517
+ if (streaming)
518
+ this.broadcastDelta({ type: "line.append", line: nextLine });
519
+ else
520
+ this.broadcastSync();
500
521
  return id;
501
522
  }
502
- updateLine(id, updater) {
503
- this.lines = this.lines.map((line) => line.id === id ? { ...line, text: updater(line.text) } : line);
504
- this.broadcastSync();
523
+ appendLineText(id, text) {
524
+ this.lines = this.lines.map((line) => line.id === id ? { ...line, text: line.text + text } : line);
525
+ this.broadcastDelta({ type: "line.text.append", id, text });
505
526
  }
506
527
  replaceLineText(id, text) {
507
528
  this.lines = this.lines.map((line) => line.id === id ? { ...line, text } : line);
@@ -538,8 +559,9 @@ export class WebRepl {
538
559
  this.queuedInput = undefined;
539
560
  this.queuedAttachments = undefined;
540
561
  this.finalizeForegroundView();
562
+ this.finalizeLiveToolLines();
541
563
  this.busy = false;
542
- this.status = { ...this.status, phase: "ready", detail: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined };
564
+ this.status = { ...this.status, phase: "ready", detail: undefined, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined };
543
565
  this.broadcastSync();
544
566
  return runWasActive;
545
567
  }
@@ -608,6 +630,7 @@ export class WebRepl {
608
630
  if (id === undefined)
609
631
  return;
610
632
  this.lines = this.lines.map((line) => line.id === id ? { ...line, live: false } : line);
633
+ this.queueDeltaOperation({ type: "line.patch", id, patch: { live: false } });
611
634
  }
612
635
  finalizeThinkingLine() {
613
636
  const id = this.thinkingLineId;
@@ -617,6 +640,11 @@ export class WebRepl {
617
640
  this.finalizedThinkingLineId = id;
618
641
  this.thinkingLineId = undefined;
619
642
  }
643
+ finalizeLiveToolLines() {
644
+ for (const id of this.liveToolLineIds.values())
645
+ this.finalizeLiveLine(id);
646
+ this.liveToolLineIds.clear();
647
+ }
620
648
  handleEvent(event) {
621
649
  this.reduce(event);
622
650
  if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
@@ -624,22 +652,26 @@ export class WebRepl {
624
652
  this.broadcastSync();
625
653
  return;
626
654
  }
627
- if (event.type === "state" || event.type === "context.metrics" || event.type === "usage" || event.type === "tool_call.delta" || event.type === "retrying") {
655
+ if (event.type === "tool_call.delta") {
656
+ this.queueDeltaStatus();
657
+ return;
658
+ }
659
+ if (event.type === "state" || event.type === "context.metrics" || event.type === "usage" || event.type === "retrying") {
628
660
  this.broadcastSync();
629
661
  return;
630
662
  }
631
663
  if (event.type === "assistant.delta") {
632
664
  this.finalizeThinkingLine();
633
- const id = this.assistantLineId ?? this.append({ kind: "assistant", text: "", live: true });
665
+ const id = this.assistantLineId ?? this.append({ kind: "assistant", text: "", live: true }, true);
634
666
  this.assistantLineId = id;
635
- this.updateLine(id, (text) => text + event.text);
667
+ this.appendLineText(id, event.text);
636
668
  return;
637
669
  }
638
670
  if (event.type === "thinking.delta") {
639
- const id = this.thinkingLineId ?? this.finalizedThinkingLineId ?? this.append(thinkingLine("", true));
671
+ const id = this.thinkingLineId ?? this.finalizedThinkingLineId ?? this.append(thinkingLine("", true), true);
640
672
  this.thinkingLineId = id;
641
673
  this.finalizedThinkingLineId = undefined;
642
- this.updateLine(id, (text) => text + event.text);
674
+ this.appendLineText(id, event.text);
643
675
  return;
644
676
  }
645
677
  if (event.type === "message") {
@@ -670,7 +702,14 @@ export class WebRepl {
670
702
  return;
671
703
  }
672
704
  if (event.message.role === "tool_result") {
673
- renderToolResultMessage(event.message, (line) => this.append(line));
705
+ renderToolResultMessage(event.message, (line, toolUseId) => {
706
+ const liveLineId = this.liveToolLineIds.get(toolUseId);
707
+ if (liveLineId === undefined)
708
+ return this.append(line);
709
+ this.replaceLine(liveLineId, line);
710
+ this.liveToolLineIds.delete(toolUseId);
711
+ return liveLineId;
712
+ });
674
713
  renderMessageImages(event.message, (line) => this.append(line));
675
714
  return;
676
715
  }
@@ -691,13 +730,22 @@ export class WebRepl {
691
730
  if (event.type === "tool.started") {
692
731
  this.finalizeLiveLine(this.assistantLineId);
693
732
  this.finalizeThinkingLine();
733
+ const id = this.append({ ...formatToolUse(event.toolUse), live: true });
734
+ this.liveToolLineIds.set(event.toolUse.id, id);
694
735
  return;
695
736
  }
696
- if (event.type === "tool.finished")
737
+ if (event.type === "tool.finished") {
738
+ const id = this.liveToolLineIds.get(event.toolUse.id);
739
+ if (id !== undefined) {
740
+ this.finalizeLiveLine(id);
741
+ this.liveToolLineIds.delete(event.toolUse.id);
742
+ }
697
743
  return;
744
+ }
698
745
  if (event.type === "terminal") {
699
746
  this.finalizeLiveLine(this.assistantLineId);
700
747
  this.finalizeThinkingLine();
748
+ this.finalizeLiveToolLines();
701
749
  this.assistantLineId = undefined;
702
750
  this.broadcastSync();
703
751
  return;
@@ -905,15 +953,86 @@ export class WebRepl {
905
953
  this.setStatus({ ...this.status, phase: "ready", detail: undefined, activityTick: this.status.activityTick + 1 });
906
954
  }
907
955
  }
908
- send(res, event, data) {
909
- res.write(`event: ${event}\n`);
910
- res.write(`data: ${JSON.stringify(data)}\n\n`);
956
+ send(subscriber, event, data) {
957
+ if (subscriber.blocked) {
958
+ subscriber.needsSync = true;
959
+ return;
960
+ }
961
+ const eventId = ++this.eventSequence;
962
+ const frame = `id: ${eventId}\nevent: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
963
+ if (subscriber.response.write(frame))
964
+ return;
965
+ subscriber.blocked = true;
966
+ subscriber.response.once("drain", () => {
967
+ subscriber.blocked = false;
968
+ if (!subscriber.needsSync || subscriber.response.destroyed)
969
+ return;
970
+ subscriber.needsSync = false;
971
+ this.send(subscriber, "sync", this.snapshot(false));
972
+ });
973
+ }
974
+ broadcastDelta(operation) {
975
+ this.queueDeltaOperation(operation);
976
+ }
977
+ queueDeltaOperation(operation) {
978
+ this.pendingDeltaOperations.push(operation);
979
+ this.scheduleDeltaFlush();
980
+ }
981
+ queueDeltaStatus() {
982
+ this.pendingDeltaStatus = true;
983
+ this.scheduleDeltaFlush();
984
+ }
985
+ scheduleDeltaFlush() {
986
+ if (this.pendingDeltaTimer)
987
+ return;
988
+ this.pendingDeltaTimer = setTimeout(() => this.flushDeltaOperations(), 25);
989
+ this.pendingDeltaTimer.unref?.();
990
+ }
991
+ flushDeltaOperations() {
992
+ this.pendingDeltaTimer = undefined;
993
+ if (this.pendingDeltaOperations.length === 0 && !this.pendingDeltaStatus)
994
+ return;
995
+ const operations = compactLineOperations(this.pendingDeltaOperations);
996
+ this.pendingDeltaOperations = [];
997
+ this.pendingDeltaStatus = false;
998
+ const payload = {
999
+ protocolVersion: 2,
1000
+ sessionId: this.runtime.engine.snapshot().session?.sessionId,
1001
+ operations,
1002
+ status: this.status,
1003
+ };
1004
+ for (const subscriber of this.subscribers)
1005
+ this.send(subscriber, "delta", payload);
1006
+ }
1007
+ discardPendingDeltaOperations() {
1008
+ if (this.pendingDeltaTimer)
1009
+ clearTimeout(this.pendingDeltaTimer);
1010
+ this.pendingDeltaTimer = undefined;
1011
+ this.pendingDeltaOperations = [];
1012
+ this.pendingDeltaStatus = false;
911
1013
  }
912
1014
  broadcastSync() {
1015
+ this.discardPendingDeltaOperations();
913
1016
  const payload = this.snapshot(false);
914
- for (const res of this.subscribers)
915
- this.send(res, "sync", payload);
1017
+ for (const subscriber of this.subscribers)
1018
+ this.send(subscriber, "sync", payload);
1019
+ }
1020
+ }
1021
+ export function compactLineOperations(operations) {
1022
+ const compacted = [];
1023
+ for (const operation of operations) {
1024
+ const previous = compacted.at(-1);
1025
+ if (operation.type === "line.text.append" && previous?.type === "line.text.append" && previous.id === operation.id) {
1026
+ previous.text += operation.text;
1027
+ continue;
1028
+ }
1029
+ if (operation.type === "line.patch" && previous?.type === "line.patch" && previous.id === operation.id) {
1030
+ previous.patch = { ...previous.patch, ...operation.patch };
1031
+ continue;
1032
+ }
1033
+ compacted.push(operation.type === "line.patch" ? { ...operation, patch: { ...operation.patch } } : { ...operation });
916
1034
  }
1035
+ return compacted;
917
1036
  }
918
1037
  function reqKeepAlive(res) {
919
1038
  const timer = setInterval(() => res.write(": keep-alive\n\n"), 25_000);
@@ -1100,7 +1219,7 @@ function pushTextBlock(blocks, text) {
1100
1219
  }
1101
1220
  function reduceStatus(status, event) {
1102
1221
  if (event.type === "state")
1103
- return { ...status, phase: event.phase, detail: event.detail, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
1222
+ return { ...status, phase: event.phase, detail: event.detail, currentTool: event.phase === "running_tools" ? status.currentTool : undefined, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
1104
1223
  if (event.type === "context.metrics")
1105
1224
  return { ...status, metrics: event.metrics, inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt, activityTick: status.activityTick + 1 };
1106
1225
  if (event.type === "usage")
@@ -1113,9 +1232,13 @@ function reduceStatus(status, event) {
1113
1232
  return { ...status, phase: "calling_model", streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.argumentsDelta), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
1114
1233
  if (event.type === "retrying")
1115
1234
  return { ...status, phase: "calling_model", detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`, retryCooldownUntil: Date.now() + event.delayMs, activityTick: status.activityTick + 1 };
1235
+ if (event.type === "tool.started")
1236
+ return { ...status, phase: "running_tools", currentTool: { id: event.toolUse.id, name: event.toolUse.name, input: event.toolUse.input, startedAt: Date.now() }, activityTick: status.activityTick + 1 };
1237
+ if (event.type === "tool.finished")
1238
+ return { ...status, currentTool: status.currentTool?.id === event.toolUse.id ? undefined : status.currentTool, activityTick: status.activityTick + 1 };
1116
1239
  if (event.type === "terminal")
1117
- return { ...status, phase: "stopped", detail: event.reason, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
1118
- if (event.type === "message" || event.type === "tool.started" || event.type === "tool.finished" || event.type === "error")
1240
+ return { ...status, phase: "stopped", detail: event.reason, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
1241
+ if (event.type === "message" || event.type === "error")
1119
1242
  return { ...status, activityTick: status.activityTick + 1 };
1120
1243
  return status;
1121
1244
  }
@@ -1237,7 +1360,7 @@ const LOGIN_FIELD_DEFINITIONS = {
1237
1360
  openai: [
1238
1361
  { key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
1239
1362
  { key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
1240
- { key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.5" },
1363
+ { key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.6" },
1241
1364
  { key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
1242
1365
  { key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
1243
1366
  ...SHARED_LOGIN_FIELDS,
@@ -1292,7 +1415,7 @@ function defaultBaseUrlForLoginProvider(provider) {
1292
1415
  function defaultModelForLoginProvider(provider) {
1293
1416
  if (provider === "anthropic")
1294
1417
  return "claude-sonnet-4-6";
1295
- return "gpt-5.5";
1418
+ return "gpt-5.6";
1296
1419
  }
1297
1420
  function validateLoginFormPayload(payload) {
1298
1421
  for (const field of LOGIN_FIELD_DEFINITIONS[payload.provider]) {
@@ -1444,6 +1567,7 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
1444
1567
  return false;
1445
1568
  if (message.role === "assistant" && activeAssistantId !== undefined && message.blocks.some((block) => block.type === "text"))
1446
1569
  return true;
1570
+ const parentToolName = typeof message.metadata?.tool === "string" ? message.metadata.tool : undefined;
1447
1571
  let rendered = false;
1448
1572
  for (const block of message.blocks) {
1449
1573
  if (block.type === "text") {
@@ -1460,7 +1584,7 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
1460
1584
  const line = imageLineForBlock(message.role, block);
1461
1585
  if (!line)
1462
1586
  continue;
1463
- append(line);
1587
+ append({ ...line, parentToolName });
1464
1588
  rendered = true;
1465
1589
  }
1466
1590
  else if (block.type === "thinking") {
@@ -1485,20 +1609,21 @@ function renderToolResultMessage(message, append) {
1485
1609
  for (const block of message.blocks) {
1486
1610
  if (block.type !== "tool_result")
1487
1611
  continue;
1488
- append(formatToolResultLine(block.name, block.output, block.ok));
1612
+ append(formatToolResultLine(block.name, block.output, block.ok), block.toolUseId);
1489
1613
  rendered = true;
1490
1614
  }
1491
1615
  return rendered;
1492
1616
  }
1493
1617
  function renderMessageImages(message, append) {
1494
1618
  let rendered = false;
1619
+ const parentToolName = typeof message.metadata?.tool === "string" ? message.metadata.tool : undefined;
1495
1620
  for (const block of message.blocks) {
1496
1621
  if (block.type !== "image")
1497
1622
  continue;
1498
1623
  const line = imageLineForBlock(message.role, block);
1499
1624
  if (!line)
1500
1625
  continue;
1501
- append(line);
1626
+ append({ ...line, parentToolName });
1502
1627
  rendered = true;
1503
1628
  }
1504
1629
  return rendered;
@@ -1586,13 +1711,15 @@ function thinkingLine(text, live = false) {
1586
1711
  }
1587
1712
  function formatToolUse(toolUse) {
1588
1713
  if (toolUse.name === "plan" && isPlanToolPayload(toolUse.input))
1589
- return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: planToolBodyTitle(toolUse.input), text: formatPlanToolPayload(toolUse.input), collapsible: true };
1714
+ return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: planToolBodyTitle(toolUse.input), text: formatPlanToolPayload(toolUse.input), collapsible: true };
1715
+ if (toolUse.name === "image2")
1716
+ return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: "图片模型处理中", text: JSON.stringify(toolUse.input ?? {}, null, 2), format: "plain", previewStyle: "summary", collapsible: true };
1590
1717
  const summary = summarizeToolUse(toolUse.name, toolUse.input);
1591
- return { kind: "tool", title: toolTitle(toolUse.name, "running"), bodyTitle: summary.bodyTitle, text: summary.text, previewStyle: "summary", collapsible: true };
1718
+ return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: summary.bodyTitle, text: summary.text, previewStyle: "summary", collapsible: true };
1592
1719
  }
1593
1720
  function formatToolResultLine(toolName, output, ok) {
1594
1721
  const formatted = formatToolResult(toolName, output, ok);
1595
- return { kind: ok ? "tool" : "error", title: toolTitle(toolName, "finished"), bodyTitle: formatted.bodyTitle, titleStatus: ok ? "success" : "failure", text: formatted.text, format: formatted.format, live: false, previewStyle: formatted.full ? undefined : "summary", summaryMaxLines: formatted.summaryMaxLines, collapsible: true };
1722
+ return { kind: ok ? "tool" : "error", toolName, title: toolTitle(toolName, "finished"), bodyTitle: formatted.bodyTitle, titleStatus: ok ? "success" : "failure", text: formatted.text, format: formatted.format, live: false, previewStyle: formatted.full ? undefined : "summary", summaryMaxLines: formatted.summaryMaxLines, collapsible: true };
1596
1723
  }
1597
1724
  function toolTitle(toolName, _phase) {
1598
1725
  const labels = {