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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bin/cli.js +97 -69
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +0 -5
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/lib/common.sh +287 -0
  38. package/scripts/regression/results/.last_pid +1 -0
  39. package/scripts/regression/results/.last_workdir +1 -0
  40. package/scripts/regression/results/02-extension-loading.log +21 -0
  41. package/scripts/regression/results/03-prompt-stream.log +16 -0
  42. package/scripts/regression/results/03-rpc-protocol.log +27 -0
  43. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  44. package/scripts/regression/results/05-tool-execution.log +8 -0
  45. package/scripts/regression/results/06-pi-new-features.log +27 -0
  46. package/scripts/regression/run-all.sh +187 -0
  47. package/config/agent/bin/fd.exe +0 -0
  48. package/config/agent/bin/rg.exe +0 -0
  49. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  50. package/config/agent/extensions/aicode-snake.ts +0 -354
  51. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  52. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  57. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  58. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  60. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  106. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  107. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  108. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
@@ -9,10 +9,14 @@
9
9
  */
10
10
 
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
12
- import { join, dirname } from "node:path";
13
- import { fileURLToPath } from "node:url";
12
+ import { join } from "node:path";
14
13
  import type { Api, Model, StopReason, Usage } from "@earendil-works/pi-ai";
15
- import { completeSimple, getSupportedThinkingLevels, type Message, type ThinkingLevel } from "@earendil-works/pi-ai";
14
+ import {
15
+ completeSimple,
16
+ getSupportedThinkingLevels,
17
+ type Message,
18
+ type ThinkingLevel,
19
+ } from "@earendil-works/pi-ai/compat";
16
20
  import {
17
21
  type AgentToolResult,
18
22
  type AgentToolUpdateCallback,
@@ -24,12 +28,7 @@ import {
24
28
  } from "@earendil-works/pi-coding-agent";
25
29
  import type { SelectItem } from "@earendil-works/pi-tui";
26
30
  import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
27
- import {
28
- Container,
29
- SelectList,
30
- Spacer,
31
- Text,
32
- } from "@earendil-works/pi-tui";
31
+ import { Container, SelectList, Spacer, Text } from "@earendil-works/pi-tui";
33
32
  import { Type } from "typebox";
34
33
 
35
34
  // ============== 常量 ==============
@@ -38,9 +37,20 @@ const TOOL_LABEL = "Advisor";
38
37
  const CHECKMARK = " ✓";
39
38
  const NO_ADVISOR_VALUE = "__no_advisor__";
40
39
  const OFF_VALUE = "__off__";
41
- const BASE_EFFORT_LEVELS: ThinkingLevel[] = ["minimal", "low", "medium", "high"];
40
+ const BASE_EFFORT_LEVELS: ThinkingLevel[] = [
41
+ "minimal",
42
+ "low",
43
+ "medium",
44
+ "high",
45
+ ];
42
46
  const XHIGH_EFFORT_LEVEL: ThinkingLevel = "xhigh";
43
- const EFFORT_ORDINAL: readonly ThinkingLevel[] = ["minimal", "low", "medium", "high", "xhigh"];
47
+ const EFFORT_ORDINAL: readonly ThinkingLevel[] = [
48
+ "minimal",
49
+ "low",
50
+ "medium",
51
+ "high",
52
+ "xhigh",
53
+ ];
44
54
  const DEFAULT_EFFORT: ThinkingLevel = "high";
45
55
  const RECOMMENDED_EFFORT_SUFFIX = " (recommended)";
46
56
 
@@ -49,15 +59,13 @@ const MSG_ADVISOR_DISABLED = "Advisor disabled";
49
59
  const MSG_REQUIRES_INTERACTIVE = "/advisor requires interactive mode";
50
60
  const MSG_ADVISOR_NUDGE = "Please advise on the executor's situation above.";
51
61
  const MSG_PERSIST_FAILED = "Failed to save advisor selection";
52
- const ERR_NO_MODEL = "No advisor model is configured. Use /advisor to enable one.";
62
+ const ERR_NO_MODEL =
63
+ "No advisor model is configured. Use /advisor to enable one.";
53
64
  const ERR_CALL_ABORTED = "Advisor call was cancelled.";
54
65
  const ERR_EMPTY_RESPONSE = "Advisor returned no text content.";
55
66
  const ERR_NO_MODEL_SELECTED = "no advisor model selected";
56
67
  const ERR_EMPTY_RESPONSE_DETAIL = "empty response";
57
68
  const ERR_ABORTED_DETAIL = "aborted";
58
- const ERR_UNKNOWN = "unknown error";
59
-
60
- // 系统提示词
61
69
  const ADVISOR_SYSTEM_PROMPT = [
62
70
  "You are a senior reviewer providing guidance to a working agent.",
63
71
  "The working agent will share their full conversation context with you.",
@@ -70,11 +78,18 @@ const ADVISOR_SYSTEM_PROMPT = [
70
78
  interface AdvisorConfig {
71
79
  modelKey?: string;
72
80
  effort?: ThinkingLevel;
73
- disabledForModels?: Array<string | { model: string; minEffort?: ThinkingLevel }>;
81
+ disabledForModels?: Array<
82
+ string | { model: string; minEffort?: ThinkingLevel }
83
+ >;
74
84
  }
75
85
 
76
86
  function getConfigPath(): string {
77
- const configDir = join(process.env.HOME || process.env.USERPROFILE || "~", ".aicode-cli", "agent", "aicode-advisor");
87
+ const configDir = join(
88
+ process.env.HOME || process.env.USERPROFILE || "~",
89
+ ".aicode-cli",
90
+ "agent",
91
+ "aicode-advisor",
92
+ );
78
93
  if (!existsSync(configDir)) {
79
94
  mkdirSync(configDir, { recursive: true, mode: 0o700 });
80
95
  }
@@ -95,17 +110,20 @@ function loadAdvisorConfig(): AdvisorConfig {
95
110
  return {};
96
111
  }
97
112
 
98
- function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | undefined): boolean {
113
+ function saveAdvisorConfig(
114
+ key: string | undefined,
115
+ effort: ThinkingLevel | undefined,
116
+ ): boolean {
99
117
  const configPath = getConfigPath();
100
118
  const existing = loadAdvisorConfig();
101
119
  const config: AdvisorConfig = { ...existing };
102
-
120
+
103
121
  if (key) config.modelKey = key;
104
122
  else delete config.modelKey;
105
-
123
+
106
124
  if (effort) config.effort = effort;
107
125
  else delete config.effort;
108
-
126
+
109
127
  try {
110
128
  writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
111
129
  return true;
@@ -114,7 +132,9 @@ function saveAdvisorConfig(key: string | undefined, effort: ThinkingLevel | unde
114
132
  }
115
133
  }
116
134
 
117
- function parseModelKey(key: string): { provider: string; modelId: string } | undefined {
135
+ function parseModelKey(
136
+ key: string,
137
+ ): { provider: string; modelId: string } | undefined {
118
138
  const idx = key.indexOf(":");
119
139
  if (idx < 1) return undefined;
120
140
  return { provider: key.slice(0, idx), modelId: key.slice(idx + 1) };
@@ -123,7 +143,9 @@ function parseModelKey(key: string): { provider: string; modelId: string } | und
123
143
  // ============== 运行时状态 ==============
124
144
  let selectedAdvisor: Model<Api> | undefined;
125
145
  let selectedAdvisorEffort: ThinkingLevel | undefined;
126
- let disabledForModelsCache: Array<string | { model: string; minEffort?: ThinkingLevel }> = [];
146
+ let disabledForModelsCache: Array<
147
+ string | { model: string; minEffort?: ThinkingLevel }
148
+ > = [];
127
149
 
128
150
  function getAdvisorModel(): Model<Api> | undefined {
129
151
  return selectedAdvisor;
@@ -141,21 +163,23 @@ function setAdvisorEffort(effort: ThinkingLevel | undefined): void {
141
163
  selectedAdvisorEffort = effort;
142
164
  }
143
165
 
144
- function setDisabledForModels(models: Array<string | { model: string; minEffort?: ThinkingLevel }>): void {
166
+ function setDisabledForModels(
167
+ models: Array<string | { model: string; minEffort?: ThinkingLevel }>,
168
+ ): void {
145
169
  disabledForModelsCache = models;
146
170
  }
147
171
 
148
172
  // ============== 工具清单序列化 ==============
149
173
  /**
150
174
  * stableStringify - 稳定序列化对象(键排序)
151
- *
175
+ *
152
176
  * 用于序列化工具参数,确保参数对象的键顺序一致,便于对比和展示。
153
- *
177
+ *
154
178
  * 功能说明:
155
179
  * - 处理 null 和非对象类型:直接 JSON.stringify
156
180
  * - 处理数组:递归序列化每个元素,undefined 替换为 "null"
157
181
  * - 处理对象:键排序后序列化,跳过 undefined 值
158
- *
182
+ *
159
183
  * @param value - 要序列化的值
160
184
  * @returns 稳定的 JSON 字符串(键排序)
161
185
  */
@@ -178,7 +202,10 @@ function stableStringify(value: unknown): string {
178
202
 
179
203
  function buildInventoryBlock(tools: ToolInfo[]): string {
180
204
  return tools
181
- .map((t) => `### ${t.name}\n${t.description}\n\nParameters: ${stableStringify(t.parameters)}`)
205
+ .map(
206
+ (t) =>
207
+ `### ${t.name}\n${t.description}\n\nParameters: ${stableStringify(t.parameters)}`,
208
+ )
182
209
  .join("\n\n---\n\n");
183
210
  }
184
211
 
@@ -186,7 +213,9 @@ function stripInflightAdvisorCall(messages: Message[]): Message[] {
186
213
  if (messages.length === 0) return messages;
187
214
  const last = messages[messages.length - 1];
188
215
  if (last.role !== "assistant") return messages;
189
- const filtered = last.content.filter((c) => !(c.type === "toolCall" && c.name === ADVISOR_TOOL_NAME));
216
+ const filtered = last.content.filter(
217
+ (c) => !(c.type === "toolCall" && c.name === ADVISOR_TOOL_NAME),
218
+ );
190
219
  if (filtered.length === last.content.length) return messages;
191
220
  if (filtered.length === 0) return messages.slice(0, -1);
192
221
  return [...messages.slice(0, -1), { ...last, content: filtered }];
@@ -206,7 +235,9 @@ function ensureUserTailForAdvisor(messages: Message[]): Message[] {
206
235
 
207
236
  function getInventoryMessage(tools: ToolInfo[]): Message | undefined {
208
237
  if (tools.length === 0) return undefined;
209
- const sorted = [...tools].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
238
+ const sorted = [...tools].sort((a, b) =>
239
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
240
+ );
210
241
  const text = `## Available Executor Tools\n\n${buildInventoryBlock(sorted)}`;
211
242
  return {
212
243
  role: "user",
@@ -228,7 +259,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
228
259
  const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
229
260
  if (!model) {
230
261
  if (ctx.hasUI) {
231
- ctx.ui.notify(`Previously configured advisor model ${config.modelKey} is no longer available`, "warning");
262
+ ctx.ui.notify(
263
+ `Previously configured advisor model ${config.modelKey} is no longer available`,
264
+ "warning",
265
+ );
232
266
  }
233
267
  return;
234
268
  }
@@ -240,7 +274,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
240
274
 
241
275
  if (isExecutorBlocked(ctx, pi.getThinkingLevel())) {
242
276
  if (ctx.hasUI) {
243
- ctx.ui.notify(`Advisor restored: ${model.provider}:${model.id} (inactive for current executor)`, "info");
277
+ ctx.ui.notify(
278
+ `Advisor restored: ${model.provider}:${model.id} (inactive for current executor)`,
279
+ "info",
280
+ );
244
281
  }
245
282
  return;
246
283
  }
@@ -251,7 +288,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
251
288
  }
252
289
 
253
290
  if (ctx.hasUI) {
254
- ctx.ui.notify(`Advisor restored: ${model.provider}:${model.id}${config.effort ? `, ${config.effort}` : ""}`, "info");
291
+ ctx.ui.notify(
292
+ `Advisor restored: ${model.provider}:${model.id}${config.effort ? `, ${config.effort}` : ""}`,
293
+ "info",
294
+ );
255
295
  }
256
296
  }
257
297
 
@@ -272,7 +312,9 @@ function buildErrorResult(
272
312
  const effort = getAdvisorEffort();
273
313
  return {
274
314
  content: [{ type: "text", text: userText }],
275
- details: advisorLabel ? { advisorModel: advisorLabel, effort, errorMessage } : { effort, errorMessage },
315
+ details: advisorLabel
316
+ ? { advisorModel: advisorLabel, effort, errorMessage }
317
+ : { effort, errorMessage },
276
318
  };
277
319
  }
278
320
 
@@ -291,22 +333,39 @@ async function executeAdvisor(
291
333
 
292
334
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(advisor);
293
335
  if (!auth.ok) {
294
- return buildErrorResult(advisorLabel, `Advisor (${advisorLabel}) is misconfigured: ${auth.error}`, auth.error);
336
+ return buildErrorResult(
337
+ advisorLabel,
338
+ `Advisor (${advisorLabel}) is misconfigured: ${auth.error}`,
339
+ auth.error,
340
+ );
295
341
  }
296
342
  if (!auth.apiKey) {
297
- return buildErrorResult(advisorLabel, `Advisor (${advisorLabel}) has no API key available.`, `no API key for ${advisor.provider}`);
343
+ return buildErrorResult(
344
+ advisorLabel,
345
+ `Advisor (${advisorLabel}) has no API key available.`,
346
+ `no API key for ${advisor.provider}`,
347
+ );
298
348
  }
299
349
 
300
350
  const { messages: sessionMessages } = buildSessionContext(
301
351
  ctx.sessionManager.getEntries(),
302
352
  ctx.sessionManager.getLeafId(),
303
353
  );
304
- const branchMessages = ensureUserTailForAdvisor(stripInflightAdvisorCall(convertToLlm(sessionMessages)));
354
+ const branchMessages = ensureUserTailForAdvisor(
355
+ stripInflightAdvisorCall(convertToLlm(sessionMessages)),
356
+ );
305
357
  const inventoryMessage = getInventoryMessage(pi.getAllTools());
306
- const messages: Message[] = inventoryMessage ? [inventoryMessage, ...branchMessages] : branchMessages;
358
+ const messages: Message[] = inventoryMessage
359
+ ? [inventoryMessage, ...branchMessages]
360
+ : branchMessages;
307
361
 
308
362
  onUpdate?.({
309
- content: [{ type: "text", text: `Consulting advisor (${advisorLabel}${effort ? `, ${effort}` : ""})…` }],
363
+ content: [
364
+ {
365
+ type: "text",
366
+ text: `Consulting advisor (${advisorLabel}${effort ? `, ${effort}` : ""})…`,
367
+ },
368
+ ],
310
369
  details: { advisorModel: advisorLabel, effort },
311
370
  });
312
371
 
@@ -332,7 +391,12 @@ async function executeAdvisor(
332
391
 
333
392
  if (response.stopReason === "error") {
334
393
  return {
335
- content: [{ type: "text", text: `Advisor call failed: ${response.errorMessage}` }],
394
+ content: [
395
+ {
396
+ type: "text",
397
+ text: `Advisor call failed: ${response.errorMessage}`,
398
+ },
399
+ ],
336
400
  details: {
337
401
  advisorModel: advisorLabel,
338
402
  effort,
@@ -344,8 +408,11 @@ async function executeAdvisor(
344
408
  }
345
409
 
346
410
  const advisorText = response.content
347
- .filter((c): c is { type: "text"; text: string } => c.type === "text")
348
- .map((c) => c.text)
411
+ .filter(
412
+ (c: { type: string }): c is { type: "text"; text: string } =>
413
+ c.type === "text",
414
+ )
415
+ .map((c: { type: "text"; text: string }) => c.text)
349
416
  .join("\n")
350
417
  .trim();
351
418
 
@@ -373,7 +440,11 @@ async function executeAdvisor(
373
440
  };
374
441
  } catch (err) {
375
442
  const message = err instanceof Error ? err.message : String(err);
376
- return buildErrorResult(advisorLabel, `Advisor call threw: ${message}`, message);
443
+ return buildErrorResult(
444
+ advisorLabel,
445
+ `Advisor call threw: ${message}`,
446
+ message,
447
+ );
377
448
  }
378
449
  }
379
450
 
@@ -441,7 +512,10 @@ function modelKey(m: { provider: string; id: string }): string {
441
512
  return `${m.provider}:${m.id}`;
442
513
  }
443
514
 
444
- function isModelBlocked(model: Model<Api> | undefined, thinkingLevel?: string): boolean {
515
+ function isModelBlocked(
516
+ model: Model<Api> | undefined,
517
+ thinkingLevel?: string,
518
+ ): boolean {
445
519
  if (!model) return false;
446
520
  const key = modelKey(model);
447
521
  for (const entry of disabledForModelsCache) {
@@ -451,14 +525,19 @@ function isModelBlocked(model: Model<Api> | undefined, thinkingLevel?: string):
451
525
  if (entry.model !== key) continue;
452
526
  if (entry.minEffort === undefined) return true;
453
527
  const thresholdOrdinal = EFFORT_ORDINAL.indexOf(entry.minEffort);
454
- const executorOrdinal = EFFORT_ORDINAL.indexOf(thinkingLevel as ThinkingLevel);
528
+ const executorOrdinal = EFFORT_ORDINAL.indexOf(
529
+ thinkingLevel as ThinkingLevel,
530
+ );
455
531
  if (executorOrdinal >= thresholdOrdinal) return true;
456
532
  }
457
533
  }
458
534
  return false;
459
535
  }
460
536
 
461
- function isExecutorBlocked(ctx: ExtensionContext, thinkingLevel?: string): boolean {
537
+ function isExecutorBlocked(
538
+ ctx: ExtensionContext,
539
+ thinkingLevel?: string,
540
+ ): boolean {
462
541
  return isModelBlocked(ctx?.model, thinkingLevel);
463
542
  }
464
543
 
@@ -514,7 +593,8 @@ const EFFORT_HEADER_PROSE =
514
593
 
515
594
  function selectListTheme(theme: Theme) {
516
595
  return {
517
- selectedPrefix: (t: string) => theme.bg("selectedBg", theme.fg("accent", t)),
596
+ selectedPrefix: (t: string) =>
597
+ theme.bg("selectedBg", theme.fg("accent", t)),
518
598
  selectedText: (t: string) => theme.bg("selectedBg", theme.bold(t)),
519
599
  description: (t: string) => theme.fg("muted", t),
520
600
  scrollInfo: (t: string) => theme.fg("dim", t),
@@ -522,7 +602,13 @@ function selectListTheme(theme: Theme) {
522
602
  };
523
603
  }
524
604
 
525
- function buildSelectPanel(theme: Theme, title: string, proseLines: string[], selectList: SelectList, helpLines?: string[]): Container {
605
+ function buildSelectPanel(
606
+ theme: Theme,
607
+ title: string,
608
+ proseLines: string[],
609
+ selectList: SelectList,
610
+ helpLines?: string[],
611
+ ): Container {
526
612
  const container = new Container();
527
613
  const border = () => new DynamicBorder((s: string) => theme.fg("accent", s));
528
614
 
@@ -534,17 +620,19 @@ function buildSelectPanel(theme: Theme, title: string, proseLines: string[], sel
534
620
  container.addChild(new Text(line, 1, 0));
535
621
  container.addChild(new Spacer(1));
536
622
  }
537
-
623
+
538
624
  // 添加帮助信息(如果有)
539
625
  if (helpLines && helpLines.length > 0) {
540
- container.addChild(new Text(theme.fg("accent", theme.bold(helpLines[0])), 1, 0));
626
+ container.addChild(
627
+ new Text(theme.fg("accent", theme.bold(helpLines[0])), 1, 0),
628
+ );
541
629
  container.addChild(new Spacer(1));
542
630
  for (let i = 1; i < helpLines.length; i++) {
543
631
  container.addChild(new Text(helpLines[i], 1, 0));
544
632
  }
545
633
  container.addChild(new Spacer(1));
546
634
  }
547
-
635
+
548
636
  container.addChild(selectList);
549
637
  container.addChild(new Spacer(1));
550
638
  container.addChild(new Text(theme.fg("dim", NAV_HINT), 1, 0));
@@ -558,7 +646,11 @@ function showAdvisorPicker(
558
646
  items: SelectItem[],
559
647
  ): Promise<string | null> {
560
648
  return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
561
- const selectList = new SelectList(items, Math.min(items.length, MAX_VISIBLE_ROWS), selectListTheme(theme));
649
+ const selectList = new SelectList(
650
+ items,
651
+ Math.min(items.length, MAX_VISIBLE_ROWS),
652
+ selectListTheme(theme),
653
+ );
562
654
  selectList.onSelect = (item) => done(item.value);
563
655
  selectList.onCancel = () => done(null);
564
656
 
@@ -588,15 +680,28 @@ function showEffortPicker(
588
680
  defaultEffort?: ThinkingLevel,
589
681
  ): Promise<string | null> {
590
682
  return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
591
- const selectList = new SelectList(items, Math.min(items.length, MAX_VISIBLE_ROWS), selectListTheme(theme));
592
- const preferredIdx = currentEffort ? items.findIndex((item) => item.value === currentEffort) : -1;
683
+ const selectList = new SelectList(
684
+ items,
685
+ Math.min(items.length, MAX_VISIBLE_ROWS),
686
+ selectListTheme(theme),
687
+ );
688
+ const preferredIdx = currentEffort
689
+ ? items.findIndex((item) => item.value === currentEffort)
690
+ : -1;
593
691
  selectList.setSelectedIndex(
594
- preferredIdx >= 0 ? preferredIdx : items.findIndex((item) => item.value === defaultEffort),
692
+ preferredIdx >= 0
693
+ ? preferredIdx
694
+ : items.findIndex((item) => item.value === defaultEffort),
595
695
  );
596
696
  selectList.onSelect = (item) => done(item.value);
597
697
  selectList.onCancel = () => done(null);
598
698
 
599
- const container = buildSelectPanel(theme, EFFORT_HEADER_TITLE, [EFFORT_HEADER_PROSE], selectList);
699
+ const container = buildSelectPanel(
700
+ theme,
701
+ EFFORT_HEADER_TITLE,
702
+ [EFFORT_HEADER_PROSE],
703
+ selectList,
704
+ );
600
705
 
601
706
  return {
602
707
  render: (w: number) => container.render(w),
@@ -630,7 +735,8 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
630
735
  });
631
736
  items.push({
632
737
  value: NO_ADVISOR_VALUE,
633
- label: currentKey === undefined ? `No advisor${CHECKMARK}` : "No advisor",
738
+ label:
739
+ currentKey === undefined ? `No advisor${CHECKMARK}` : "No advisor",
634
740
  });
635
741
 
636
742
  const choice = await showAdvisorPicker(ctx, items);
@@ -672,15 +778,26 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
672
778
  { value: OFF_VALUE, label: "off" },
673
779
  ...levels.map((level) => ({
674
780
  value: level,
675
- label: level === DEFAULT_EFFORT ? `${level}${RECOMMENDED_EFFORT_SUFFIX}` : level,
781
+ label:
782
+ level === DEFAULT_EFFORT
783
+ ? `${level}${RECOMMENDED_EFFORT_SUFFIX}`
784
+ : level,
676
785
  })),
677
786
  ];
678
787
 
679
- const effortResult = await showEffortPicker(ctx, effortItems, getAdvisorEffort(), DEFAULT_EFFORT);
788
+ const effortResult = await showEffortPicker(
789
+ ctx,
790
+ effortItems,
791
+ getAdvisorEffort(),
792
+ DEFAULT_EFFORT,
793
+ );
680
794
  if (!effortResult) {
681
795
  return;
682
796
  }
683
- effortChoice = effortResult === OFF_VALUE ? undefined : (effortResult as ThinkingLevel);
797
+ effortChoice =
798
+ effortResult === OFF_VALUE
799
+ ? undefined
800
+ : (effortResult as ThinkingLevel);
684
801
  }
685
802
 
686
803
  if (!saveAdvisorConfig(modelKey(picked), effortChoice)) {
@@ -693,15 +810,21 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
693
810
  const activeToolsNow = pi.getActiveTools();
694
811
  const activeHasNow = activeToolsNow.includes(ADVISOR_TOOL_NAME);
695
812
  const blocked = isExecutorBlocked(ctx, pi.getThinkingLevel());
696
-
813
+
697
814
  if (!activeHasNow && !blocked) {
698
815
  pi.setActiveTools([...activeToolsNow, ADVISOR_TOOL_NAME]);
699
816
  }
700
-
817
+
701
818
  if (blocked) {
702
- ctx.ui.notify(`Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""} (inactive for current executor)`, "info");
819
+ ctx.ui.notify(
820
+ `Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""} (inactive for current executor)`,
821
+ "info",
822
+ );
703
823
  } else {
704
- ctx.ui.notify(`Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""}`, "info");
824
+ ctx.ui.notify(
825
+ `Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""}`,
826
+ "info",
827
+ );
705
828
  }
706
829
  },
707
830
  });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @author: xujianjiang
3
+ * aicode-banner extension
4
+ *
5
+ * 在 `session_start` 触发时打印启动 banner (含快捷命令、常用提示词、技能).
6
+ * 替代原 cli.js 启动时同步 `console.log(generateBanner(...))`:
7
+ * 1) 避开 RPC 模式 stdout 污染 (banner 现在走 TUI notify, 不写 stdout)
8
+ * 2) chdir 到 homedir 之后才触发, 配合 cli.js 入口的防御性 chdir
9
+ * 3) 只在首次 startup 时打印; resume/new/fork 不重复
10
+ *
11
+ * ponytail: 用 ctx.ui.notify 走 TUI 通知路径, 不直写 console.
12
+ */
13
+
14
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
+
16
+ const COLOR_THEMES = [
17
+ { name: "deep-cyan", hex: "#2e7d8c" },
18
+ { name: "dark-cyan", hex: "#399aad" },
19
+ { name: "cyan", hex: "#49bccf" },
20
+ { name: "light-cyan", hex: "#5ec9db" },
21
+ { name: "bright-cyan", hex: "#73d6e8" },
22
+ { name: "pale-cyan", hex: "#8ae3f0" },
23
+ { name: "faint-cyan", hex: "#a0f0f8" },
24
+ ];
25
+
26
+ function hexToRgb(hex: string): { r: number; g: number; b: number } {
27
+ const h = hex.replace("#", "");
28
+ return {
29
+ r: parseInt(h.slice(0, 2), 16),
30
+ g: parseInt(h.slice(2, 4), 16),
31
+ b: parseInt(h.slice(4, 6), 16),
32
+ };
33
+ }
34
+
35
+ function getRotatedColorTheme(): string {
36
+ const day = new Date().getDay();
37
+ return COLOR_THEMES[day % COLOR_THEMES.length].hex;
38
+ }
39
+
40
+ // 最小可用 banner: 走 ctx.ui.notify() 渲染, 不依赖 dist/banner.js
41
+ // 保留 cli.js 旧 banner 的核心信息密度, 但走 TUI 而非 stdout
42
+ function buildBanner(themeColor: string, version: string): string {
43
+ const theme =
44
+ themeColor === "random" || themeColor === "rotate"
45
+ ? getRotatedColorTheme()
46
+ : themeColor;
47
+ const rgb = hexToRgb(theme);
48
+ const color = (text: string) =>
49
+ `\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m${text}\x1b[0m`;
50
+ const dim = (text: string) => `\x1b[2m${text}\x1b[0m`;
51
+ const bold = (text: string) => `\x1b[1m${text}\x1b[0m`;
52
+
53
+ // cwd 用 try/catch 防御 ENOENT (虽然 cli.js 已 chdir 到 homedir, 多一道防御)
54
+ let cwd: string;
55
+ try {
56
+ cwd = process
57
+ .cwd()
58
+ .replace(process.env.HOME || process.env.USERPROFILE || "", "~");
59
+ } catch {
60
+ cwd = "~";
61
+ }
62
+
63
+ return [
64
+ `${color(bold("AICODE CLI"))} ${dim(`v${version}`)}`,
65
+ `${dim("Dir:")} ${dim(cwd)}`,
66
+ "",
67
+ dim(
68
+ "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -",
69
+ ),
70
+ dim(
71
+ " 直接用自然语言描述需求即可 • 复杂任务先用 /plan 生成计划 • @文件路径 指定文件",
72
+ ),
73
+ dim(
74
+ "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -",
75
+ ),
76
+ "",
77
+ bold(" 常用命令"),
78
+ ` ${color("/model")} 切换模型 ${color("/settings")} 设置 ${color("/compact")} 压缩上下文`,
79
+ ` ${color("/new")} 新会话 ${color("/reload")} 重载配置 ${color("/resume")} 恢复会话`,
80
+ ` ${color("/plan")} 计划模式(只读) ${color("/bugs")} 已知bug修复 ${color("/goal")} 目标导向`,
81
+ "",
82
+ bold(" 提示词与技能"),
83
+ ` ${color("/workflow")} 六阶段工作流 ${color("/code-review")} 代码审查 ${color("/commit")} Git提交`,
84
+ ` ${color("/brainstorming")} 头脑风暴 ${color("/skill-creator")} 创建技能`,
85
+ ].join("\n");
86
+ }
87
+
88
+ export default function (pi: ExtensionAPI) {
89
+ const version = process.env.AICODE_CLI_VERSION || "";
90
+ // 主题色从 env 读 (cli.js 启动时已注入 AICODE_BANNER_THEME), 缺省用基准色
91
+ const themeColor = process.env.AICODE_BANNER_THEME || "#49bccf";
92
+
93
+ pi.on("session_start", (event, ctx) => {
94
+ // 只在 startup (新会话) 时打印, 不在 resume/new/fork 重复刷屏
95
+ if (event.reason !== "startup") return;
96
+ // 没有 TUI (纯 RPC/JSON 模式) 不打印 — 不污染 JSON 输出
97
+ if (!ctx.hasUI) return;
98
+
99
+ ctx.ui.notify(buildBanner(themeColor, version), "info");
100
+ });
101
+ }