@ia-ccun/code-agent-cli 0.0.56 → 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.
@@ -37,23 +37,43 @@ interface GrillState {
37
37
  }
38
38
 
39
39
  const RecordTurnParams = Type.Object({
40
- question: Type.String({ description: "The exact question asked, one question only." }),
41
- recommendedAnswer: Type.String({ description: "The assistant's recommended answer to the question." }),
42
- userAnswer: Type.Optional(Type.String({ description: "The user's answer, if already provided." })),
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(Type.String({ description: "Short rationale, dependency, or follow-up notes." })),
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(Type.String({ description: "Relative output path. Defaults to GRILL-ME.md." })),
53
- summary: Type.Optional(Type.String({ description: "Optional current shared-understanding summary." })),
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(Type.String({ description: "Which decision is blocking progress next." })),
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(statePath(cwd), JSON.stringify(state, null, 2) + "\n", "utf8");
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: { summary?: string; agreedDecisions?: string[]; openRisks?: string[]; nextDecisionNeeded?: string },
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(`**Recommended answer:** ${turn.recommendedAnswer || "_(none)_"}`, "");
98
- lines.push(`**User answer:** ${turn.userAnswer || "_(not recorded)_"}`, "");
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("## Agreed Decisions", "", ...extra.agreedDecisions.map((d) => `- ${d}`), "");
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("## Open Risks", "", ...extra.openRisks.map((r) => `- ${r}`), "");
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("## Next Decision Needed", "", extra.nextDecisionNeeded.trim(), "");
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(`Refusing to write outside project directory: ${requested}`);
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: "Start a deterministic design interview and save results to Markdown",
179
+ description:
180
+ "Start a deterministic design interview and save results to Markdown",
128
181
  handler: async (args, ctx) => {
129
- const plan = args.trim() || "(No plan supplied yet. Ask the user to paste or describe the plan first.)";
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
- pi.sendUserMessage(
141
- `开始 /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 文件。`,
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: "Record one /grill-me question, recommended answer, user answer, and decision status in project state.",
150
- promptSnippet: "Record structured progress for an active /grill-me design interview",
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: [{ type: "text", text: `Recorded grill turn #${state.turns.length}` }],
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: "Save the active /grill-me interview state as a Markdown file inside the project directory.",
178
- promptSnippet: "Save /grill-me interview decisions and risks to Markdown in the project directory",
179
- promptGuidelines: ["Use grill_save_results when the /grill-me interview is complete or the user asks to save/stop."],
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: [{ type: "text", text: "No active /grill-me state found. Run /grill-me first." }],
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: [{ type: "text", text: `Saved grill results to ${outputPath}` }],
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 { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { Box, Spacer, Text, Editor, type EditorTheme, Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
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) => pattern.test(normalizedCommand));
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
- { label: "批准并立即执行", value: "approve" },
293
- { label: "从当前计划继续", value: "continue" },
294
- { label: "重新生成计划", value: "regenerate" },
295
- { label: "退出计划模式", value: "exit" },
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((option) => option.value === "continue");
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 => normalizeContinueNote(continueNote);
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) => renderedLines.push(truncateToWidth(line, width));
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 (let optionIndex = 0; optionIndex < ACTION_OPTIONS.length; optionIndex++) {
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(`${cursorPrefix}${theme.fg(optionColor, `${bullet} ${optionLabel}`)}`);
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(theme.fg("dim", " ↑↓ 移动 • Enter 继续 • Tab 编辑备注 • Esc 取消"));
446
+ addLine(
447
+ theme.fg("dim", " ↑↓ 移动 • Enter 继续 • Tab 编辑备注 • Esc 取消"),
448
+ );
422
449
  } else {
423
- addLine(theme.fg("dim", " ↑↓ 移动 • Enter 继续 • Tab 添加备注 • Esc 取消"));
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[] => pi.getAllTools().map((tool) => tool.name);
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.getActiveTools().filter((tool) => !WRITE_LIKE_TOOLS.has(tool));
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", "⏸ 计划") + ctx.ui.theme.fg("dim", " | plan (Ctrl+Alt+P)")
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: "启用只读计划模式。用法:/plan, /plan on, /plan off, /plan status, /plan <任务>",
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(raw);
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 { kind?: string; message?: string } | undefined;
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(new Text(theme.fg("customMessageLabel", theme.bold("📋 计划")), 0, 0));
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(new Text(`${theme.fg("customMessageText", msg)} ${theme.fg("dim", "(ctrl+o 展开)")}`, 0, 0));
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(`${theme.fg("dim", "消息:")}${theme.fg("customMessageText", msg)}`);
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
- if (firstOpenStep) {
844
- pi.sendUserMessage(
845
- `${EXECUTION_TRIGGER_PROMPT} 从步骤 ${firstOpenStep.step} 开始:${firstOpenStep.text}`,
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(EXECUTION_TRIGGER_PROMPT);
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
- pi.sendUserMessage(
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
- if (firstOpenStep) {
874
- pi.sendUserMessage(
875
- `从提议的计划继续。用户备注:${continueNote}。聚焦步骤 ${firstOpenStep.step}: ${firstOpenStep.text}。优化文件、验证和风险分析。`,
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
- state.goal = fallbackGoalFromPrompt(event.prompt);
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
  });
@@ -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(fullPrompt);
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
  /**
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "mcpServers": {
3
+ "drawio": {
4
+ "command": "npx",
5
+ "args": ["-y", "@drawio/mcp"]
6
+ }
3
7
  }
4
8
  }
@@ -1,56 +1,54 @@
1
1
  {
2
- "quietStartup":true,
3
- "defaultProvider": "aicode",
4
- "defaultModel": "qwen3.5-35b-a3b",
5
- "theme":"light",
6
- "extensions":[
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@0.17.1",
18
- "npm:pi-rewind@0.5.0",
19
- "npm:pi-mcp-adapter@2.4.0",
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@1.1.1"
20
+ "npm:@samfp/pi-memory"
23
21
  ],
24
22
  "memory": {
25
23
  "localPath": "~/.aicode-cli/memory",
26
24
  "lessonInjection": "selective",
27
- "consolidationModel": "qwen3.5-35b-a3b"
25
+ "consolidationModel": "deepseek-v4-flash"
28
26
  },
29
27
  "subagents": {
30
28
  "agentOverrides": {
31
29
  "scout": {
32
- "model": "qwen3.5-35b-a3b"
30
+ "model": "deepseek-v4-flash"
33
31
  },
34
32
  "researcher": {
35
- "model": "qwen3.5-35b-a3b"
33
+ "model": "deepseek-v4-flash"
36
34
  },
37
35
  "planner": {
38
- "model": "qwen3.5-35b-a3b"
36
+ "model": "deepseek-v4-flash"
39
37
  },
40
38
  "worker": {
41
- "model": "qwen3.5-35b-a3b"
39
+ "model": "deepseek-v4-flash"
42
40
  },
43
41
  "reviewer": {
44
- "model": "qwen3.5-35b-a3b"
42
+ "model": "deepseek-v4-flash"
45
43
  },
46
44
  "context-builder": {
47
- "model": "qwen3.5-35b-a3b"
45
+ "model": "deepseek-v4-flash"
48
46
  },
49
47
  "oracle": {
50
- "model": "qwen3.5-35b-a3b"
48
+ "model": "deepseek-v4-flash"
51
49
  },
52
50
  "delegate": {
53
- "model": "qwen3.5-35b-a3b"
51
+ "model": "deepseek-v4-flash"
54
52
  }
55
53
  }
56
54
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
2
+ "$schema": "https://pi.dev/schemas/theme.schema.json",
3
3
  "name": "catppuccin-mocha",
4
4
  "vars": {
5
5
  "base": "#17131F",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-ccun/code-agent-cli",
3
- "version": "0.0.56",
3
+ "version": "0.0.57-beta.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -34,10 +34,10 @@
34
34
  "prepare": "husky"
35
35
  },
36
36
  "dependencies": {
37
- "@earendil-works/pi-agent-core": "^1.1.0",
38
- "@earendil-works/pi-ai": "^1.1.0",
39
- "@earendil-works/pi-coding-agent": "^1.1.0",
40
- "@earendil-works/pi-tui": "^1.1.0"
37
+ "@earendil-works/pi-agent-core": "^1.4.0",
38
+ "@earendil-works/pi-ai": "^1.4.0",
39
+ "@earendil-works/pi-coding-agent": "^1.4.0",
40
+ "@earendil-works/pi-tui": "^1.4.0"
41
41
  },
42
42
  "overrides": {
43
43
  "glob": "^11.0.0",