@ia-ccun/code-agent-cli 0.0.56-beta.2 → 0.0.57-beta.1
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/config/agent/extensions/aicode-grillMe.ts +102 -28
- package/config/agent/extensions/aicode-plan.ts +98 -41
- package/config/agent/extensions/aicode-todo-progress.ts +2 -1
- package/config/agent/extensions/custom-footer.ts +3 -2
- package/config/agent/extensions/review.ts +6 -1
- package/config/agent/mcp.json +4 -0
- package/config/agent/settings.json +19 -21
- package/config/agent/themes/catppuccin-mocha.json +1 -1
- package/config/changelog.md +19 -66
- package/package.json +5 -5
- package/scripts/regression/lib/common.sh +31 -12
- package/scripts/regression/results/.last_pid +1 -1
- package/scripts/regression/results/.last_workdir +1 -1
- package/scripts/regression/results/02-extension-loading.err.log +0 -0
- package/scripts/regression/results/02-extension-loading.log +15 -20
- package/scripts/regression/results/03-prompt-stream.err.log +0 -0
- package/scripts/regression/results/03-prompt-stream.log +12 -16
- package/scripts/regression/results/03-rpc-protocol.err.log +0 -0
- package/scripts/regression/results/03-rpc-protocol.log +19 -21
- package/scripts/regression/results/04-slash-commands-and-skills.err.log +0 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +12 -11
- package/scripts/regression/results/05-tool-execution.err.log +0 -0
- package/scripts/regression/results/05-tool-execution.log +5 -5
- package/scripts/regression/results/06-pi-new-features.err.log +0 -0
- package/scripts/regression/results/06-pi-new-features.log +25 -18
- package/scripts/regression/results/repro.log +4 -0
|
@@ -37,23 +37,43 @@ interface GrillState {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const RecordTurnParams = Type.Object({
|
|
40
|
-
question: Type.String({
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
question: Type.String({
|
|
41
|
+
description: "The exact question asked, one question only.",
|
|
42
|
+
}),
|
|
43
|
+
recommendedAnswer: Type.String({
|
|
44
|
+
description: "The assistant's recommended answer to the question.",
|
|
45
|
+
}),
|
|
46
|
+
userAnswer: Type.Optional(
|
|
47
|
+
Type.String({ description: "The user's answer, if already provided." }),
|
|
48
|
+
),
|
|
43
49
|
decisionStatus: Type.Union([
|
|
44
50
|
Type.Literal("resolved"),
|
|
45
51
|
Type.Literal("open"),
|
|
46
52
|
Type.Literal("needs-codebase-check"),
|
|
47
53
|
]),
|
|
48
|
-
notes: Type.Optional(
|
|
54
|
+
notes: Type.Optional(
|
|
55
|
+
Type.String({
|
|
56
|
+
description: "Short rationale, dependency, or follow-up notes.",
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
49
59
|
});
|
|
50
60
|
|
|
51
61
|
const SaveResultsParams = Type.Object({
|
|
52
|
-
path: Type.Optional(
|
|
53
|
-
|
|
62
|
+
path: Type.Optional(
|
|
63
|
+
Type.String({
|
|
64
|
+
description: "Relative output path. Defaults to GRILL-ME.md.",
|
|
65
|
+
}),
|
|
66
|
+
),
|
|
67
|
+
summary: Type.Optional(
|
|
68
|
+
Type.String({
|
|
69
|
+
description: "Optional current shared-understanding summary.",
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
54
72
|
agreedDecisions: Type.Optional(Type.Array(Type.String())),
|
|
55
73
|
openRisks: Type.Optional(Type.Array(Type.String())),
|
|
56
|
-
nextDecisionNeeded: Type.Optional(
|
|
74
|
+
nextDecisionNeeded: Type.Optional(
|
|
75
|
+
Type.String({ description: "Which decision is blocking progress next." }),
|
|
76
|
+
),
|
|
57
77
|
});
|
|
58
78
|
|
|
59
79
|
function stateDir(cwd: string): string {
|
|
@@ -74,12 +94,21 @@ async function readState(cwd: string): Promise<GrillState | undefined> {
|
|
|
74
94
|
|
|
75
95
|
async function writeState(cwd: string, state: GrillState): Promise<void> {
|
|
76
96
|
await mkdir(stateDir(cwd), { recursive: true });
|
|
77
|
-
await writeFile(
|
|
97
|
+
await writeFile(
|
|
98
|
+
statePath(cwd),
|
|
99
|
+
JSON.stringify(state, null, 2) + "\n",
|
|
100
|
+
"utf8",
|
|
101
|
+
);
|
|
78
102
|
}
|
|
79
103
|
|
|
80
104
|
function renderMarkdown(
|
|
81
105
|
state: GrillState,
|
|
82
|
-
extra: {
|
|
106
|
+
extra: {
|
|
107
|
+
summary?: string;
|
|
108
|
+
agreedDecisions?: string[];
|
|
109
|
+
openRisks?: string[];
|
|
110
|
+
nextDecisionNeeded?: string;
|
|
111
|
+
},
|
|
83
112
|
): string {
|
|
84
113
|
const lines: string[] = [];
|
|
85
114
|
lines.push("# Grill Me Results", "");
|
|
@@ -94,20 +123,41 @@ function renderMarkdown(
|
|
|
94
123
|
} else {
|
|
95
124
|
state.turns.forEach((turn, index) => {
|
|
96
125
|
lines.push(`### ${index + 1}. ${turn.question}`, "");
|
|
97
|
-
lines.push(
|
|
98
|
-
|
|
126
|
+
lines.push(
|
|
127
|
+
`**Recommended answer:** ${turn.recommendedAnswer || "_(none)_"}`,
|
|
128
|
+
"",
|
|
129
|
+
);
|
|
130
|
+
lines.push(
|
|
131
|
+
`**User answer:** ${turn.userAnswer || "_(not recorded)_"}`,
|
|
132
|
+
"",
|
|
133
|
+
);
|
|
99
134
|
lines.push(`**Status:** ${turn.decisionStatus}`, "");
|
|
100
135
|
if (turn.notes?.trim()) lines.push(`**Notes:** ${turn.notes.trim()}`, "");
|
|
101
136
|
});
|
|
102
137
|
}
|
|
103
138
|
if (extra.agreedDecisions?.length) {
|
|
104
|
-
lines.push(
|
|
139
|
+
lines.push(
|
|
140
|
+
"## Agreed Decisions",
|
|
141
|
+
"",
|
|
142
|
+
...extra.agreedDecisions.map((d) => `- ${d}`),
|
|
143
|
+
"",
|
|
144
|
+
);
|
|
105
145
|
}
|
|
106
146
|
if (extra.openRisks?.length) {
|
|
107
|
-
lines.push(
|
|
147
|
+
lines.push(
|
|
148
|
+
"## Open Risks",
|
|
149
|
+
"",
|
|
150
|
+
...extra.openRisks.map((r) => `- ${r}`),
|
|
151
|
+
"",
|
|
152
|
+
);
|
|
108
153
|
}
|
|
109
154
|
if (extra.nextDecisionNeeded?.trim()) {
|
|
110
|
-
lines.push(
|
|
155
|
+
lines.push(
|
|
156
|
+
"## Next Decision Needed",
|
|
157
|
+
"",
|
|
158
|
+
extra.nextDecisionNeeded.trim(),
|
|
159
|
+
"",
|
|
160
|
+
);
|
|
111
161
|
}
|
|
112
162
|
return lines.join("\n");
|
|
113
163
|
}
|
|
@@ -117,16 +167,21 @@ function safeOutputPath(cwd: string, input?: string): string {
|
|
|
117
167
|
const absolute = resolve(cwd, requested);
|
|
118
168
|
const root = resolve(cwd);
|
|
119
169
|
if (absolute !== root && !absolute.startsWith(root + "/")) {
|
|
120
|
-
throw new Error(
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Refusing to write outside project directory: ${requested}`,
|
|
172
|
+
);
|
|
121
173
|
}
|
|
122
174
|
return absolute;
|
|
123
175
|
}
|
|
124
176
|
|
|
125
177
|
export default function grillMeExtension(pi: ExtensionAPI) {
|
|
126
178
|
pi.registerCommand("grill-me", {
|
|
127
|
-
description:
|
|
179
|
+
description:
|
|
180
|
+
"Start a deterministic design interview and save results to Markdown",
|
|
128
181
|
handler: async (args, ctx) => {
|
|
129
|
-
const plan =
|
|
182
|
+
const plan =
|
|
183
|
+
args.trim() ||
|
|
184
|
+
"(No plan supplied yet. Ask the user to paste or describe the plan first.)";
|
|
130
185
|
const state: GrillState = {
|
|
131
186
|
createdAt: new Date().toISOString(),
|
|
132
187
|
updatedAt: new Date().toISOString(),
|
|
@@ -137,17 +192,23 @@ export default function grillMeExtension(pi: ExtensionAPI) {
|
|
|
137
192
|
await writeState(ctx.cwd, state);
|
|
138
193
|
ctx.ui.notify(`Grill session initialized: ${statePath(ctx.cwd)}`, "info");
|
|
139
194
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
195
|
+
const grillPrompt = `开始 /grill-me 访谈,计划如下:\n\n${plan}\n\n规则:\n- 无情地质问这个计划的每个细节,直到我们达成共识。\n- 沿着设计树的每个分支推进,逐个解决决策之间的依赖关系。\n- 每次只问一个问题。\n- 对每个问题,提供你的推荐答案,用户可以使用A B C 的方式快速选择。\n- 如果问题可以通过探索代码库来回答,则先探索代码库而不是直接提问。\n- 每个问答决策被捕获后,使用 grill_record_turn 记录。\n- 当达成足够共识或我要求停止/保存时,使用 grill_save_results 将结果保存为项目目录中的 .aicode-cli文件夹下的 Markdown 文件。`;
|
|
196
|
+
// pi 1.4.0: agent busy 时 sendUserMessage 必须带 deliverAs
|
|
197
|
+
if (ctx.isIdle()) {
|
|
198
|
+
pi.sendUserMessage(grillPrompt);
|
|
199
|
+
} else {
|
|
200
|
+
pi.sendUserMessage(grillPrompt, { deliverAs: "followUp" });
|
|
201
|
+
}
|
|
143
202
|
},
|
|
144
203
|
});
|
|
145
204
|
|
|
146
205
|
pi.registerTool({
|
|
147
206
|
name: "grill_record_turn",
|
|
148
207
|
label: "Grill Record Turn",
|
|
149
|
-
description:
|
|
150
|
-
|
|
208
|
+
description:
|
|
209
|
+
"Record one /grill-me question, recommended answer, user answer, and decision status in project state.",
|
|
210
|
+
promptSnippet:
|
|
211
|
+
"Record structured progress for an active /grill-me design interview",
|
|
151
212
|
promptGuidelines: [
|
|
152
213
|
"Use grill_record_turn after each /grill-me interview question is answered or resolved from codebase exploration.",
|
|
153
214
|
"Do not use grill_record_turn for more than one question at a time.",
|
|
@@ -165,7 +226,9 @@ export default function grillMeExtension(pi: ExtensionAPI) {
|
|
|
165
226
|
state.updatedAt = new Date().toISOString();
|
|
166
227
|
await writeState(ctx.cwd, state);
|
|
167
228
|
return {
|
|
168
|
-
content: [
|
|
229
|
+
content: [
|
|
230
|
+
{ type: "text", text: `Recorded grill turn #${state.turns.length}` },
|
|
231
|
+
],
|
|
169
232
|
details: { path: statePath(ctx.cwd), count: state.turns.length },
|
|
170
233
|
};
|
|
171
234
|
},
|
|
@@ -174,15 +237,24 @@ export default function grillMeExtension(pi: ExtensionAPI) {
|
|
|
174
237
|
pi.registerTool({
|
|
175
238
|
name: "grill_save_results",
|
|
176
239
|
label: "Grill Save Results",
|
|
177
|
-
description:
|
|
178
|
-
|
|
179
|
-
|
|
240
|
+
description:
|
|
241
|
+
"Save the active /grill-me interview state as a Markdown file inside the project directory.",
|
|
242
|
+
promptSnippet:
|
|
243
|
+
"Save /grill-me interview decisions and risks to Markdown in the project directory",
|
|
244
|
+
promptGuidelines: [
|
|
245
|
+
"Use grill_save_results when the /grill-me interview is complete or the user asks to save/stop.",
|
|
246
|
+
],
|
|
180
247
|
parameters: SaveResultsParams,
|
|
181
248
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
182
249
|
const state = await readState(ctx.cwd);
|
|
183
250
|
if (!state) {
|
|
184
251
|
return {
|
|
185
|
-
content: [
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: "No active /grill-me state found. Run /grill-me first.",
|
|
256
|
+
},
|
|
257
|
+
],
|
|
186
258
|
isError: true,
|
|
187
259
|
details: { path: statePath(ctx.cwd) },
|
|
188
260
|
};
|
|
@@ -190,7 +262,9 @@ export default function grillMeExtension(pi: ExtensionAPI) {
|
|
|
190
262
|
const outputPath = safeOutputPath(ctx.cwd, params.path);
|
|
191
263
|
await writeFile(outputPath, renderMarkdown(state, params), "utf8");
|
|
192
264
|
return {
|
|
193
|
-
content: [
|
|
265
|
+
content: [
|
|
266
|
+
{ type: "text", text: `Saved grill results to ${outputPath}` },
|
|
267
|
+
],
|
|
194
268
|
details: { path: outputPath, turns: state.turns.length },
|
|
195
269
|
};
|
|
196
270
|
},
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import {
|
|
6
|
+
Box,
|
|
7
|
+
Spacer,
|
|
8
|
+
Text,
|
|
9
|
+
Editor,
|
|
10
|
+
type EditorTheme,
|
|
11
|
+
Key,
|
|
12
|
+
matchesKey,
|
|
13
|
+
truncateToWidth,
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
3
15
|
|
|
4
16
|
const STATUS_KEY = "aicode-plan";
|
|
5
17
|
const TODO_WIDGET_KEY = "aicode-plan-todos";
|
|
@@ -171,7 +183,9 @@ function isSafeReadOnlyCommand(command: string): boolean {
|
|
|
171
183
|
];
|
|
172
184
|
|
|
173
185
|
// 使用标准化后的命令进行检查(已移除错误重定向)
|
|
174
|
-
const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) =>
|
|
186
|
+
const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) =>
|
|
187
|
+
pattern.test(normalizedCommand),
|
|
188
|
+
);
|
|
175
189
|
if (isDestructive) return false;
|
|
176
190
|
return SAFE_PATTERNS.some((pattern) => pattern.test(normalizedCommand));
|
|
177
191
|
}
|
|
@@ -288,14 +302,17 @@ function getAssistantTextFromMessage(message: unknown): string {
|
|
|
288
302
|
|
|
289
303
|
// ============== UI 组件 ==============
|
|
290
304
|
|
|
291
|
-
const ACTION_OPTIONS: ReadonlyArray<{ label: string; value: PlanNextAction }> =
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
305
|
+
const ACTION_OPTIONS: ReadonlyArray<{ label: string; value: PlanNextAction }> =
|
|
306
|
+
[
|
|
307
|
+
{ label: "批准并立即执行", value: "approve" },
|
|
308
|
+
{ label: "从当前计划继续", value: "continue" },
|
|
309
|
+
{ label: "重新生成计划", value: "regenerate" },
|
|
310
|
+
{ label: "退出计划模式", value: "exit" },
|
|
311
|
+
];
|
|
297
312
|
|
|
298
|
-
const CONTINUE_OPTION_INDEX = ACTION_OPTIONS.findIndex(
|
|
313
|
+
const CONTINUE_OPTION_INDEX = ACTION_OPTIONS.findIndex(
|
|
314
|
+
(option) => option.value === "continue",
|
|
315
|
+
);
|
|
299
316
|
|
|
300
317
|
function normalizeContinueNote(input: string): string {
|
|
301
318
|
return input.replace(/\s+/g, " ").trim();
|
|
@@ -350,7 +367,8 @@ async function selectPlanNextActionWithInlineNote(
|
|
|
350
367
|
tui.requestRender();
|
|
351
368
|
};
|
|
352
369
|
|
|
353
|
-
const getNormalizedContinueNote = (): string =>
|
|
370
|
+
const getNormalizedContinueNote = (): string =>
|
|
371
|
+
normalizeContinueNote(continueNote);
|
|
354
372
|
|
|
355
373
|
const openContinueEditor = () => {
|
|
356
374
|
if (cursorIndex !== CONTINUE_OPTION_INDEX) {
|
|
@@ -388,14 +406,19 @@ async function selectPlanNextActionWithInlineNote(
|
|
|
388
406
|
}
|
|
389
407
|
|
|
390
408
|
const renderedLines: string[] = [];
|
|
391
|
-
const addLine = (line: string) =>
|
|
409
|
+
const addLine = (line: string) =>
|
|
410
|
+
renderedLines.push(truncateToWidth(line, width));
|
|
392
411
|
|
|
393
412
|
addLine(theme.fg("accent", "─".repeat(width)));
|
|
394
413
|
addLine(theme.fg("text", " 计划模式:下一步操作"));
|
|
395
414
|
renderedLines.push("");
|
|
396
415
|
|
|
397
416
|
const maxInlineLabelLength = Math.max(20, width - 8);
|
|
398
|
-
for (
|
|
417
|
+
for (
|
|
418
|
+
let optionIndex = 0;
|
|
419
|
+
optionIndex < ACTION_OPTIONS.length;
|
|
420
|
+
optionIndex++
|
|
421
|
+
) {
|
|
399
422
|
const option = ACTION_OPTIONS[optionIndex];
|
|
400
423
|
const isCursorOption = optionIndex === cursorIndex;
|
|
401
424
|
const isContinueOption = optionIndex === CONTINUE_OPTION_INDEX;
|
|
@@ -410,7 +433,9 @@ async function selectPlanNextActionWithInlineNote(
|
|
|
410
433
|
const cursorPrefix = isCursorOption ? theme.fg("accent", "→ ") : " ";
|
|
411
434
|
const bullet = isCursorOption ? "●" : "○";
|
|
412
435
|
const optionColor = isCursorOption ? "accent" : "text";
|
|
413
|
-
addLine(
|
|
436
|
+
addLine(
|
|
437
|
+
`${cursorPrefix}${theme.fg(optionColor, `${bullet} ${optionLabel}`)}`,
|
|
438
|
+
);
|
|
414
439
|
}
|
|
415
440
|
|
|
416
441
|
renderedLines.push("");
|
|
@@ -418,9 +443,13 @@ async function selectPlanNextActionWithInlineNote(
|
|
|
418
443
|
addLine(theme.fg("dim", " 正在输入备注 • Enter 继续 • Tab/Esc 停止编辑"));
|
|
419
444
|
} else if (cursorIndex === CONTINUE_OPTION_INDEX) {
|
|
420
445
|
if (getNormalizedContinueNote().length > 0) {
|
|
421
|
-
addLine(
|
|
446
|
+
addLine(
|
|
447
|
+
theme.fg("dim", " ↑↓ 移动 • Enter 继续 • Tab 编辑备注 • Esc 取消"),
|
|
448
|
+
);
|
|
422
449
|
} else {
|
|
423
|
-
addLine(
|
|
450
|
+
addLine(
|
|
451
|
+
theme.fg("dim", " ↑↓ 移动 • Enter 继续 • Tab 添加备注 • Esc 取消"),
|
|
452
|
+
);
|
|
424
453
|
}
|
|
425
454
|
} else {
|
|
426
455
|
addLine(theme.fg("dim", " ↑↓ 移动 • Enter 选择 • Esc 取消"));
|
|
@@ -501,7 +530,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
501
530
|
let restoreTools: string[] | null = null;
|
|
502
531
|
let todoItems: TodoItem[] = [];
|
|
503
532
|
|
|
504
|
-
const getAllToolNames = (): string[] =>
|
|
533
|
+
const getAllToolNames = (): string[] =>
|
|
534
|
+
pi.getAllTools().map((tool) => tool.name);
|
|
505
535
|
|
|
506
536
|
const getPlanTools = (): string[] => {
|
|
507
537
|
const available = new Set(getAllToolNames());
|
|
@@ -510,7 +540,9 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
510
540
|
return [...planTools];
|
|
511
541
|
}
|
|
512
542
|
|
|
513
|
-
const fallback = pi
|
|
543
|
+
const fallback = pi
|
|
544
|
+
.getActiveTools()
|
|
545
|
+
.filter((tool) => !WRITE_LIKE_TOOLS.has(tool));
|
|
514
546
|
return [...new Set(fallback)];
|
|
515
547
|
};
|
|
516
548
|
|
|
@@ -564,7 +596,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
564
596
|
}
|
|
565
597
|
|
|
566
598
|
const statusText = planModeEnabled
|
|
567
|
-
? ctx.ui.theme.fg("warning", "⏸ 计划") +
|
|
599
|
+
? ctx.ui.theme.fg("warning", "⏸ 计划") +
|
|
600
|
+
ctx.ui.theme.fg("dim", " | plan (Ctrl+Alt+P)")
|
|
568
601
|
: undefined;
|
|
569
602
|
ctx.ui.setStatus(STATUS_KEY, statusText);
|
|
570
603
|
updateTodoWidget(ctx);
|
|
@@ -626,7 +659,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
626
659
|
|
|
627
660
|
// 注册命令
|
|
628
661
|
pi.registerCommand("plan", {
|
|
629
|
-
description:
|
|
662
|
+
description:
|
|
663
|
+
"启用只读计划模式。用法:/plan, /plan on, /plan off, /plan status, /plan <任务>",
|
|
630
664
|
handler: async (args, ctx) => {
|
|
631
665
|
const raw = args.trim();
|
|
632
666
|
|
|
@@ -674,7 +708,13 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
674
708
|
enterPlanMode(ctx);
|
|
675
709
|
}
|
|
676
710
|
|
|
677
|
-
pi.sendUserMessage
|
|
711
|
+
// pi 1.4.0: agent busy 时 sendUserMessage 必须带 deliverAs, 否则抛
|
|
712
|
+
// "Agent is already processing"
|
|
713
|
+
if (ctx.isIdle()) {
|
|
714
|
+
pi.sendUserMessage(raw);
|
|
715
|
+
} else {
|
|
716
|
+
pi.sendUserMessage(raw, { deliverAs: "followUp" });
|
|
717
|
+
}
|
|
678
718
|
},
|
|
679
719
|
});
|
|
680
720
|
|
|
@@ -699,7 +739,7 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
699
739
|
pi.registerShortcut("ctrl+alt+p", {
|
|
700
740
|
description: "切换计划模式",
|
|
701
741
|
handler: async (ctx) => {
|
|
702
|
-
const wasEnabled = planModeEnabled;
|
|
742
|
+
const wasEnabled = planModeEnabled; // 保存当前状态
|
|
703
743
|
if (planModeEnabled) {
|
|
704
744
|
exitPlanMode(ctx, "计划模式已禁用。返回直接执行模式。", {
|
|
705
745
|
resetProgress: true,
|
|
@@ -714,21 +754,33 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
714
754
|
|
|
715
755
|
// 注册消息渲染器
|
|
716
756
|
pi.registerMessageRenderer(CUSTOM_TYPE, (message, { expanded }, theme) => {
|
|
717
|
-
const details = message.details as
|
|
757
|
+
const details = message.details as
|
|
758
|
+
| { kind?: string; message?: string }
|
|
759
|
+
| undefined;
|
|
718
760
|
const kind = details?.kind ?? "status";
|
|
719
761
|
const msg = details?.message ?? "";
|
|
720
762
|
const box = new Box(1, 1, (value) => theme.bg("customMessageBg", value));
|
|
721
|
-
box.addChild(
|
|
763
|
+
box.addChild(
|
|
764
|
+
new Text(theme.fg("customMessageLabel", theme.bold("📋 计划")), 0, 0),
|
|
765
|
+
);
|
|
722
766
|
box.addChild(new Spacer(1));
|
|
723
767
|
if (!expanded) {
|
|
724
|
-
box.addChild(
|
|
768
|
+
box.addChild(
|
|
769
|
+
new Text(
|
|
770
|
+
`${theme.fg("customMessageText", msg)} ${theme.fg("dim", "(ctrl+o 展开)")}`,
|
|
771
|
+
0,
|
|
772
|
+
0,
|
|
773
|
+
),
|
|
774
|
+
);
|
|
725
775
|
return box;
|
|
726
776
|
}
|
|
727
777
|
const lines = [
|
|
728
778
|
`${theme.fg("dim", "状态:")}${theme.fg("customMessageText", kind)}`,
|
|
729
779
|
];
|
|
730
780
|
if (msg) {
|
|
731
|
-
lines.push(
|
|
781
|
+
lines.push(
|
|
782
|
+
`${theme.fg("dim", "消息:")}${theme.fg("customMessageText", msg)}`,
|
|
783
|
+
);
|
|
732
784
|
}
|
|
733
785
|
box.addChild(new Text(lines.join("\n"), 0, 0));
|
|
734
786
|
return box;
|
|
@@ -840,12 +892,14 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
840
892
|
exitPlanMode(ctx, "计划已批准。进入直接执行模式进行实施。");
|
|
841
893
|
|
|
842
894
|
const firstOpenStep = todoItems.find((item) => !item.completed);
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
895
|
+
const triggerText = firstOpenStep
|
|
896
|
+
? `${EXECUTION_TRIGGER_PROMPT} 从步骤 ${firstOpenStep.step} 开始:${firstOpenStep.text}`
|
|
897
|
+
: EXECUTION_TRIGGER_PROMPT;
|
|
898
|
+
// pi 1.4.0: agent busy 时必须带 deliverAs
|
|
899
|
+
if (ctx.isIdle()) {
|
|
900
|
+
pi.sendUserMessage(triggerText);
|
|
847
901
|
} else {
|
|
848
|
-
pi.sendUserMessage(
|
|
902
|
+
pi.sendUserMessage(triggerText, { deliverAs: "followUp" });
|
|
849
903
|
}
|
|
850
904
|
return;
|
|
851
905
|
}
|
|
@@ -853,9 +907,13 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
853
907
|
if (selection.action === "regenerate") {
|
|
854
908
|
todoItems = [];
|
|
855
909
|
setStatus(ctx);
|
|
856
|
-
|
|
857
|
-
"从头重新生成计划。重新检查上下文并提供刷新后的 Plan: 部分。"
|
|
858
|
-
)
|
|
910
|
+
const regenText =
|
|
911
|
+
"从头重新生成计划。重新检查上下文并提供刷新后的 Plan: 部分。";
|
|
912
|
+
if (ctx.isIdle()) {
|
|
913
|
+
pi.sendUserMessage(regenText);
|
|
914
|
+
} else {
|
|
915
|
+
pi.sendUserMessage(regenText, { deliverAs: "followUp" });
|
|
916
|
+
}
|
|
859
917
|
return;
|
|
860
918
|
}
|
|
861
919
|
|
|
@@ -870,14 +928,13 @@ export default function aicodePlan(pi: ExtensionAPI) {
|
|
|
870
928
|
}
|
|
871
929
|
|
|
872
930
|
const firstOpenStep = todoItems.find((item) => !item.completed);
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
931
|
+
const contText = firstOpenStep
|
|
932
|
+
? `从提议的计划继续。用户备注:${continueNote}。聚焦步骤 ${firstOpenStep.step}: ${firstOpenStep.text}。优化文件、验证和风险分析。`
|
|
933
|
+
: `从提议的计划继续。用户备注:${continueNote}。无需重新生成完整计划,优化实现细节。`;
|
|
934
|
+
if (ctx.isIdle()) {
|
|
935
|
+
pi.sendUserMessage(contText);
|
|
877
936
|
} else {
|
|
878
|
-
pi.sendUserMessage(
|
|
879
|
-
`从提议的计划继续。用户备注:${continueNote}。无需重新生成完整计划,优化实现细节。`,
|
|
880
|
-
);
|
|
937
|
+
pi.sendUserMessage(contText, { deliverAs: "followUp" });
|
|
881
938
|
}
|
|
882
939
|
return;
|
|
883
940
|
}
|
|
@@ -425,7 +425,8 @@ export default function todoProgress(pi: ExtensionAPI) {
|
|
|
425
425
|
|
|
426
426
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
427
427
|
clear(ctx, state);
|
|
428
|
-
|
|
428
|
+
// pi 1.4.0: BeforeAgentStartEvent 是 union, custom run 没有 prompt
|
|
429
|
+
state.goal = fallbackGoalFromPrompt(event.messageType === "user" ? event.prompt : "");
|
|
429
430
|
persistState();
|
|
430
431
|
return { systemPrompt: event.systemPrompt + TODO_POLICY };
|
|
431
432
|
});
|
|
@@ -47,11 +47,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
47
47
|
// 缓存有效期 1 小时
|
|
48
48
|
if (cache.needsUpdate && Date.now() - cache.timestamp < 3600000) {
|
|
49
49
|
// 在输入框上方显示版本更新提示
|
|
50
|
-
const msg = `\x1b[38;2;243;139;168m⚠️ 发现新版本 v${cache.localVersion} → v${cache.latestVersion}\x1b[0m \x1b[
|
|
50
|
+
const msg = `\x1b[38;2;243;139;168m⚠️ 发现新版本 v${cache.localVersion} → v${cache.latestVersion}\x1b[0m \x1b[36m run npm install -g @ia-ccun/code-agent-cli\x1b[0m`;
|
|
51
51
|
ctx.ui.setWidget("version-update", [msg], { placement: "aboveEditor" });
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
} catch {
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore: cache read failure shouldn't break footer
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -1070,7 +1070,12 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
1070
1070
|
ctx.ui.notify(`Starting review: ${hint}${modeHint}`, "info");
|
|
1071
1071
|
|
|
1072
1072
|
// Send as a user message that triggers a turn
|
|
1073
|
-
pi.sendUserMessage
|
|
1073
|
+
// pi 1.4.0: agent busy 时 sendUserMessage 必须带 deliverAs
|
|
1074
|
+
if (ctx.isIdle()) {
|
|
1075
|
+
pi.sendUserMessage(fullPrompt);
|
|
1076
|
+
} else {
|
|
1077
|
+
pi.sendUserMessage(fullPrompt, { deliverAs: "followUp" });
|
|
1078
|
+
}
|
|
1074
1079
|
}
|
|
1075
1080
|
|
|
1076
1081
|
/**
|
package/config/agent/mcp.json
CHANGED
|
@@ -1,56 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"~/.aicode-cli/agent/extensions/bash-check.ts",
|
|
2
|
+
"quietStartup":true,
|
|
3
|
+
"defaultProvider": "aicode",
|
|
4
|
+
"defaultModel": "deepseek-v4-flash",
|
|
5
|
+
"theme":"light",
|
|
6
|
+
"extensions":[
|
|
8
7
|
"~/.aicode-cli/agent/extensions/context-monitor.ts",
|
|
9
8
|
"~/.aicode-cli/agent/extensions/context.ts",
|
|
10
9
|
"~/.aicode-cli/agent/extensions/custom-footer.ts",
|
|
11
10
|
"~/.aicode-cli/agent/extensions/custom.ts",
|
|
12
11
|
"~/.aicode-cli/agent/extensions/review.ts",
|
|
13
|
-
"~/.aicode-cli/agent/extensions/working-msg.ts"
|
|
14
|
-
"-extensions/aicode-pets.ts"
|
|
12
|
+
"~/.aicode-cli/agent/extensions/working-msg.ts"
|
|
15
13
|
],
|
|
16
14
|
"packages":[
|
|
17
|
-
"npm:pi-subagents
|
|
18
|
-
"npm:pi-rewind
|
|
19
|
-
"npm:pi-mcp-adapter
|
|
15
|
+
"npm:pi-subagents",
|
|
16
|
+
"npm:pi-rewind",
|
|
17
|
+
"npm:pi-mcp-adapter",
|
|
20
18
|
"npm:@juicesharp/rpiv-todo",
|
|
21
19
|
"npm:@juicesharp/rpiv-ask-user-question",
|
|
22
|
-
"npm:@samfp/pi-memory
|
|
20
|
+
"npm:@samfp/pi-memory"
|
|
23
21
|
],
|
|
24
22
|
"memory": {
|
|
25
23
|
"localPath": "~/.aicode-cli/memory",
|
|
26
24
|
"lessonInjection": "selective",
|
|
27
|
-
"consolidationModel": "
|
|
25
|
+
"consolidationModel": "deepseek-v4-flash"
|
|
28
26
|
},
|
|
29
27
|
"subagents": {
|
|
30
28
|
"agentOverrides": {
|
|
31
29
|
"scout": {
|
|
32
|
-
"model": "
|
|
30
|
+
"model": "deepseek-v4-flash"
|
|
33
31
|
},
|
|
34
32
|
"researcher": {
|
|
35
|
-
"model": "
|
|
33
|
+
"model": "deepseek-v4-flash"
|
|
36
34
|
},
|
|
37
35
|
"planner": {
|
|
38
|
-
"model": "
|
|
36
|
+
"model": "deepseek-v4-flash"
|
|
39
37
|
},
|
|
40
38
|
"worker": {
|
|
41
|
-
"model": "
|
|
39
|
+
"model": "deepseek-v4-flash"
|
|
42
40
|
},
|
|
43
41
|
"reviewer": {
|
|
44
|
-
"model": "
|
|
42
|
+
"model": "deepseek-v4-flash"
|
|
45
43
|
},
|
|
46
44
|
"context-builder": {
|
|
47
|
-
"model": "
|
|
45
|
+
"model": "deepseek-v4-flash"
|
|
48
46
|
},
|
|
49
47
|
"oracle": {
|
|
50
|
-
"model": "
|
|
48
|
+
"model": "deepseek-v4-flash"
|
|
51
49
|
},
|
|
52
50
|
"delegate": {
|
|
53
|
-
"model": "
|
|
51
|
+
"model": "deepseek-v4-flash"
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
54
|
}
|