@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.
- package/bin/cli.js +97 -69
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +619 -182
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +0 -5
- package/config/agent/extensions/custom.ts +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- package/config/agent/settings.json +15 -14
- package/config/agent/skills/hello/SKILL.md +1 -1
- package/config/agent/skills/refactor/SKILL.md +40 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +6 -7
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +21 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +27 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +27 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
- package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
- package/config/agent/extensions/aicode-snake.ts +0 -354
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
- /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
- /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
- /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
- /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 {
|
|
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> & {
|
|
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 {
|
|
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 =
|
|
67
|
-
|
|
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<
|
|
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
|
-
| {
|
|
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
|
-
| {
|
|
130
|
-
|
|
131
|
-
|
|
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<
|
|
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: {
|
|
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(
|
|
176
|
-
|
|
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(
|
|
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 = {
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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
|
-
...(
|
|
289
|
-
(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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(
|
|
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, {
|
|
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(
|
|
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) =>
|
|
563
|
+
(entry) =>
|
|
564
|
+
entry.turnId === resolvedTurnId &&
|
|
565
|
+
entry.type === "turn-boundary" &&
|
|
566
|
+
entry.phase === "end",
|
|
462
567
|
);
|
|
463
568
|
if (!hasEndBoundary) {
|
|
464
|
-
appendTranscriptEntry(state, {
|
|
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 (
|
|
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(
|
|
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(
|
|
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: {
|
|
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(
|
|
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(
|
|
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<
|
|
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<
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
857
|
+
upsertTranscriptTextEntry(
|
|
858
|
+
state,
|
|
859
|
+
turnId,
|
|
860
|
+
"assistant-text",
|
|
861
|
+
answer,
|
|
862
|
+
streaming,
|
|
863
|
+
);
|
|
714
864
|
}
|
|
715
865
|
}
|
|
716
866
|
|
|
717
|
-
function applyTranscriptEvent(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
980
|
+
upsertTranscriptTextEntry(
|
|
981
|
+
state,
|
|
982
|
+
turnId,
|
|
983
|
+
"thinking",
|
|
984
|
+
details.thinking,
|
|
985
|
+
false,
|
|
986
|
+
);
|
|
807
987
|
}
|
|
808
|
-
upsertTranscriptTextEntry(
|
|
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(
|
|
813
|
-
|
|
814
|
-
|
|
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" ||
|
|
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(
|
|
1025
|
+
return entries.filter(
|
|
1026
|
+
(entry) => entry.type === "assistant-text" && !entry.streaming,
|
|
1027
|
+
).length;
|
|
828
1028
|
}
|
|
829
1029
|
|
|
830
|
-
function buildOverlayTranscript(
|
|
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(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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: {
|
|
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
|
|
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
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
975
|
-
|
|
976
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
1694
|
+
console.warn("[btw] Unsubscribe failed during session removal:", error);
|
|
1389
1695
|
}
|
|
1390
1696
|
}
|
|
1391
1697
|
|
|
1392
|
-
function clearBtwSessionSubscriptions(
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
1441
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
? {
|
|
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
|
-
? {
|
|
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(
|
|
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
|
-
|
|
1615
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2282
|
+
function parseOverlayBtwCommand(
|
|
2283
|
+
value: string,
|
|
2284
|
+
): { name: string; args: string } | null {
|
|
1913
2285
|
const trimmed = value.trim();
|
|
1914
|
-
const match = trimmed.match(
|
|
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(
|
|
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(
|
|
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 = (
|
|
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 = (
|
|
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 })
|
|
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
|
|
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(
|
|
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 =
|
|
2489
|
+
const completedTurnId =
|
|
2490
|
+
transcriptState.lastTurnId ?? transcriptState.currentTurnId;
|
|
2100
2491
|
const streamedThinking =
|
|
2101
|
-
completedTurnId !== null
|
|
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(
|
|
2525
|
+
setOverlayStatus(
|
|
2526
|
+
"Ready for a follow-up. Hidden BTW thread updated.",
|
|
2527
|
+
ctx,
|
|
2528
|
+
);
|
|
2129
2529
|
}
|
|
2130
2530
|
} catch (error) {
|
|
2131
|
-
const errorMessage =
|
|
2531
|
+
const errorMessage =
|
|
2532
|
+
error instanceof Error ? error.message : String(error);
|
|
2132
2533
|
setTranscriptFailure(transcriptState, errorMessage);
|
|
2133
|
-
setOverlayStatus(
|
|
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) => ({
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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(
|
|
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:
|
|
2707
|
+
description:
|
|
2708
|
+
"侧边对话。默认带主会话上下文;用 /btw:tangent 开启无上下文模式。加 --save 保存到主对话。",
|
|
2272
2709
|
handler: async (args, ctx) => {
|
|
2273
2710
|
await dispatchBtwCommand("btw", args, ctx);
|
|
2274
2711
|
},
|