@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.3

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 (142) hide show
  1. package/bin/cli.js +97 -69
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +0 -5
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/lib/common.sh +287 -0
  38. package/scripts/regression/results/.last_pid +1 -0
  39. package/scripts/regression/results/.last_workdir +1 -0
  40. package/scripts/regression/results/02-extension-loading.log +21 -0
  41. package/scripts/regression/results/03-prompt-stream.log +16 -0
  42. package/scripts/regression/results/03-rpc-protocol.log +27 -0
  43. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  44. package/scripts/regression/results/05-tool-execution.log +8 -0
  45. package/scripts/regression/results/06-pi-new-features.log +27 -0
  46. package/scripts/regression/run-all.sh +187 -0
  47. package/config/agent/bin/fd.exe +0 -0
  48. package/config/agent/bin/rg.exe +0 -0
  49. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  50. package/config/agent/extensions/aicode-snake.ts +0 -354
  51. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  52. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  57. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  58. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  60. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  106. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  107. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  108. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
@@ -10,7 +10,12 @@ import {
10
10
  type ExtensionContext,
11
11
  type ResourceLoader,
12
12
  } from "@earendil-works/pi-coding-agent";
13
- import { type AssistantMessage, type Message, type ThinkingLevel as AiThinkingLevel, type UserMessage } from "@earendil-works/pi-ai";
13
+ import {
14
+ type AssistantMessage,
15
+ type Message,
16
+ type ThinkingLevel as AiThinkingLevel,
17
+ type UserMessage,
18
+ } from "@earendil-works/pi-ai";
14
19
  import {
15
20
  Box,
16
21
  Container,
@@ -28,7 +33,6 @@ import {
28
33
  } from "@earendil-works/pi-tui";
29
34
 
30
35
  // ============== 常量 ==============
31
- const STATUS_KEY = "aicode-btw";
32
36
  const BTW_MESSAGE_TYPE = "aicode-btw-message";
33
37
  const BTW_ENTRY_TYPE = "aicode-btw-entry";
34
38
  const BTW_RESET_TYPE = "aicode-btw-reset";
@@ -44,11 +48,19 @@ function matchesBtwFocusShortcut(data: string): boolean {
44
48
  * 类型守卫:检查 entry 是否是有效的 Message 对象
45
49
  * ✅ 用于安全转换,避免使用危险的 as unknown as
46
50
  */
47
- function isMessageLike(entry: unknown): entry is Partial<Message> & { role?: string; customType?: string; content?: unknown } {
51
+ function isMessageLike(entry: unknown): entry is Partial<Message> & {
52
+ role?: string;
53
+ customType?: string;
54
+ content?: unknown;
55
+ } {
48
56
  if (!entry || typeof entry !== "object") {
49
57
  return false;
50
58
  }
51
- const candidate = entry as { role?: unknown; customType?: string; content?: unknown };
59
+ const candidate = entry as {
60
+ role?: unknown;
61
+ customType?: string;
62
+ content?: unknown;
63
+ };
52
64
  return typeof candidate.role === "string" && Array.isArray(candidate.content);
53
65
  }
54
66
 
@@ -63,8 +75,10 @@ const BTW_SYSTEM_PROMPT = [
63
75
  const BTW_SUMMARIZE_SYSTEM_PROMPT =
64
76
  "Summarize the side conversation concisely. Preserve key decisions, plans, insights, risks, and action items. Output only the summary.";
65
77
 
66
- const BTW_CONTINUE_THREAD_USER_TEXT = "[The following is a separate side conversation. Continue this thread.]";
67
- const BTW_CONTINUE_THREAD_ASSISTANT_TEXT = "Understood, continuing our side conversation.";
78
+ const BTW_CONTINUE_THREAD_USER_TEXT =
79
+ "[The following is a separate side conversation. Continue this thread.]";
80
+ const BTW_CONTINUE_THREAD_ASSISTANT_TEXT =
81
+ "Understood, continuing our side conversation.";
68
82
 
69
83
  type SessionThinkingLevel = "off" | AiThinkingLevel;
70
84
  type BtwThreadMode = "contextual" | "tangent";
@@ -100,7 +114,10 @@ type BtwResetDetails = {
100
114
  };
101
115
 
102
116
  type BtwModelOverrideDetails =
103
- | ({ timestamp: number; action: "set" } & Pick<SessionModel, "provider" | "id" | "api">)
117
+ | ({ timestamp: number; action: "set" } & Pick<
118
+ SessionModel,
119
+ "provider" | "id" | "api"
120
+ >)
104
121
  | { timestamp: number; action: "clear" };
105
122
 
106
123
  type BtwThinkingOverrideDetails =
@@ -124,11 +141,35 @@ type ResolvedBtwSettings = {
124
141
  };
125
142
 
126
143
  type BtwTranscriptEntry =
127
- | { id: number; turnId: number; type: "turn-boundary"; phase: "start" | "end" }
144
+ | {
145
+ id: number;
146
+ turnId: number;
147
+ type: "turn-boundary";
148
+ phase: "start" | "end";
149
+ }
128
150
  | { id: number; turnId: number; type: "user-message"; text: string }
129
- | { id: number; turnId: number; type: "thinking"; text: string; streaming: boolean }
130
- | { id: number; turnId: number; type: "assistant-text"; text: string; streaming: boolean }
131
- | { id: number; turnId: number; type: "tool-call"; toolCallId: string; toolName: string; args: string }
151
+ | {
152
+ id: number;
153
+ turnId: number;
154
+ type: "thinking";
155
+ text: string;
156
+ streaming: boolean;
157
+ }
158
+ | {
159
+ id: number;
160
+ turnId: number;
161
+ type: "assistant-text";
162
+ text: string;
163
+ streaming: boolean;
164
+ }
165
+ | {
166
+ id: number;
167
+ turnId: number;
168
+ type: "tool-call";
169
+ toolCallId: string;
170
+ toolName: string;
171
+ args: string;
172
+ }
132
173
  | {
133
174
  id: number;
134
175
  turnId: number;
@@ -149,7 +190,10 @@ type BtwTranscriptState = {
149
190
  nextTurnId: number;
150
191
  currentTurnId: number | null;
151
192
  lastTurnId: number | null;
152
- toolCalls: Map<string, { turnId: number; callEntryId: number; resultEntryId?: number }>;
193
+ toolCalls: Map<
194
+ string,
195
+ { turnId: number; callEntryId: number; resultEntryId?: number }
196
+ >;
153
197
  };
154
198
 
155
199
  type BtwSessionRuntime = {
@@ -168,17 +212,31 @@ type OverlayRuntime = {
168
212
  closed?: boolean;
169
213
  };
170
214
 
171
- function isVisibleBtwMessage(message: { role: string; customType?: string }): boolean {
215
+ function isVisibleBtwMessage(message: {
216
+ role: string;
217
+ customType?: string;
218
+ }): boolean {
172
219
  return message.role === "custom" && message.customType === BTW_MESSAGE_TYPE;
173
220
  }
174
221
 
175
- function isCustomEntry(entry: unknown, customType: string): entry is { type: "custom"; customType: string; data?: unknown } {
176
- return !!entry && typeof entry === "object" && (entry as { type?: string }).type === "custom" && (entry as { customType?: string }).customType === customType;
222
+ function isCustomEntry(
223
+ entry: unknown,
224
+ customType: string,
225
+ ): entry is { type: "custom"; customType: string; data?: unknown } {
226
+ return (
227
+ !!entry &&
228
+ typeof entry === "object" &&
229
+ (entry as { type?: string }).type === "custom" &&
230
+ (entry as { customType?: string }).customType === customType
231
+ );
177
232
  }
178
233
 
179
234
  function stripDynamicSystemPromptFooter(systemPrompt: string): string {
180
235
  return systemPrompt
181
- .replace(/\nCurrent date and time:[^\n]*(?:\nCurrent working directory:[^\n]*)?$/u, "")
236
+ .replace(
237
+ /\nCurrent date and time:[^\n]*(?:\nCurrent working directory:[^\n]*)?$/u,
238
+ "",
239
+ )
182
240
  .replace(/\nCurrent working directory:[^\n]*$/u, "")
183
241
  .trim();
184
242
  }
@@ -187,7 +245,11 @@ function createBtwResourceLoader(
187
245
  ctx: ExtensionCommandContext,
188
246
  appendSystemPrompt: string[] = [BTW_SYSTEM_PROMPT],
189
247
  ): ResourceLoader {
190
- const extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };
248
+ const extensionsResult = {
249
+ extensions: [],
250
+ errors: [],
251
+ runtime: createExtensionRuntime(),
252
+ };
191
253
  const systemPrompt = stripDynamicSystemPromptFooter(ctx.getSystemPrompt());
192
254
 
193
255
  return {
@@ -197,13 +259,18 @@ function createBtwResourceLoader(
197
259
  getThemes: () => ({ themes: [], diagnostics: [] }),
198
260
  getAgentsFiles: () => ({ agentsFiles: [] }),
199
261
  getSystemPrompt: () => systemPrompt,
262
+ getSystemPromptSource: () => undefined,
200
263
  getAppendSystemPrompt: () => appendSystemPrompt,
264
+ getAppendSystemPromptSources: () => [],
201
265
  extendResources: () => {},
202
266
  reload: async () => {},
203
267
  };
204
268
  }
205
269
 
206
- function extractText(parts: AssistantMessage["content"], type: "text" | "thinking"): string {
270
+ function extractText(
271
+ parts: AssistantMessage["content"],
272
+ type: "text" | "thinking",
273
+ ): string {
207
274
  const chunks: string[] = [];
208
275
 
209
276
  for (const part of parts) {
@@ -231,7 +298,9 @@ function parseBtwArgs(args: string): ParsedBtwArgs {
231
298
  return { question, save };
232
299
  }
233
300
 
234
- function parseBtwModelArgs(args: string):
301
+ function parseBtwModelArgs(
302
+ args: string,
303
+ ):
235
304
  | { action: "show" }
236
305
  | { action: "clear" }
237
306
  | { action: "set"; model: BtwModelRef }
@@ -247,14 +316,19 @@ function parseBtwModelArgs(args: string):
247
316
 
248
317
  const parts = trimmed.split(/\s+/);
249
318
  if (parts.length !== 3) {
250
- return { action: "invalid", message: "Usage: /btw:model <provider> <model> <api> | clear" };
319
+ return {
320
+ action: "invalid",
321
+ message: "Usage: /btw:model <provider> <model> <api> | clear",
322
+ };
251
323
  }
252
324
 
253
325
  const [provider, id, api] = parts;
254
326
  return { action: "set", model: { provider, id, api } as BtwModelRef };
255
327
  }
256
328
 
257
- function parseBtwThinkingArgs(args: string):
329
+ function parseBtwThinkingArgs(
330
+ args: string,
331
+ ):
258
332
  | { action: "show" }
259
333
  | { action: "clear" }
260
334
  | { action: "set"; thinkingLevel: SessionThinkingLevel } {
@@ -270,7 +344,9 @@ function parseBtwThinkingArgs(args: string):
270
344
  return { action: "set", thinkingLevel: trimmed as SessionThinkingLevel };
271
345
  }
272
346
 
273
- function formatModelRef(model: Pick<SessionModel, "provider" | "id" | "api">): string {
347
+ function formatModelRef(
348
+ model: Pick<SessionModel, "provider" | "id" | "api">,
349
+ ): string {
274
350
  return `${model.provider}/${model.id} (${model.api})`;
275
351
  }
276
352
 
@@ -285,13 +361,16 @@ function buildBtwSeedState(
285
361
  if (mode === "contextual") {
286
362
  try {
287
363
  messages.push(
288
- ...(buildSessionContext(ctx.sessionManager.getEntries(), ctx.sessionManager.getLeafId()).messages as Message[]).filter(
289
- (message) => !isVisibleBtwMessage(message),
290
- ),
364
+ ...(
365
+ buildSessionContext(
366
+ ctx.sessionManager.getEntries(),
367
+ ctx.sessionManager.getLeafId(),
368
+ ).messages as Message[]
369
+ ).filter((message) => !isVisibleBtwMessage(message)),
291
370
  );
292
371
  } catch (error) {
293
372
  // ✅ 改进错误处理:记录错误而不忽略
294
- console.warn('[btw] buildSessionContext failed, using fallback:', error);
373
+ console.warn("[btw] buildSessionContext failed, using fallback:", error);
295
374
  messages.push(
296
375
  ...ctx.sessionManager.getEntries().flatMap((entry) => {
297
376
  // ✅ 使用类型守卫进行安全转换,避免 as unknown as
@@ -300,8 +379,15 @@ function buildBtwSeedState(
300
379
  }
301
380
 
302
381
  const message = entry; // ✅ 类型安全
303
-
304
- return isVisibleBtwMessage({ role: message.role, customType: message.customType }) ? [] : [message as Message];
382
+ if (
383
+ !isVisibleBtwMessage({
384
+ role: message.role ?? "",
385
+ customType: message.customType,
386
+ })
387
+ ) {
388
+ return [message as Message];
389
+ }
390
+ return [];
305
391
  }),
306
392
  );
307
393
  }
@@ -347,16 +433,25 @@ function buildBtwSeedState(
347
433
  content: [{ type: "text", text: entry.answer }],
348
434
  provider: entry.provider,
349
435
  model: entry.model,
350
- api: entry.api || sessionModel?.api || ctx.model?.api || "openai-responses",
351
- usage:
352
- entry.usage ?? {
436
+ api:
437
+ entry.api ||
438
+ sessionModel?.api ||
439
+ ctx.model?.api ||
440
+ "openai-responses",
441
+ usage: entry.usage ?? {
442
+ input: 0,
443
+ output: 0,
444
+ cacheRead: 0,
445
+ cacheWrite: 0,
446
+ totalTokens: 0,
447
+ cost: {
353
448
  input: 0,
354
449
  output: 0,
355
450
  cacheRead: 0,
356
451
  cacheWrite: 0,
357
- totalTokens: 0,
358
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
452
+ total: 0,
359
453
  },
454
+ },
360
455
  stopReason: "stop",
361
456
  timestamp: entry.timestamp,
362
457
  },
@@ -394,7 +489,7 @@ function formatToolPreview(value: unknown): string {
394
489
  return preview.length > 120 ? `${preview.slice(0, 117)}...` : preview;
395
490
  } catch (error) {
396
491
  // ✅ 改进错误处理:记录错误而不忽略
397
- console.warn('[btw] JSON.stringify failed in formatToolPreview:', error);
492
+ console.warn("[btw] JSON.stringify failed in formatToolPreview:", error);
398
493
  return "";
399
494
  }
400
495
  }
@@ -419,10 +514,10 @@ function createEmptyTranscriptState(): BtwTranscriptState {
419
514
  function clearTranscriptState(state: BtwTranscriptState): void {
420
515
  // ✅ 清理 toolCalls Map(防止内存泄漏)
421
516
  state.toolCalls.clear();
422
-
517
+
423
518
  // 清理 entries 数组
424
519
  state.entries = [];
425
-
520
+
426
521
  // 重置状态
427
522
  state.nextEntryId = 1;
428
523
  state.nextTurnId = 1;
@@ -447,21 +542,35 @@ function ensureTranscriptTurn(state: BtwTranscriptState): number {
447
542
  const turnId = state.nextTurnId++;
448
543
  state.currentTurnId = turnId;
449
544
  state.lastTurnId = turnId;
450
- appendTranscriptEntry(state, { type: "turn-boundary", turnId, phase: "start" } as Omit<Extract<BtwTranscriptEntry, { type: "turn-boundary" }>, "id">);
545
+ appendTranscriptEntry(state, {
546
+ type: "turn-boundary",
547
+ turnId,
548
+ phase: "start",
549
+ } as Omit<Extract<BtwTranscriptEntry, { type: "turn-boundary" }>, "id">);
451
550
  return turnId;
452
551
  }
453
552
 
454
- function finishTranscriptTurn(state: BtwTranscriptState, turnId?: number | null): void {
553
+ function finishTranscriptTurn(
554
+ state: BtwTranscriptState,
555
+ turnId?: number | null,
556
+ ): void {
455
557
  const resolvedTurnId = turnId ?? state.currentTurnId;
456
558
  if (resolvedTurnId === null || resolvedTurnId === undefined) {
457
559
  return;
458
560
  }
459
561
 
460
562
  const hasEndBoundary = state.entries.some(
461
- (entry) => entry.turnId === resolvedTurnId && entry.type === "turn-boundary" && entry.phase === "end",
563
+ (entry) =>
564
+ entry.turnId === resolvedTurnId &&
565
+ entry.type === "turn-boundary" &&
566
+ entry.phase === "end",
462
567
  );
463
568
  if (!hasEndBoundary) {
464
- appendTranscriptEntry(state, { type: "turn-boundary", turnId: resolvedTurnId, phase: "end" } as Omit<Extract<BtwTranscriptEntry, { type: "turn-boundary" }>, "id">);
569
+ appendTranscriptEntry(state, {
570
+ type: "turn-boundary",
571
+ turnId: resolvedTurnId,
572
+ phase: "end",
573
+ } as Omit<Extract<BtwTranscriptEntry, { type: "turn-boundary" }>, "id">);
465
574
  }
466
575
 
467
576
  for (const entry of state.entries) {
@@ -469,7 +578,11 @@ function finishTranscriptTurn(state: BtwTranscriptState, turnId?: number | null)
469
578
  continue;
470
579
  }
471
580
 
472
- if (entry.type === "thinking" || entry.type === "assistant-text" || entry.type === "tool-result") {
581
+ if (
582
+ entry.type === "thinking" ||
583
+ entry.type === "assistant-text" ||
584
+ entry.type === "tool-result"
585
+ ) {
473
586
  entry.streaming = false;
474
587
  }
475
588
  }
@@ -487,7 +600,10 @@ function finishTranscriptTurn(state: BtwTranscriptState, turnId?: number | null)
487
600
  }
488
601
  }
489
602
 
490
- function removeTranscriptTurn(state: BtwTranscriptState, turnId: number | null): void {
603
+ function removeTranscriptTurn(
604
+ state: BtwTranscriptState,
605
+ turnId: number | null,
606
+ ): void {
491
607
  if (turnId === null) {
492
608
  return;
493
609
  }
@@ -524,7 +640,11 @@ function findLatestTranscriptEntry<TType extends BtwTranscriptEntry["type"]>(
524
640
 
525
641
  function ensureTranscriptTurnForUserMessage(state: BtwTranscriptState): number {
526
642
  if (state.currentTurnId !== null) {
527
- const currentAssistant = findLatestTranscriptEntry(state, state.currentTurnId, "assistant-text");
643
+ const currentAssistant = findLatestTranscriptEntry(
644
+ state,
645
+ state.currentTurnId,
646
+ "assistant-text",
647
+ );
528
648
  if (currentAssistant && !currentAssistant.streaming) {
529
649
  finishTranscriptTurn(state, state.currentTurnId);
530
650
  }
@@ -533,7 +653,9 @@ function ensureTranscriptTurnForUserMessage(state: BtwTranscriptState): number {
533
653
  return ensureTranscriptTurn(state);
534
654
  }
535
655
 
536
- function extractMessageText(message: { content?: string | AssistantMessage["content"] | UserMessage["content"] }): string {
656
+ function extractMessageText(message: {
657
+ content?: string | AssistantMessage["content"] | UserMessage["content"];
658
+ }): string {
537
659
  if (typeof message.content === "string") {
538
660
  return message.content;
539
661
  }
@@ -541,13 +663,20 @@ function extractMessageText(message: { content?: string | AssistantMessage["cont
541
663
  return "";
542
664
  }
543
665
  return message.content
544
- .filter((part): part is { type: "text"; text: string } => part.type === "text" && typeof part.text === "string")
666
+ .filter(
667
+ (part): part is { type: "text"; text: string } =>
668
+ part.type === "text" && typeof part.text === "string",
669
+ )
545
670
  .map((part) => part.text)
546
671
  .join("\n")
547
672
  .trim();
548
673
  }
549
674
 
550
- function upsertUserMessageEntry(state: BtwTranscriptState, turnId: number, text: string): void {
675
+ function upsertUserMessageEntry(
676
+ state: BtwTranscriptState,
677
+ turnId: number,
678
+ text: string,
679
+ ): void {
551
680
  if (!text) {
552
681
  return;
553
682
  }
@@ -558,7 +687,10 @@ function upsertUserMessageEntry(state: BtwTranscriptState, turnId: number, text:
558
687
  return;
559
688
  }
560
689
 
561
- appendTranscriptEntry(state, { type: "user-message", turnId, text } as Omit<Extract<BtwTranscriptEntry, { type: "user-message" }>, "id">);
690
+ appendTranscriptEntry(state, { type: "user-message", turnId, text } as Omit<
691
+ Extract<BtwTranscriptEntry, { type: "user-message" }>,
692
+ "id"
693
+ >);
562
694
  }
563
695
 
564
696
  function upsertTranscriptTextEntry(
@@ -579,10 +711,16 @@ function upsertTranscriptTextEntry(
579
711
  return;
580
712
  }
581
713
 
582
- appendTranscriptEntry(state, { type, turnId, text, streaming } as Omit<Extract<BtwTranscriptEntry, { type: "thinking" | "assistant-text" }>, "id">);
714
+ appendTranscriptEntry(state, { type, turnId, text, streaming } as Omit<
715
+ Extract<BtwTranscriptEntry, { type: "thinking" | "assistant-text" }>,
716
+ "id"
717
+ >);
583
718
  }
584
719
 
585
- function summarizeToolResult(value: unknown, maxLength = 400): { content: string; truncated: boolean } {
720
+ function summarizeToolResult(
721
+ value: unknown,
722
+ maxLength = 400,
723
+ ): { content: string; truncated: boolean } {
586
724
  let content = "";
587
725
 
588
726
  if (value && typeof value === "object") {
@@ -617,7 +755,10 @@ function summarizeToolResult(value: unknown, maxLength = 400): { content: string
617
755
  content = JSON.stringify(value, null, 2);
618
756
  } catch (error) {
619
757
  // ✅ 改进错误处理:记录错误而不忽略
620
- console.warn('[btw] JSON.stringify failed in summarizeToolResult:', error);
758
+ console.warn(
759
+ "[btw] JSON.stringify failed in summarizeToolResult:",
760
+ error,
761
+ );
621
762
  content = String(value);
622
763
  }
623
764
  }
@@ -671,7 +812,10 @@ function upsertToolResultEntry(
671
812
  const toolCall = ensureToolCallEntry(state, turnId, toolCallId, toolName, "");
672
813
  const existing =
673
814
  toolCall.resultEntryId !== undefined
674
- ? state.entries.find((entry) => entry.id === toolCall.resultEntryId && entry.type === "tool-result")
815
+ ? state.entries.find(
816
+ (entry) =>
817
+ entry.id === toolCall.resultEntryId && entry.type === "tool-result",
818
+ )
675
819
  : undefined;
676
820
 
677
821
  if (existing && existing.type === "tool-result") {
@@ -710,11 +854,20 @@ function applyAssistantMessageToTranscript(
710
854
  }
711
855
 
712
856
  if (answer) {
713
- upsertTranscriptTextEntry(state, turnId, "assistant-text", answer, streaming);
857
+ upsertTranscriptTextEntry(
858
+ state,
859
+ turnId,
860
+ "assistant-text",
861
+ answer,
862
+ streaming,
863
+ );
714
864
  }
715
865
  }
716
866
 
717
- function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEvent): void {
867
+ function applyTranscriptEvent(
868
+ state: BtwTranscriptState,
869
+ event: AgentSessionEvent,
870
+ ): void {
718
871
  switch (event.type) {
719
872
  case "turn_start": {
720
873
  ensureTranscriptTurn(state);
@@ -723,7 +876,11 @@ function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEven
723
876
  case "message_start": {
724
877
  if (event.message.role === "user") {
725
878
  const turnId = ensureTranscriptTurnForUserMessage(state);
726
- upsertUserMessageEntry(state, turnId, extractMessageText(event.message));
879
+ upsertUserMessageEntry(
880
+ state,
881
+ turnId,
882
+ extractMessageText(event.message),
883
+ );
727
884
  return;
728
885
  }
729
886
 
@@ -745,7 +902,11 @@ function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEven
745
902
  case "message_end": {
746
903
  if (event.message.role === "user") {
747
904
  const turnId = ensureTranscriptTurnForUserMessage(state);
748
- upsertUserMessageEntry(state, turnId, extractMessageText(event.message));
905
+ upsertUserMessageEntry(
906
+ state,
907
+ turnId,
908
+ extractMessageText(event.message),
909
+ );
749
910
  return;
750
911
  }
751
912
 
@@ -757,11 +918,19 @@ function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEven
757
918
  }
758
919
  case "tool_execution_start": {
759
920
  const turnId = ensureTranscriptTurn(state);
760
- ensureToolCallEntry(state, turnId, event.toolCallId, event.toolName, formatToolPreview(event.args));
921
+ ensureToolCallEntry(
922
+ state,
923
+ turnId,
924
+ event.toolCallId,
925
+ event.toolName,
926
+ formatToolPreview(event.args),
927
+ );
761
928
  return;
762
929
  }
763
930
  case "tool_execution_update": {
764
- const turnId = state.toolCalls.get(event.toolCallId)?.turnId ?? ensureTranscriptTurn(state);
931
+ const turnId =
932
+ state.toolCalls.get(event.toolCallId)?.turnId ??
933
+ ensureTranscriptTurn(state);
765
934
  const result = summarizeToolResult(event.partialResult);
766
935
  upsertToolResultEntry(
767
936
  state,
@@ -776,7 +945,9 @@ function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEven
776
945
  return;
777
946
  }
778
947
  case "tool_execution_end": {
779
- const turnId = state.toolCalls.get(event.toolCallId)?.turnId ?? ensureTranscriptTurn(state);
948
+ const turnId =
949
+ state.toolCalls.get(event.toolCallId)?.turnId ??
950
+ ensureTranscriptTurn(state);
780
951
  const result = summarizeToolResult(event.result);
781
952
  upsertToolResultEntry(
782
953
  state,
@@ -799,45 +970,100 @@ function applyTranscriptEvent(state: BtwTranscriptState, event: AgentSessionEven
799
970
  }
800
971
  }
801
972
 
802
- function appendPersistedTranscriptTurn(state: BtwTranscriptState, details: BtwDetails): void {
973
+ function appendPersistedTranscriptTurn(
974
+ state: BtwTranscriptState,
975
+ details: BtwDetails,
976
+ ): void {
803
977
  const turnId = ensureTranscriptTurn(state);
804
978
  upsertUserMessageEntry(state, turnId, details.question);
805
979
  if (details.thinking) {
806
- upsertTranscriptTextEntry(state, turnId, "thinking", details.thinking, false);
980
+ upsertTranscriptTextEntry(
981
+ state,
982
+ turnId,
983
+ "thinking",
984
+ details.thinking,
985
+ false,
986
+ );
807
987
  }
808
- upsertTranscriptTextEntry(state, turnId, "assistant-text", details.answer, false);
988
+ upsertTranscriptTextEntry(
989
+ state,
990
+ turnId,
991
+ "assistant-text",
992
+ details.answer,
993
+ false,
994
+ );
809
995
  finishTranscriptTurn(state, turnId);
810
996
  }
811
997
 
812
- function setTranscriptFailure(state: BtwTranscriptState, message: string): void {
813
- const turnId = state.currentTurnId ?? state.lastTurnId ?? ensureTranscriptTurn(state);
814
- upsertTranscriptTextEntry(state, turnId, "assistant-text", `❌ ${message}`, false);
998
+ function setTranscriptFailure(
999
+ state: BtwTranscriptState,
1000
+ message: string,
1001
+ ): void {
1002
+ const turnId =
1003
+ state.currentTurnId ?? state.lastTurnId ?? ensureTranscriptTurn(state);
1004
+ upsertTranscriptTextEntry(
1005
+ state,
1006
+ turnId,
1007
+ "assistant-text",
1008
+ `❌ ${message}`,
1009
+ false,
1010
+ );
815
1011
  finishTranscriptTurn(state, turnId);
816
1012
  }
817
1013
 
818
1014
  function hasStreamingTranscriptEntry(entries: BtwTranscript): boolean {
819
1015
  return entries.some(
820
1016
  (entry) =>
821
- (entry.type === "thinking" || entry.type === "assistant-text" || entry.type === "tool-result") &&
1017
+ (entry.type === "thinking" ||
1018
+ entry.type === "assistant-text" ||
1019
+ entry.type === "tool-result") &&
822
1020
  entry.streaming,
823
1021
  );
824
1022
  }
825
1023
 
826
1024
  function getCompletedExchangeCount(entries: BtwTranscript): number {
827
- return entries.filter((entry) => entry.type === "assistant-text" && !entry.streaming).length;
1025
+ return entries.filter(
1026
+ (entry) => entry.type === "assistant-text" && !entry.streaming,
1027
+ ).length;
828
1028
  }
829
1029
 
830
- function buildOverlayTranscript(entries: BtwTranscript, theme: ExtensionContext["ui"]["theme"]): string[] {
1030
+ function buildOverlayTranscript(
1031
+ entries: BtwTranscript,
1032
+ theme: ExtensionContext["ui"]["theme"],
1033
+ ): string[] {
831
1034
  if (entries.length === 0) {
832
1035
  return [theme.fg("dim", "暂无 BTW 对话。输入侧边问题开始。")];
833
1036
  }
834
1037
 
835
1038
  const lines: string[] = [];
836
- const userBadge = buildTranscriptBadge(theme, "You", "userMessageBg", "accent");
837
- const thinkingBadge = buildTranscriptBadge(theme, "Thinking", "toolPendingBg", "warning");
838
- const toolBadge = buildTranscriptBadge(theme, "Tool", "toolPendingBg", "warning");
839
- const assistantBadge = buildTranscriptBadge(theme, "Assistant", "customMessageBg", "success");
840
- const separator = theme.fg("borderMuted", "────────────────────────────────────────");
1039
+ const userBadge = buildTranscriptBadge(
1040
+ theme,
1041
+ "You",
1042
+ "userMessageBg",
1043
+ "accent",
1044
+ );
1045
+ const thinkingBadge = buildTranscriptBadge(
1046
+ theme,
1047
+ "Thinking",
1048
+ "toolPendingBg",
1049
+ "warning",
1050
+ );
1051
+ const toolBadge = buildTranscriptBadge(
1052
+ theme,
1053
+ "Tool",
1054
+ "toolPendingBg",
1055
+ "warning",
1056
+ );
1057
+ const assistantBadge = buildTranscriptBadge(
1058
+ theme,
1059
+ "Assistant",
1060
+ "customMessageBg",
1061
+ "success",
1062
+ );
1063
+ const separator = theme.fg(
1064
+ "borderMuted",
1065
+ "────────────────────────────────────────",
1066
+ );
841
1067
  const blockIndent = " ";
842
1068
  const resultIndent = blockIndent;
843
1069
 
@@ -868,7 +1094,11 @@ function buildOverlayTranscript(entries: BtwTranscript, theme: ExtensionContext[
868
1094
  const pushStackedBlock = (
869
1095
  header: string,
870
1096
  text: string,
871
- options: { blankBefore?: boolean; indent?: string; style?: (value: string) => string } = {},
1097
+ options: {
1098
+ blankBefore?: boolean;
1099
+ indent?: string;
1100
+ style?: (value: string) => string;
1101
+ } = {},
872
1102
  ) => {
873
1103
  const bodyLines = text.split("\n");
874
1104
  const indent = options.indent ?? blockIndent;
@@ -898,7 +1128,9 @@ function buildOverlayTranscript(entries: BtwTranscript, theme: ExtensionContext[
898
1128
  }
899
1129
 
900
1130
  if (entry.type === "thinking") {
901
- const thinkingHeader = entry.streaming ? `${thinkingBadge} ${theme.fg("warning", "▍")}` : thinkingBadge;
1131
+ const thinkingHeader = entry.streaming
1132
+ ? `${thinkingBadge} ${theme.fg("warning", "▍")}`
1133
+ : thinkingBadge;
902
1134
  pushStackedBlock(thinkingHeader, entry.text, {
903
1135
  style: (line) => theme.fg("warning", theme.italic(line)),
904
1136
  });
@@ -918,17 +1150,26 @@ function buildOverlayTranscript(entries: BtwTranscript, theme: ExtensionContext[
918
1150
  : entry.streaming
919
1151
  ? theme.fg("warning", "↳ 流式输出中")
920
1152
  : theme.fg("dim", "↳ result");
921
- const truncationLabel = entry.truncated ? theme.fg("dim", " (truncated)") : "";
922
- pushStackedBlock(`${resultHeaderLabel}${truncationLabel}`, entry.content, {
923
- blankBefore: false,
924
- indent: resultIndent,
925
- style: (line) => (entry.isError ? theme.fg("error", line) : theme.fg("dim", line)),
926
- });
1153
+ const truncationLabel = entry.truncated
1154
+ ? theme.fg("dim", " (truncated)")
1155
+ : "";
1156
+ pushStackedBlock(
1157
+ `${resultHeaderLabel}${truncationLabel}`,
1158
+ entry.content,
1159
+ {
1160
+ blankBefore: false,
1161
+ indent: resultIndent,
1162
+ style: (line) =>
1163
+ entry.isError ? theme.fg("error", line) : theme.fg("dim", line),
1164
+ },
1165
+ );
927
1166
  continue;
928
1167
  }
929
1168
 
930
1169
  if (entry.type === "assistant-text") {
931
- const assistantHeader = entry.streaming ? `${assistantBadge} ${theme.fg("warning", "▍")}` : assistantBadge;
1170
+ const assistantHeader = entry.streaming
1171
+ ? `${assistantBadge} ${theme.fg("warning", "▍")}`
1172
+ : assistantBadge;
932
1173
  pushStackedBlock(assistantHeader, entry.text);
933
1174
  }
934
1175
  }
@@ -936,7 +1177,9 @@ function buildOverlayTranscript(entries: BtwTranscript, theme: ExtensionContext[
936
1177
  return lines;
937
1178
  }
938
1179
 
939
- function getLastAssistantMessage(session: AgentSession): AssistantMessage | null {
1180
+ function getLastAssistantMessage(
1181
+ session: AgentSession,
1182
+ ): AssistantMessage | null {
940
1183
  for (let i = session.state.messages.length - 1; i >= 0; i--) {
941
1184
  const message = session.state.messages[i];
942
1185
  if (message.role === "assistant") {
@@ -957,10 +1200,18 @@ function buildBtwMessageContent(question: string, answer: string): string {
957
1200
  }
958
1201
 
959
1202
  function formatThread(thread: BtwHandoffExchange[]): string {
960
- return thread.map((entry) => `User: ${entry.user.trim()}\nAssistant: ${entry.assistant.trim()}`).join("\n\n---\n\n");
1203
+ return thread
1204
+ .map(
1205
+ (entry) =>
1206
+ `User: ${entry.user.trim()}\nAssistant: ${entry.assistant.trim()}`,
1207
+ )
1208
+ .join("\n\n---\n\n");
961
1209
  }
962
1210
 
963
- function isThreadContinuationMarker(messages: Message[], index: number): boolean {
1211
+ function isThreadContinuationMarker(
1212
+ messages: Message[],
1213
+ index: number,
1214
+ ): boolean {
964
1215
  const userMessage = messages[index];
965
1216
  const assistantMessage = messages[index + 1];
966
1217
  return (
@@ -971,9 +1222,18 @@ function isThreadContinuationMarker(messages: Message[], index: number): boolean
971
1222
  );
972
1223
  }
973
1224
 
974
- function extractBtwHandoffThread(sessionRuntime: BtwSessionRuntime): BtwHandoffExchange[] {
975
- const handoffMessages = sessionRuntime.session.state.messages.slice(sessionRuntime.sideThreadStartIndex);
976
- const threadMessages = isThreadContinuationMarker(handoffMessages as Message[], 0) ? handoffMessages.slice(2) : handoffMessages;
1225
+ function extractBtwHandoffThread(
1226
+ sessionRuntime: BtwSessionRuntime,
1227
+ ): BtwHandoffExchange[] {
1228
+ const handoffMessages = sessionRuntime.session.state.messages.slice(
1229
+ sessionRuntime.sideThreadStartIndex,
1230
+ );
1231
+ const threadMessages = isThreadContinuationMarker(
1232
+ handoffMessages as Message[],
1233
+ 0,
1234
+ )
1235
+ ? handoffMessages.slice(2)
1236
+ : handoffMessages;
977
1237
  const exchanges: BtwHandoffExchange[] = [];
978
1238
  let currentUser = "";
979
1239
  let currentAssistant = "";
@@ -1007,7 +1267,9 @@ function extractBtwHandoffThread(sessionRuntime: BtwSessionRuntime): BtwHandoffE
1007
1267
  continue;
1008
1268
  }
1009
1269
 
1010
- currentAssistant = currentAssistant ? `${currentAssistant}\n\n${text}` : text;
1270
+ currentAssistant = currentAssistant
1271
+ ? `${currentAssistant}\n\n${text}`
1272
+ : text;
1011
1273
  }
1012
1274
 
1013
1275
  pushCurrent();
@@ -1040,7 +1302,11 @@ function saveVisibleBtwNote(
1040
1302
  return "saved";
1041
1303
  }
1042
1304
 
1043
- function notify(ctx: ExtensionContext | ExtensionCommandContext, message: string, level: "info" | "warning" | "error"): void {
1305
+ function notify(
1306
+ ctx: ExtensionContext | ExtensionCommandContext,
1307
+ message: string,
1308
+ level: "info" | "warning" | "error",
1309
+ ): void {
1044
1310
  if (ctx.hasUI) {
1045
1311
  ctx.ui.notify(message, level);
1046
1312
  }
@@ -1166,7 +1432,10 @@ class BtwOverlayComponent extends Container implements Focusable {
1166
1432
  private borderLine(innerWidth: number, edge: "top" | "bottom"): string {
1167
1433
  const left = edge === "top" ? "┌" : "└";
1168
1434
  const right = edge === "top" ? "┐" : "┘";
1169
- return this.theme.fg("borderMuted", `${left}${"─".repeat(innerWidth)}${right}`);
1435
+ return this.theme.fg(
1436
+ "borderMuted",
1437
+ `${left}${"─".repeat(innerWidth)}${right}`,
1438
+ );
1170
1439
  }
1171
1440
 
1172
1441
  private wrapTranscript(innerWidth: number): string[] {
@@ -1194,13 +1463,20 @@ class BtwOverlayComponent extends Container implements Focusable {
1194
1463
 
1195
1464
  if (matchesKey(data, Key.pageUp)) {
1196
1465
  this.followTranscript = false;
1197
- this.transcriptScrollOffset = Math.max(0, this.transcriptScrollOffset - Math.max(1, this.transcriptViewportHeight - 1));
1466
+ this.transcriptScrollOffset = Math.max(
1467
+ 0,
1468
+ this.transcriptScrollOffset -
1469
+ Math.max(1, this.transcriptViewportHeight - 1),
1470
+ );
1198
1471
  this.tui.requestRender();
1199
1472
  return;
1200
1473
  }
1201
1474
 
1202
1475
  if (matchesKey(data, Key.pageDown)) {
1203
- this.transcriptScrollOffset += Math.max(1, this.transcriptViewportHeight - 1);
1476
+ this.transcriptScrollOffset += Math.max(
1477
+ 1,
1478
+ this.transcriptViewportHeight - 1,
1479
+ );
1204
1480
  this.tui.requestRender();
1205
1481
  return;
1206
1482
  }
@@ -1237,7 +1513,10 @@ class BtwOverlayComponent extends Container implements Focusable {
1237
1513
  if (this.followTranscript) {
1238
1514
  this.transcriptScrollOffset = maxScroll;
1239
1515
  } else {
1240
- this.transcriptScrollOffset = Math.max(0, Math.min(this.transcriptScrollOffset, maxScroll));
1516
+ this.transcriptScrollOffset = Math.max(
1517
+ 0,
1518
+ Math.min(this.transcriptScrollOffset, maxScroll),
1519
+ );
1241
1520
  if (this.transcriptScrollOffset >= maxScroll) {
1242
1521
  this.followTranscript = true;
1243
1522
  }
@@ -1247,7 +1526,10 @@ class BtwOverlayComponent extends Container implements Focusable {
1247
1526
  this.transcriptScrollOffset,
1248
1527
  this.transcriptScrollOffset + transcriptHeight,
1249
1528
  );
1250
- const transcriptPadCount = Math.max(0, transcriptHeight - visibleTranscript.length);
1529
+ const transcriptPadCount = Math.max(
1530
+ 0,
1531
+ transcriptHeight - visibleTranscript.length,
1532
+ );
1251
1533
  const hiddenAbove = this.transcriptScrollOffset;
1252
1534
  const hiddenBelow = Math.max(0, maxScroll - this.transcriptScrollOffset);
1253
1535
  const summary =
@@ -1257,7 +1539,12 @@ class BtwOverlayComponent extends Container implements Focusable {
1257
1539
 
1258
1540
  const lines = [this.borderLine(innerWidth, "top")];
1259
1541
 
1260
- lines.push(this.frameLine(this.theme.fg("accent", this.theme.bold(this.modeTextValue.trim())), innerWidth));
1542
+ lines.push(
1543
+ this.frameLine(
1544
+ this.theme.fg("accent", this.theme.bold(this.modeTextValue.trim())),
1545
+ innerWidth,
1546
+ ),
1547
+ );
1261
1548
  lines.push(this.frameLine(this.theme.fg("dim", summary), innerWidth));
1262
1549
  lines.push(this.ruleLine(innerWidth));
1263
1550
 
@@ -1269,9 +1556,19 @@ class BtwOverlayComponent extends Container implements Focusable {
1269
1556
  }
1270
1557
 
1271
1558
  lines.push(this.ruleLine(innerWidth));
1272
- lines.push(this.frameLine(this.theme.fg("warning", this.statusTextValue.trim()), innerWidth));
1559
+ lines.push(
1560
+ this.frameLine(
1561
+ this.theme.fg("warning", this.statusTextValue.trim()),
1562
+ innerWidth,
1563
+ ),
1564
+ );
1273
1565
  lines.push(this.inputFrameLine(dialogWidth));
1274
- lines.push(this.frameLine(this.theme.fg("dim", this.hintsTextValue.trim()), innerWidth));
1566
+ lines.push(
1567
+ this.frameLine(
1568
+ this.theme.fg("dim", this.hintsTextValue.trim()),
1569
+ innerWidth,
1570
+ ),
1571
+ );
1275
1572
  lines.push(this.borderLine(innerWidth, "bottom"));
1276
1573
 
1277
1574
  return lines;
@@ -1295,7 +1592,9 @@ class BtwOverlayComponent extends Container implements Focusable {
1295
1592
  this.modeText.setText(this.modeTextValue);
1296
1593
  const entries = this.readTranscriptEntries();
1297
1594
  const exchanges = getCompletedExchangeCount(entries);
1298
- const active = hasStreamingTranscriptEntry(entries) ? " · 流式输出中" : " · 空闲";
1595
+ const active = hasStreamingTranscriptEntry(entries)
1596
+ ? " · 流式输出中"
1597
+ : " · 空闲";
1299
1598
  this.summaryTextValue = `${exchanges} 轮对话${active}`;
1300
1599
  this.summaryText.setText(this.summaryTextValue);
1301
1600
 
@@ -1308,7 +1607,8 @@ class BtwOverlayComponent extends Container implements Focusable {
1308
1607
  const status = this.getStatus() ?? "就绪。Enter 提交,Escape 取消。";
1309
1608
  this.statusTextValue = status;
1310
1609
  this.statusText.setText(this.statusTextValue);
1311
- this.hintsTextValue = "Enter 提交 · Alt+/ 切换焦点 · Escape 取消 · PgUp/PgDn 滚动";
1610
+ this.hintsTextValue =
1611
+ "Enter 提交 · Alt+/ 切换焦点 · Escape 取消 · PgUp/PgDn 滚动";
1312
1612
  this.hintsText.setText(this.hintsTextValue);
1313
1613
  this.tui.requestRender();
1314
1614
  }
@@ -1335,7 +1635,10 @@ export default function (pi: ExtensionAPI) {
1335
1635
  }
1336
1636
  }
1337
1637
 
1338
- function setOverlayStatus(status: string | null, ctx?: ExtensionContext | ExtensionCommandContext): void {
1638
+ function setOverlayStatus(
1639
+ status: string | null,
1640
+ ctx?: ExtensionContext | ExtensionCommandContext,
1641
+ ): void {
1339
1642
  overlayStatus = status;
1340
1643
  syncUi(ctx);
1341
1644
  }
@@ -1376,7 +1679,10 @@ export default function (pi: ExtensionAPI) {
1376
1679
  overlayRuntime?.refresh?.();
1377
1680
  }
1378
1681
 
1379
- function removeBtwSessionSubscription(sessionRuntime: BtwSessionRuntime, unsubscribe: () => void): void {
1682
+ function removeBtwSessionSubscription(
1683
+ sessionRuntime: BtwSessionRuntime,
1684
+ unsubscribe: () => void,
1685
+ ): void {
1380
1686
  if (!sessionRuntime.subscriptions.delete(unsubscribe)) {
1381
1687
  return;
1382
1688
  }
@@ -1385,11 +1691,13 @@ export default function (pi: ExtensionAPI) {
1385
1691
  unsubscribe();
1386
1692
  } catch (error) {
1387
1693
  // ✅ 改进错误处理:记录错误而不忽略
1388
- console.warn('[btw] Unsubscribe failed during session removal:', error);
1694
+ console.warn("[btw] Unsubscribe failed during session removal:", error);
1389
1695
  }
1390
1696
  }
1391
1697
 
1392
- function clearBtwSessionSubscriptions(sessionRuntime: BtwSessionRuntime): void {
1698
+ function clearBtwSessionSubscriptions(
1699
+ sessionRuntime: BtwSessionRuntime,
1700
+ ): void {
1393
1701
  for (const unsubscribe of [...sessionRuntime.subscriptions]) {
1394
1702
  removeBtwSessionSubscription(sessionRuntime, unsubscribe);
1395
1703
  }
@@ -1400,7 +1708,10 @@ export default function (pi: ExtensionAPI) {
1400
1708
  event: AgentSessionEvent,
1401
1709
  ctx?: ExtensionContext | ExtensionCommandContext,
1402
1710
  ): void {
1403
- if (activeBtwSession?.session !== sessionRuntime.session || !overlayRuntime) {
1711
+ if (
1712
+ activeBtwSession?.session !== sessionRuntime.session ||
1713
+ !overlayRuntime
1714
+ ) {
1404
1715
  return;
1405
1716
  }
1406
1717
 
@@ -1412,7 +1723,12 @@ export default function (pi: ExtensionAPI) {
1412
1723
  }
1413
1724
 
1414
1725
  if (event.type === "tool_execution_end") {
1415
- setOverlayStatus(sessionRuntime.session.isStreaming ? `⏳ 运行工具: ${event.toolName}` : "⏳ 流式输出中...", ctx);
1726
+ setOverlayStatus(
1727
+ sessionRuntime.session.isStreaming
1728
+ ? `⏳ 运行工具: ${event.toolName}`
1729
+ : "⏳ 流式输出中...",
1730
+ ctx,
1731
+ );
1416
1732
  return;
1417
1733
  }
1418
1734
 
@@ -1431,15 +1747,19 @@ export default function (pi: ExtensionAPI) {
1431
1747
  }
1432
1748
  }
1433
1749
 
1434
- function subscribeOverlayToActiveBtwSession(ctx?: ExtensionContext | ExtensionCommandContext): void {
1750
+ function subscribeOverlayToActiveBtwSession(
1751
+ ctx?: ExtensionContext | ExtensionCommandContext,
1752
+ ): void {
1435
1753
  const sessionRuntime = activeBtwSession;
1436
1754
  if (!sessionRuntime || sessionRuntime.subscriptions.size > 0) {
1437
1755
  return;
1438
1756
  }
1439
1757
 
1440
- const unsubscribe = sessionRuntime.session.subscribe((event: AgentSessionEvent) => {
1441
- handleBtwSessionEvent(sessionRuntime, event, ctx);
1442
- });
1758
+ const unsubscribe = sessionRuntime.session.subscribe(
1759
+ (event: AgentSessionEvent) => {
1760
+ handleBtwSessionEvent(sessionRuntime, event, ctx);
1761
+ },
1762
+ );
1443
1763
  sessionRuntime.subscriptions.add(unsubscribe);
1444
1764
  }
1445
1765
 
@@ -1462,7 +1782,7 @@ export default function (pi: ExtensionAPI) {
1462
1782
  await current.session.abort();
1463
1783
  } catch (error) {
1464
1784
  // ✅ 改进错误处理:记录错误而不忽略
1465
- console.warn('[btw] Session abort failed during disposal:', error);
1785
+ console.warn("[btw] Session abort failed during disposal:", error);
1466
1786
  }
1467
1787
 
1468
1788
  current.session.dispose();
@@ -1478,7 +1798,8 @@ export default function (pi: ExtensionAPI) {
1478
1798
  notifyOnFallback = false,
1479
1799
  ): Promise<ResolvedBtwModel> {
1480
1800
  if (btwModelOverride) {
1481
- const auth = await ctx.modelRegistry.getApiKeyAndHeaders(btwModelOverride);
1801
+ const auth =
1802
+ await ctx.modelRegistry.getApiKeyAndHeaders(btwModelOverride);
1482
1803
  if (auth.ok && auth.apiKey) {
1483
1804
  return {
1484
1805
  model: btwModelOverride,
@@ -1533,7 +1854,8 @@ export default function (pi: ExtensionAPI) {
1533
1854
  notifyOnFallback = false,
1534
1855
  ): Promise<ResolvedBtwSettings> {
1535
1856
  const resolvedModel = await resolveBtwModel(ctx, notifyOnFallback);
1536
- const thinkingLevel = btwThinkingOverride ?? (pi.getThinkingLevel() as SessionThinkingLevel);
1857
+ const thinkingLevel =
1858
+ btwThinkingOverride ?? (pi.getThinkingLevel() as SessionThinkingLevel);
1537
1859
 
1538
1860
  return {
1539
1861
  model: resolvedModel.model,
@@ -1565,14 +1887,26 @@ export default function (pi: ExtensionAPI) {
1565
1887
  }
1566
1888
 
1567
1889
  function describeResolvedThinking(settings: ResolvedBtwSettings): string {
1568
- const source = settings.thinkingSource === "override" ? "override" : "inherits main thread";
1890
+ const source =
1891
+ settings.thinkingSource === "override"
1892
+ ? "override"
1893
+ : "inherits main thread";
1569
1894
  return `BTW thinking: ${settings.thinkingLevel} (${source}).`;
1570
1895
  }
1571
1896
 
1572
- async function setBtwModelOverride(ctx: ExtensionCommandContext, nextModel: SessionModel | null): Promise<void> {
1897
+ async function setBtwModelOverride(
1898
+ ctx: ExtensionCommandContext,
1899
+ nextModel: SessionModel | null,
1900
+ ): Promise<void> {
1573
1901
  btwModelOverride = nextModel;
1574
1902
  const details: BtwModelOverrideDetails = nextModel
1575
- ? { action: "set", timestamp: Date.now(), provider: nextModel.provider, id: nextModel.id, api: nextModel.api }
1903
+ ? {
1904
+ action: "set",
1905
+ timestamp: Date.now(),
1906
+ provider: nextModel.provider,
1907
+ id: nextModel.id,
1908
+ api: nextModel.api,
1909
+ }
1576
1910
  : { action: "clear", timestamp: Date.now() };
1577
1911
  pi.appendEntry(BTW_MODEL_OVERRIDE_TYPE, details);
1578
1912
  await disposeBtwSession();
@@ -1590,7 +1924,11 @@ export default function (pi: ExtensionAPI) {
1590
1924
  ): Promise<void> {
1591
1925
  btwThinkingOverride = nextThinkingLevel;
1592
1926
  const details: BtwThinkingOverrideDetails = nextThinkingLevel
1593
- ? { action: "set", timestamp: Date.now(), thinkingLevel: nextThinkingLevel }
1927
+ ? {
1928
+ action: "set",
1929
+ timestamp: Date.now(),
1930
+ thinkingLevel: nextThinkingLevel,
1931
+ }
1594
1932
  : { action: "clear", timestamp: Date.now() };
1595
1933
  pi.appendEntry(BTW_THINKING_OVERRIDE_TYPE, details);
1596
1934
  await disposeBtwSession();
@@ -1602,7 +1940,10 @@ export default function (pi: ExtensionAPI) {
1602
1940
  notify(ctx, `${message} ${describeResolvedThinking(settings)}`, "info");
1603
1941
  }
1604
1942
 
1605
- async function createBtwSubSession(ctx: ExtensionCommandContext, mode: BtwThreadMode): Promise<BtwSessionRuntime> {
1943
+ async function createBtwSubSession(
1944
+ ctx: ExtensionCommandContext,
1945
+ mode: BtwThreadMode,
1946
+ ): Promise<BtwSessionRuntime> {
1606
1947
  const settings = await resolveBtwSettings(ctx, true);
1607
1948
  if (!settings.model) {
1608
1949
  throw new Error(settings.fallbackReason || "No active model selected.");
@@ -1611,22 +1952,30 @@ export default function (pi: ExtensionAPI) {
1611
1952
  const { session } = await createAgentSession({
1612
1953
  sessionManager: SessionManager.inMemory(),
1613
1954
  model: settings.model,
1614
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
1615
- thinkingLevel: settings.thinkingLevel,
1616
- // Match pi's default coding-agent toolset (read/bash/edit/write).
1955
+ // pi 1.1.0: 不传 modelRuntime — 让 pi 用默认 runtime (从 ~/.aicode-cli/agent/auth.json 读)
1956
+ // 之前 Sprint 1 桥接 ctx.modelRegistry → ModelRuntime 是错的, ModelRegistry 缺 checkAuth 方法
1617
1957
  tools: ["read", "bash", "edit", "write"],
1618
1958
  resourceLoader: createBtwResourceLoader(ctx),
1619
1959
  });
1620
1960
 
1621
- const { messages: seedMessages, sideThreadStartIndex } = buildBtwSeedState(ctx, pendingThread, mode, settings.model);
1961
+ const { messages: seedMessages, sideThreadStartIndex } = buildBtwSeedState(
1962
+ ctx,
1963
+ pendingThread,
1964
+ mode,
1965
+ settings.model,
1966
+ );
1622
1967
  if (seedMessages.length > 0) {
1623
- session.agent.state.messages = seedMessages as typeof session.state.messages;
1968
+ session.agent.state.messages =
1969
+ seedMessages as typeof session.state.messages;
1624
1970
  }
1625
1971
 
1626
1972
  return { session, mode, subscriptions: new Set(), sideThreadStartIndex };
1627
1973
  }
1628
1974
 
1629
- async function ensureBtwSession(ctx: ExtensionCommandContext, mode: BtwThreadMode): Promise<BtwSessionRuntime | null> {
1975
+ async function ensureBtwSession(
1976
+ ctx: ExtensionCommandContext,
1977
+ mode: BtwThreadMode,
1978
+ ): Promise<BtwSessionRuntime | null> {
1630
1979
  const settings = await resolveBtwSettings(ctx);
1631
1980
  if (!settings.model) {
1632
1981
  return null;
@@ -1641,7 +1990,9 @@ export default function (pi: ExtensionAPI) {
1641
1990
  return activeBtwSession;
1642
1991
  }
1643
1992
 
1644
- async function ensureOverlay(ctx: ExtensionCommandContext | ExtensionContext): Promise<void> {
1993
+ async function ensureOverlay(
1994
+ ctx: ExtensionCommandContext | ExtensionContext,
1995
+ ): Promise<void> {
1645
1996
  if (!ctx.hasUI) {
1646
1997
  return;
1647
1998
  }
@@ -1743,11 +2094,19 @@ export default function (pi: ExtensionAPI) {
1743
2094
  if (overlayRuntime === runtime) {
1744
2095
  overlayRuntime = null;
1745
2096
  }
1746
- notify(ctx, error instanceof Error ? error.message : String(error), "error");
2097
+ notify(
2098
+ ctx,
2099
+ error instanceof Error ? error.message : String(error),
2100
+ "error",
2101
+ );
1747
2102
  });
1748
2103
  }
1749
2104
 
1750
- async function dispatchBtwCommand(name: string, args: string, ctx: ExtensionCommandContext): Promise<boolean> {
2105
+ async function dispatchBtwCommand(
2106
+ name: string,
2107
+ args: string,
2108
+ ctx: ExtensionCommandContext,
2109
+ ): Promise<boolean> {
1751
2110
  const trimmedArgs = args.trim();
1752
2111
 
1753
2112
  if (name === "btw") {
@@ -1845,7 +2204,10 @@ export default function (pi: ExtensionAPI) {
1845
2204
  return true;
1846
2205
  }
1847
2206
 
1848
- await setBtwThinkingOverride(ctx, parsed.action === "clear" ? null : parsed.thinkingLevel);
2207
+ await setBtwThinkingOverride(
2208
+ ctx,
2209
+ parsed.action === "clear" ? null : parsed.thinkingLevel,
2210
+ );
1849
2211
  return true;
1850
2212
  }
1851
2213
 
@@ -1872,7 +2234,11 @@ export default function (pi: ExtensionAPI) {
1872
2234
  notify(ctx, `已注入 BTW 线程(${count} 轮对话)。`, "info");
1873
2235
  } catch (error) {
1874
2236
  setOverlayStatus("注入失败。线程已保留,可重试或总结。", ctx);
1875
- notify(ctx, error instanceof Error ? error.message : String(error), "error");
2237
+ notify(
2238
+ ctx,
2239
+ error instanceof Error ? error.message : String(error),
2240
+ "error",
2241
+ );
1876
2242
  }
1877
2243
  return true;
1878
2244
  }
@@ -1901,7 +2267,11 @@ export default function (pi: ExtensionAPI) {
1901
2267
  notify(ctx, `已注入 BTW 总结(${count} 轮对话)。`, "info");
1902
2268
  } catch (error) {
1903
2269
  setOverlayStatus("总结失败。线程已保留,可重试或注入。", ctx);
1904
- notify(ctx, error instanceof Error ? error.message : String(error), "error");
2270
+ notify(
2271
+ ctx,
2272
+ error instanceof Error ? error.message : String(error),
2273
+ "error",
2274
+ );
1905
2275
  }
1906
2276
  return true;
1907
2277
  }
@@ -1909,9 +2279,13 @@ export default function (pi: ExtensionAPI) {
1909
2279
  return false;
1910
2280
  }
1911
2281
 
1912
- function parseOverlayBtwCommand(value: string): { name: string; args: string } | null {
2282
+ function parseOverlayBtwCommand(
2283
+ value: string,
2284
+ ): { name: string; args: string } | null {
1913
2285
  const trimmed = value.trim();
1914
- const match = trimmed.match(/^\/(btw:(?:new|tangent|clear|inject|summarize|model|thinking))(?:\s+(.*))?$/);
2286
+ const match = trimmed.match(
2287
+ /^\/(btw:(?:new|tangent|clear|inject|summarize|model|thinking))(?:\s+(.*))?$/,
2288
+ );
1915
2289
  if (!match) {
1916
2290
  return null;
1917
2291
  }
@@ -1922,7 +2296,10 @@ export default function (pi: ExtensionAPI) {
1922
2296
  };
1923
2297
  }
1924
2298
 
1925
- async function submitFromOverlay(ctx: ExtensionCommandContext | ExtensionContext, value: string): Promise<void> {
2299
+ async function submitFromOverlay(
2300
+ ctx: ExtensionCommandContext | ExtensionContext,
2301
+ value: string,
2302
+ ): Promise<void> {
1926
2303
  const question = value.trim();
1927
2304
  if (!question) {
1928
2305
  setOverlayStatus("Enter a BTW prompt before submitting.", ctx);
@@ -1930,7 +2307,10 @@ export default function (pi: ExtensionAPI) {
1930
2307
  }
1931
2308
 
1932
2309
  if (!("getSystemPrompt" in ctx)) {
1933
- setOverlayStatus("BTW overlay submit requires a command context. Reopen BTW from a command.", ctx);
2310
+ setOverlayStatus(
2311
+ "BTW overlay submit requires a command context. Reopen BTW from a command.",
2312
+ ctx,
2313
+ );
1934
2314
  return;
1935
2315
  }
1936
2316
 
@@ -1984,7 +2364,9 @@ export default function (pi: ExtensionAPI) {
1984
2364
 
1985
2365
  for (let i = 0; i < branch.length; i++) {
1986
2366
  if (isCustomEntry(branch[i], BTW_MODEL_OVERRIDE_TYPE)) {
1987
- const details = (branch[i] as unknown as { data?: BtwModelOverrideDetails }).data;
2367
+ const details = (
2368
+ branch[i] as unknown as { data?: BtwModelOverrideDetails }
2369
+ ).data;
1988
2370
  if (details?.action === "set") {
1989
2371
  const resolved = ctx.modelRegistry.find(details.provider, details.id);
1990
2372
  if (resolved) {
@@ -1999,7 +2381,9 @@ export default function (pi: ExtensionAPI) {
1999
2381
  }
2000
2382
 
2001
2383
  if (isCustomEntry(branch[i], BTW_THINKING_OVERRIDE_TYPE)) {
2002
- const details = (branch[i] as unknown as { data?: BtwThinkingOverrideDetails }).data;
2384
+ const details = (
2385
+ branch[i] as unknown as { data?: BtwThinkingOverrideDetails }
2386
+ ).data;
2003
2387
  btwThinkingOverride =
2004
2388
  details?.action === "set"
2005
2389
  ? details.thinkingLevel
@@ -2010,7 +2394,8 @@ export default function (pi: ExtensionAPI) {
2010
2394
 
2011
2395
  if (isCustomEntry(branch[i], BTW_RESET_TYPE)) {
2012
2396
  lastResetIndex = i;
2013
- const details = (branch[i] as unknown as { data?: BtwResetDetails }).data;
2397
+ const details = (branch[i] as unknown as { data?: BtwResetDetails })
2398
+ .data;
2014
2399
  pendingMode = details?.mode ?? "contextual";
2015
2400
  }
2016
2401
  }
@@ -2055,7 +2440,9 @@ export default function (pi: ExtensionAPI) {
2055
2440
 
2056
2441
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
2057
2442
  if (!auth.ok || !auth.apiKey) {
2058
- const message = auth.ok ? `No credentials available for ${model.provider}/${model.id}.` : auth.error;
2443
+ const message = auth.ok
2444
+ ? `No credentials available for ${model.provider}/${model.id}.`
2445
+ : auth.error;
2059
2446
  setOverlayStatus(message, ctx);
2060
2447
  notify(ctx, message, "error");
2061
2448
  await ensureOverlay(ctx);
@@ -2088,7 +2475,10 @@ export default function (pi: ExtensionAPI) {
2088
2475
  throw new Error("BTW request finished without a response.");
2089
2476
  }
2090
2477
  if (response.stopReason === "aborted") {
2091
- removeTranscriptTurn(transcriptState, transcriptState.lastTurnId ?? transcriptState.currentTurnId);
2478
+ removeTranscriptTurn(
2479
+ transcriptState,
2480
+ transcriptState.lastTurnId ?? transcriptState.currentTurnId,
2481
+ );
2092
2482
  setOverlayStatus("Request aborted.", ctx);
2093
2483
  return;
2094
2484
  }
@@ -2096,9 +2486,16 @@ export default function (pi: ExtensionAPI) {
2096
2486
  throw new Error(response.errorMessage || "BTW request failed.");
2097
2487
  }
2098
2488
 
2099
- const completedTurnId = transcriptState.lastTurnId ?? transcriptState.currentTurnId;
2489
+ const completedTurnId =
2490
+ transcriptState.lastTurnId ?? transcriptState.currentTurnId;
2100
2491
  const streamedThinking =
2101
- completedTurnId !== null ? findLatestTranscriptEntry(transcriptState, completedTurnId, "thinking")?.text : "";
2492
+ completedTurnId !== null
2493
+ ? findLatestTranscriptEntry(
2494
+ transcriptState,
2495
+ completedTurnId,
2496
+ "thinking",
2497
+ )?.text
2498
+ : "";
2102
2499
  const answer = extractAnswer(response);
2103
2500
  const thinking = extractThinking(response) || streamedThinking || "";
2104
2501
 
@@ -2125,12 +2522,19 @@ export default function (pi: ExtensionAPI) {
2125
2522
  notify(ctx, "已排队,将在当前轮次结束后保存到主对话。", "info");
2126
2523
  setOverlayStatus("已排队,将在当前轮次结束后保存到主对话。", ctx);
2127
2524
  } else {
2128
- setOverlayStatus("Ready for a follow-up. Hidden BTW thread updated.", ctx);
2525
+ setOverlayStatus(
2526
+ "Ready for a follow-up. Hidden BTW thread updated.",
2527
+ ctx,
2528
+ );
2129
2529
  }
2130
2530
  } catch (error) {
2131
- const errorMessage = error instanceof Error ? error.message : String(error);
2531
+ const errorMessage =
2532
+ error instanceof Error ? error.message : String(error);
2132
2533
  setTranscriptFailure(transcriptState, errorMessage);
2133
- setOverlayStatus("Request failed. Thread preserved for retry or follow-up.", ctx);
2534
+ setOverlayStatus(
2535
+ "Request failed. Thread preserved for retry or follow-up.",
2536
+ ctx,
2537
+ );
2134
2538
  notify(ctx, errorMessage, "error");
2135
2539
  await disposeBtwSession();
2136
2540
  } finally {
@@ -2139,15 +2543,23 @@ export default function (pi: ExtensionAPI) {
2139
2543
  }
2140
2544
 
2141
2545
  function getPendingThreadForHandoff(): BtwHandoffExchange[] {
2142
- return pendingThread.map((entry) => ({ user: entry.question, assistant: entry.answer }));
2143
- }
2144
-
2145
- async function getBtwHandoffThread(
2146
- ctx: ExtensionCommandContext,
2147
- ): Promise<{ sessionRuntime: BtwSessionRuntime | null; thread: BtwHandoffExchange[] }> {
2148
- const sessionRuntime = activeBtwSession ?? (await ensureBtwSession(ctx, pendingMode));
2149
- const thread = sessionRuntime ? extractBtwHandoffThread(sessionRuntime) : [];
2150
- const resolvedThread = thread.length > 0 ? thread : getPendingThreadForHandoff();
2546
+ return pendingThread.map((entry) => ({
2547
+ user: entry.question,
2548
+ assistant: entry.answer,
2549
+ }));
2550
+ }
2551
+
2552
+ async function getBtwHandoffThread(ctx: ExtensionCommandContext): Promise<{
2553
+ sessionRuntime: BtwSessionRuntime | null;
2554
+ thread: BtwHandoffExchange[];
2555
+ }> {
2556
+ const sessionRuntime =
2557
+ activeBtwSession ?? (await ensureBtwSession(ctx, pendingMode));
2558
+ const thread = sessionRuntime
2559
+ ? extractBtwHandoffThread(sessionRuntime)
2560
+ : [];
2561
+ const resolvedThread =
2562
+ thread.length > 0 ? thread : getPendingThreadForHandoff();
2151
2563
 
2152
2564
  if (resolvedThread.length === 0) {
2153
2565
  throw new Error("无可用 BTW 对话用于交接。");
@@ -2156,7 +2568,10 @@ export default function (pi: ExtensionAPI) {
2156
2568
  return { sessionRuntime, thread: resolvedThread };
2157
2569
  }
2158
2570
 
2159
- async function summarizeThread(ctx: ExtensionCommandContext, thread: BtwHandoffExchange[]): Promise<string> {
2571
+ async function summarizeThread(
2572
+ ctx: ExtensionCommandContext,
2573
+ thread: BtwHandoffExchange[],
2574
+ ): Promise<string> {
2160
2575
  const settings = await resolveBtwSettings(ctx, true);
2161
2576
  const model = settings.model;
2162
2577
  if (!model) {
@@ -2165,16 +2580,21 @@ export default function (pi: ExtensionAPI) {
2165
2580
 
2166
2581
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
2167
2582
  if (!auth.ok || !auth.apiKey) {
2168
- throw new Error(auth.ok ? `No credentials available for ${model.provider}/${model.id}.` : auth.error);
2583
+ throw new Error(
2584
+ auth.ok
2585
+ ? `No credentials available for ${model.provider}/${model.id}.`
2586
+ : auth.error,
2587
+ );
2169
2588
  }
2170
2589
 
2171
2590
  const { session } = await createAgentSession({
2172
2591
  sessionManager: SessionManager.inMemory(),
2173
2592
  model,
2174
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
2175
- thinkingLevel: "off",
2176
- tools: [],
2177
- resourceLoader: createBtwResourceLoader(ctx, [BTW_SUMMARIZE_SYSTEM_PROMPT]),
2593
+ // pi 1.1.0: 不传 modelRuntime — 让 pi 用默认 runtime
2594
+ // (Sprint 1 桥接 ctx.modelRegistry → ModelRuntime 是错的, ModelRegistry 缺 checkAuth)
2595
+ resourceLoader: createBtwResourceLoader(ctx, [
2596
+ BTW_SUMMARIZE_SYSTEM_PROMPT,
2597
+ ]),
2178
2598
  });
2179
2599
 
2180
2600
  try {
@@ -2185,7 +2605,9 @@ export default function (pi: ExtensionAPI) {
2185
2605
  throw new Error("BTW summarize finished without a response.");
2186
2606
  }
2187
2607
  if (response.stopReason === "error") {
2188
- throw new Error(response.errorMessage || "Failed to summarize BTW thread.");
2608
+ throw new Error(
2609
+ response.errorMessage || "Failed to summarize BTW thread.",
2610
+ );
2189
2611
  }
2190
2612
  if (response.stopReason === "aborted") {
2191
2613
  throw new Error("BTW summarize aborted.");
@@ -2197,13 +2619,19 @@ export default function (pi: ExtensionAPI) {
2197
2619
  await session.abort();
2198
2620
  } catch (error) {
2199
2621
  // ✅ 改进错误处理:记录错误而不忽略
2200
- console.warn('[btw] Session abort failed during summarize shutdown:', error);
2622
+ console.warn(
2623
+ "[btw] Session abort failed during summarize shutdown:",
2624
+ error,
2625
+ );
2201
2626
  }
2202
2627
  session.dispose();
2203
2628
  }
2204
2629
  }
2205
2630
 
2206
- function sendThreadToMain(ctx: ExtensionCommandContext, content: string): void {
2631
+ function sendThreadToMain(
2632
+ ctx: ExtensionCommandContext,
2633
+ content: string,
2634
+ ): void {
2207
2635
  if (ctx.isIdle()) {
2208
2636
  pi.sendUserMessage(content);
2209
2637
  } else {
@@ -2211,37 +2639,45 @@ export default function (pi: ExtensionAPI) {
2211
2639
  }
2212
2640
  }
2213
2641
 
2214
- pi.registerMessageRenderer(BTW_MESSAGE_TYPE, (message, { expanded }, theme) => {
2215
- const details = message.details as BtwDetails | undefined;
2216
- const content = typeof message.content === "string" ? message.content : "[non-text btw message]";
2217
- const lines = [theme.fg("accent", theme.bold("[BTW]")), content];
2642
+ pi.registerMessageRenderer(
2643
+ BTW_MESSAGE_TYPE,
2644
+ (message, { expanded }, theme) => {
2645
+ const details = message.details as BtwDetails | undefined;
2646
+ const content =
2647
+ typeof message.content === "string"
2648
+ ? message.content
2649
+ : "[non-text btw message]";
2650
+ const lines = [theme.fg("accent", theme.bold("[BTW]")), content];
2218
2651
 
2219
- if (expanded && details) {
2220
- lines.push(
2221
- theme.fg(
2222
- "dim",
2223
- `model: ${details.provider}/${details.model} (${details.api ?? "openai-responses"}) · thinking: ${details.thinkingLevel}`,
2224
- ),
2225
- );
2226
-
2227
- if (details.usage) {
2652
+ if (expanded && details) {
2228
2653
  lines.push(
2229
2654
  theme.fg(
2230
2655
  "dim",
2231
- `tokens: in ${details.usage.input} · out ${details.usage.output} · total ${details.usage.totalTokens}`,
2656
+ `model: ${details.provider}/${details.model} (${details.api ?? "openai-responses"}) · thinking: ${details.thinkingLevel}`,
2232
2657
  ),
2233
2658
  );
2659
+
2660
+ if (details.usage) {
2661
+ lines.push(
2662
+ theme.fg(
2663
+ "dim",
2664
+ `tokens: in ${details.usage.input} · out ${details.usage.output} · total ${details.usage.totalTokens}`,
2665
+ ),
2666
+ );
2667
+ }
2234
2668
  }
2235
- }
2236
2669
 
2237
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
2238
- box.addChild(new Text(lines.join("\n"), 0, 0));
2239
- return box;
2240
- });
2670
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
2671
+ box.addChild(new Text(lines.join("\n"), 0, 0));
2672
+ return box;
2673
+ },
2674
+ );
2241
2675
 
2242
2676
  pi.on("context", async (event) => {
2243
2677
  return {
2244
- messages: event.messages.filter((message) => !isVisibleBtwMessage(message)),
2678
+ messages: event.messages.filter(
2679
+ (message) => !isVisibleBtwMessage(message),
2680
+ ),
2245
2681
  };
2246
2682
  });
2247
2683
 
@@ -2268,7 +2704,8 @@ export default function (pi: ExtensionAPI) {
2268
2704
  }
2269
2705
 
2270
2706
  pi.registerCommand("btw", {
2271
- description: "侧边对话。默认带主会话上下文;用 /btw:tangent 开启无上下文模式。加 --save 保存到主对话。",
2707
+ description:
2708
+ "侧边对话。默认带主会话上下文;用 /btw:tangent 开启无上下文模式。加 --save 保存到主对话。",
2272
2709
  handler: async (args, ctx) => {
2273
2710
  await dispatchBtwCommand("btw", args, ctx);
2274
2711
  },