neoctl 0.2.17 → 0.2.22

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.
@@ -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"}
@@ -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,6 +134,7 @@ 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
  }
@@ -148,6 +157,7 @@ export declare class WebRepl {
148
157
  private assistantLineId;
149
158
  private thinkingLineId;
150
159
  private finalizedThinkingLineId;
160
+ private readonly liveToolLineIds;
151
161
  private activeAbortController;
152
162
  private interruptArmed;
153
163
  private lines;
@@ -267,6 +277,7 @@ export declare class WebRepl {
267
277
  private reduce;
268
278
  private finalizeLiveLine;
269
279
  private finalizeThinkingLine;
280
+ private finalizeLiveToolLines;
270
281
  private handleEvent;
271
282
  private matchesPendingUserImageEcho;
272
283
  private handleCommandOrPrompt;
package/dist/web/index.js CHANGED
@@ -253,6 +253,7 @@ export class WebRepl {
253
253
  assistantLineId;
254
254
  thinkingLineId;
255
255
  finalizedThinkingLineId;
256
+ liveToolLineIds = new Map();
256
257
  activeAbortController;
257
258
  interruptArmed = false;
258
259
  lines;
@@ -394,6 +395,7 @@ export class WebRepl {
394
395
  this.assistantLineId = undefined;
395
396
  this.thinkingLineId = undefined;
396
397
  this.finalizedThinkingLineId = undefined;
398
+ this.liveToolLineIds.clear();
397
399
  if (line)
398
400
  this.append(line);
399
401
  this.broadcastSync();
@@ -426,8 +428,18 @@ export class WebRepl {
426
428
  content: payload.content ?? "",
427
429
  id: payload.id,
428
430
  title: payload.title,
431
+ usage: payload.usage,
429
432
  source: payload.source,
430
433
  });
434
+ const usage = payload.usage?.trim();
435
+ if (!shouldClear && usage) {
436
+ this.append({
437
+ kind: "meta",
438
+ title: "提示词用法",
439
+ text: usage,
440
+ format: "markdown",
441
+ });
442
+ }
431
443
  this.broadcastSync();
432
444
  return { ok: true, appPrompt };
433
445
  }
@@ -538,8 +550,9 @@ export class WebRepl {
538
550
  this.queuedInput = undefined;
539
551
  this.queuedAttachments = undefined;
540
552
  this.finalizeForegroundView();
553
+ this.finalizeLiveToolLines();
541
554
  this.busy = false;
542
- this.status = { ...this.status, phase: "ready", detail: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined };
555
+ this.status = { ...this.status, phase: "ready", detail: undefined, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined };
543
556
  this.broadcastSync();
544
557
  return runWasActive;
545
558
  }
@@ -617,6 +630,11 @@ export class WebRepl {
617
630
  this.finalizedThinkingLineId = id;
618
631
  this.thinkingLineId = undefined;
619
632
  }
633
+ finalizeLiveToolLines() {
634
+ for (const id of this.liveToolLineIds.values())
635
+ this.finalizeLiveLine(id);
636
+ this.liveToolLineIds.clear();
637
+ }
620
638
  handleEvent(event) {
621
639
  this.reduce(event);
622
640
  if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
@@ -670,7 +688,14 @@ export class WebRepl {
670
688
  return;
671
689
  }
672
690
  if (event.message.role === "tool_result") {
673
- renderToolResultMessage(event.message, (line) => this.append(line));
691
+ renderToolResultMessage(event.message, (line, toolUseId) => {
692
+ const liveLineId = this.liveToolLineIds.get(toolUseId);
693
+ if (liveLineId === undefined)
694
+ return this.append(line);
695
+ this.replaceLine(liveLineId, line);
696
+ this.liveToolLineIds.delete(toolUseId);
697
+ return liveLineId;
698
+ });
674
699
  renderMessageImages(event.message, (line) => this.append(line));
675
700
  return;
676
701
  }
@@ -691,13 +716,22 @@ export class WebRepl {
691
716
  if (event.type === "tool.started") {
692
717
  this.finalizeLiveLine(this.assistantLineId);
693
718
  this.finalizeThinkingLine();
719
+ const id = this.append({ ...formatToolUse(event.toolUse), live: true });
720
+ this.liveToolLineIds.set(event.toolUse.id, id);
694
721
  return;
695
722
  }
696
- if (event.type === "tool.finished")
723
+ if (event.type === "tool.finished") {
724
+ const id = this.liveToolLineIds.get(event.toolUse.id);
725
+ if (id !== undefined) {
726
+ this.finalizeLiveLine(id);
727
+ this.liveToolLineIds.delete(event.toolUse.id);
728
+ }
697
729
  return;
730
+ }
698
731
  if (event.type === "terminal") {
699
732
  this.finalizeLiveLine(this.assistantLineId);
700
733
  this.finalizeThinkingLine();
734
+ this.finalizeLiveToolLines();
701
735
  this.assistantLineId = undefined;
702
736
  this.broadcastSync();
703
737
  return;
@@ -1100,7 +1134,7 @@ function pushTextBlock(blocks, text) {
1100
1134
  }
1101
1135
  function reduceStatus(status, event) {
1102
1136
  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 };
1137
+ 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
1138
  if (event.type === "context.metrics")
1105
1139
  return { ...status, metrics: event.metrics, inputTokenUpdatedAt: event.metrics.estimatedInputTokens !== status.metrics?.estimatedInputTokens ? Date.now() : status.inputTokenUpdatedAt, activityTick: status.activityTick + 1 };
1106
1140
  if (event.type === "usage")
@@ -1113,9 +1147,13 @@ function reduceStatus(status, event) {
1113
1147
  return { ...status, phase: "calling_model", streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.argumentsDelta), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
1114
1148
  if (event.type === "retrying")
1115
1149
  return { ...status, phase: "calling_model", detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`, retryCooldownUntil: Date.now() + event.delayMs, activityTick: status.activityTick + 1 };
1150
+ if (event.type === "tool.started")
1151
+ 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 };
1152
+ if (event.type === "tool.finished")
1153
+ return { ...status, currentTool: status.currentTool?.id === event.toolUse.id ? undefined : status.currentTool, activityTick: status.activityTick + 1 };
1116
1154
  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")
1155
+ return { ...status, phase: "stopped", detail: event.reason, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
1156
+ if (event.type === "message" || event.type === "error")
1119
1157
  return { ...status, activityTick: status.activityTick + 1 };
1120
1158
  return status;
1121
1159
  }
@@ -1237,7 +1275,7 @@ const LOGIN_FIELD_DEFINITIONS = {
1237
1275
  openai: [
1238
1276
  { key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
1239
1277
  { 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" },
1278
+ { key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.6" },
1241
1279
  { key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
1242
1280
  { key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
1243
1281
  ...SHARED_LOGIN_FIELDS,
@@ -1292,7 +1330,7 @@ function defaultBaseUrlForLoginProvider(provider) {
1292
1330
  function defaultModelForLoginProvider(provider) {
1293
1331
  if (provider === "anthropic")
1294
1332
  return "claude-sonnet-4-6";
1295
- return "gpt-5.5";
1333
+ return "gpt-5.6";
1296
1334
  }
1297
1335
  function validateLoginFormPayload(payload) {
1298
1336
  for (const field of LOGIN_FIELD_DEFINITIONS[payload.provider]) {
@@ -1444,6 +1482,7 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
1444
1482
  return false;
1445
1483
  if (message.role === "assistant" && activeAssistantId !== undefined && message.blocks.some((block) => block.type === "text"))
1446
1484
  return true;
1485
+ const parentToolName = typeof message.metadata?.tool === "string" ? message.metadata.tool : undefined;
1447
1486
  let rendered = false;
1448
1487
  for (const block of message.blocks) {
1449
1488
  if (block.type === "text") {
@@ -1460,7 +1499,7 @@ function renderMessage(message, append, activeAssistantId, options = {}) {
1460
1499
  const line = imageLineForBlock(message.role, block);
1461
1500
  if (!line)
1462
1501
  continue;
1463
- append(line);
1502
+ append({ ...line, parentToolName });
1464
1503
  rendered = true;
1465
1504
  }
1466
1505
  else if (block.type === "thinking") {
@@ -1485,20 +1524,21 @@ function renderToolResultMessage(message, append) {
1485
1524
  for (const block of message.blocks) {
1486
1525
  if (block.type !== "tool_result")
1487
1526
  continue;
1488
- append(formatToolResultLine(block.name, block.output, block.ok));
1527
+ append(formatToolResultLine(block.name, block.output, block.ok), block.toolUseId);
1489
1528
  rendered = true;
1490
1529
  }
1491
1530
  return rendered;
1492
1531
  }
1493
1532
  function renderMessageImages(message, append) {
1494
1533
  let rendered = false;
1534
+ const parentToolName = typeof message.metadata?.tool === "string" ? message.metadata.tool : undefined;
1495
1535
  for (const block of message.blocks) {
1496
1536
  if (block.type !== "image")
1497
1537
  continue;
1498
1538
  const line = imageLineForBlock(message.role, block);
1499
1539
  if (!line)
1500
1540
  continue;
1501
- append(line);
1541
+ append({ ...line, parentToolName });
1502
1542
  rendered = true;
1503
1543
  }
1504
1544
  return rendered;
@@ -1586,13 +1626,15 @@ function thinkingLine(text, live = false) {
1586
1626
  }
1587
1627
  function formatToolUse(toolUse) {
1588
1628
  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 };
1629
+ return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: planToolBodyTitle(toolUse.input), text: formatPlanToolPayload(toolUse.input), collapsible: true };
1630
+ if (toolUse.name === "image2")
1631
+ 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
1632
  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 };
1633
+ return { kind: "tool", toolName: toolUse.name, title: toolTitle(toolUse.name, "running"), bodyTitle: summary.bodyTitle, text: summary.text, previewStyle: "summary", collapsible: true };
1592
1634
  }
1593
1635
  function formatToolResultLine(toolName, output, ok) {
1594
1636
  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 };
1637
+ 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
1638
  }
1597
1639
  function toolTitle(toolName, _phase) {
1598
1640
  const labels = {