@ia-ccun/code-agent-cli 0.0.52 → 0.0.53-beta.2
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/bin/README.md +102 -0
- package/config/agent/extensions/aicode-advisor.ts +197 -61
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +677 -183
- package/config/agent/extensions/aicode-cd.ts +1017 -0
- package/config/agent/extensions/aicode-grillMe.ts +198 -0
- package/config/agent/extensions/aicode-pets.ts +351 -0
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +555 -0
- package/config/agent/extensions/aicode-tips.ts +69 -30
- package/config/agent/extensions/aicode-todo-progress.ts +547 -0
- 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 +119 -109
- package/config/agent/extensions/error-monitor.ts +38 -51
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/ponytail.ts +441 -0
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/tps.ts +87 -0
- package/config/agent/extensions/working-msg.ts +164 -51
- package/config/agent/help.md +0 -7
- package/config/agent/models.json +7 -7
- package/config/agent/settings.json +2 -1
- package/config/agent/skills/chroma/LICENSE +21 -0
- package/config/agent/skills/chroma/chroma-local/SKILL.md +165 -0
- package/config/agent/skills/chroma/chroma-local/cli.md +48 -0
- package/config/agent/skills/chroma/chroma-local/data-model.md +103 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/python.md +166 -0
- package/config/agent/skills/chroma/chroma-local/error-handling/typescript.md +231 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/python.md +95 -0
- package/config/agent/skills/chroma/chroma-local/local-chroma/typescript.md +101 -0
- package/config/agent/skills/chroma/chroma-local/metadata/python.md +154 -0
- package/config/agent/skills/chroma/chroma-local/metadata/typescript.md +153 -0
- package/config/agent/skills/chroma/chroma-local/querying/python.md +147 -0
- package/config/agent/skills/chroma/chroma-local/querying/typescript.md +157 -0
- package/config/agent/skills/chroma/chroma-local/regex/python.md +69 -0
- package/config/agent/skills/chroma/chroma-local/regex/typescript.md +71 -0
- package/config/agent/skills/chroma/chroma-local/understanding-a-codebase.md +80 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/python.md +159 -0
- package/config/agent/skills/chroma/chroma-local/updating-deleting/typescript.md +228 -0
- package/config/agent/skills/milvus-skill/LICENSE +202 -0
- package/config/agent/skills/milvus-skill/README.md +37 -0
- package/config/agent/skills/milvus-skill/SKILL.md +259 -0
- package/config/agent/skills/milvus-skill/references/collection.md +86 -0
- package/config/agent/skills/milvus-skill/references/database.md +24 -0
- package/config/agent/skills/milvus-skill/references/index.md +69 -0
- package/config/agent/skills/milvus-skill/references/partition.md +29 -0
- package/config/agent/skills/milvus-skill/references/patterns.md +150 -0
- package/config/agent/skills/milvus-skill/references/user-role.md +90 -0
- package/config/agent/skills/milvus-skill/references/vector.md +247 -0
- package/config/agent/skills/ponytail/SKILL.md +100 -0
- package/config/agent/skills/ponytail-audit/SKILL.md +40 -0
- package/config/agent/skills/ponytail-debt/SKILL.md +44 -0
- package/config/agent/skills/ponytail-help/SKILL.md +68 -0
- package/config/agent/skills/ponytail-review/SKILL.md +56 -0
- package/config/agent/skills/zread/README.md +115 -0
- package/config/agent/skills/zread/SKILL.md +103 -0
- package/config/agent/skills/zread/references/stdio-protocol.md +312 -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 +7 -7
- package/scripts/postinstall.js +58 -1
- 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 +18 -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 +29 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/dangerous-patterns.json +0 -106
- package/config/agent/extensions/bash-check.ts +0 -227
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
12
|
-
import { join
|
|
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 {
|
|
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[] = [
|
|
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[] = [
|
|
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 =
|
|
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<
|
|
81
|
+
disabledForModels?: Array<
|
|
82
|
+
string | { model: string; minEffort?: ThinkingLevel }
|
|
83
|
+
>;
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
function getConfigPath(): string {
|
|
77
|
-
const configDir = join(
|
|
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(
|
|
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(
|
|
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<
|
|
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,11 +163,26 @@ function setAdvisorEffort(effort: ThinkingLevel | undefined): void {
|
|
|
141
163
|
selectedAdvisorEffort = effort;
|
|
142
164
|
}
|
|
143
165
|
|
|
144
|
-
function setDisabledForModels(
|
|
166
|
+
function setDisabledForModels(
|
|
167
|
+
models: Array<string | { model: string; minEffort?: ThinkingLevel }>,
|
|
168
|
+
): void {
|
|
145
169
|
disabledForModelsCache = models;
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
// ============== 工具清单序列化 ==============
|
|
173
|
+
/**
|
|
174
|
+
* stableStringify - 稳定序列化对象(键排序)
|
|
175
|
+
*
|
|
176
|
+
* 用于序列化工具参数,确保参数对象的键顺序一致,便于对比和展示。
|
|
177
|
+
*
|
|
178
|
+
* 功能说明:
|
|
179
|
+
* - 处理 null 和非对象类型:直接 JSON.stringify
|
|
180
|
+
* - 处理数组:递归序列化每个元素,undefined 替换为 "null"
|
|
181
|
+
* - 处理对象:键排序后序列化,跳过 undefined 值
|
|
182
|
+
*
|
|
183
|
+
* @param value - 要序列化的值
|
|
184
|
+
* @returns 稳定的 JSON 字符串(键排序)
|
|
185
|
+
*/
|
|
149
186
|
function stableStringify(value: unknown): string {
|
|
150
187
|
if (value === null || typeof value !== "object") {
|
|
151
188
|
return JSON.stringify(value);
|
|
@@ -165,7 +202,10 @@ function stableStringify(value: unknown): string {
|
|
|
165
202
|
|
|
166
203
|
function buildInventoryBlock(tools: ToolInfo[]): string {
|
|
167
204
|
return tools
|
|
168
|
-
.map(
|
|
205
|
+
.map(
|
|
206
|
+
(t) =>
|
|
207
|
+
`### ${t.name}\n${t.description}\n\nParameters: ${stableStringify(t.parameters)}`,
|
|
208
|
+
)
|
|
169
209
|
.join("\n\n---\n\n");
|
|
170
210
|
}
|
|
171
211
|
|
|
@@ -173,7 +213,9 @@ function stripInflightAdvisorCall(messages: Message[]): Message[] {
|
|
|
173
213
|
if (messages.length === 0) return messages;
|
|
174
214
|
const last = messages[messages.length - 1];
|
|
175
215
|
if (last.role !== "assistant") return messages;
|
|
176
|
-
const filtered = last.content.filter(
|
|
216
|
+
const filtered = last.content.filter(
|
|
217
|
+
(c) => !(c.type === "toolCall" && c.name === ADVISOR_TOOL_NAME),
|
|
218
|
+
);
|
|
177
219
|
if (filtered.length === last.content.length) return messages;
|
|
178
220
|
if (filtered.length === 0) return messages.slice(0, -1);
|
|
179
221
|
return [...messages.slice(0, -1), { ...last, content: filtered }];
|
|
@@ -193,7 +235,9 @@ function ensureUserTailForAdvisor(messages: Message[]): Message[] {
|
|
|
193
235
|
|
|
194
236
|
function getInventoryMessage(tools: ToolInfo[]): Message | undefined {
|
|
195
237
|
if (tools.length === 0) return undefined;
|
|
196
|
-
const sorted = [...tools].sort((a, b) =>
|
|
238
|
+
const sorted = [...tools].sort((a, b) =>
|
|
239
|
+
a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
|
|
240
|
+
);
|
|
197
241
|
const text = `## Available Executor Tools\n\n${buildInventoryBlock(sorted)}`;
|
|
198
242
|
return {
|
|
199
243
|
role: "user",
|
|
@@ -215,7 +259,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
|
|
|
215
259
|
const model = ctx.modelRegistry.find(parsed.provider, parsed.modelId);
|
|
216
260
|
if (!model) {
|
|
217
261
|
if (ctx.hasUI) {
|
|
218
|
-
ctx.ui.notify(
|
|
262
|
+
ctx.ui.notify(
|
|
263
|
+
`Previously configured advisor model ${config.modelKey} is no longer available`,
|
|
264
|
+
"warning",
|
|
265
|
+
);
|
|
219
266
|
}
|
|
220
267
|
return;
|
|
221
268
|
}
|
|
@@ -227,7 +274,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
|
|
|
227
274
|
|
|
228
275
|
if (isExecutorBlocked(ctx, pi.getThinkingLevel())) {
|
|
229
276
|
if (ctx.hasUI) {
|
|
230
|
-
ctx.ui.notify(
|
|
277
|
+
ctx.ui.notify(
|
|
278
|
+
`Advisor restored: ${model.provider}:${model.id} (inactive for current executor)`,
|
|
279
|
+
"info",
|
|
280
|
+
);
|
|
231
281
|
}
|
|
232
282
|
return;
|
|
233
283
|
}
|
|
@@ -238,7 +288,10 @@ function restoreAdvisorState(ctx: ExtensionContext, pi: ExtensionAPI): void {
|
|
|
238
288
|
}
|
|
239
289
|
|
|
240
290
|
if (ctx.hasUI) {
|
|
241
|
-
ctx.ui.notify(
|
|
291
|
+
ctx.ui.notify(
|
|
292
|
+
`Advisor restored: ${model.provider}:${model.id}${config.effort ? `, ${config.effort}` : ""}`,
|
|
293
|
+
"info",
|
|
294
|
+
);
|
|
242
295
|
}
|
|
243
296
|
}
|
|
244
297
|
|
|
@@ -259,7 +312,9 @@ function buildErrorResult(
|
|
|
259
312
|
const effort = getAdvisorEffort();
|
|
260
313
|
return {
|
|
261
314
|
content: [{ type: "text", text: userText }],
|
|
262
|
-
details: advisorLabel
|
|
315
|
+
details: advisorLabel
|
|
316
|
+
? { advisorModel: advisorLabel, effort, errorMessage }
|
|
317
|
+
: { effort, errorMessage },
|
|
263
318
|
};
|
|
264
319
|
}
|
|
265
320
|
|
|
@@ -278,22 +333,39 @@ async function executeAdvisor(
|
|
|
278
333
|
|
|
279
334
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(advisor);
|
|
280
335
|
if (!auth.ok) {
|
|
281
|
-
return buildErrorResult(
|
|
336
|
+
return buildErrorResult(
|
|
337
|
+
advisorLabel,
|
|
338
|
+
`Advisor (${advisorLabel}) is misconfigured: ${auth.error}`,
|
|
339
|
+
auth.error,
|
|
340
|
+
);
|
|
282
341
|
}
|
|
283
342
|
if (!auth.apiKey) {
|
|
284
|
-
return buildErrorResult(
|
|
343
|
+
return buildErrorResult(
|
|
344
|
+
advisorLabel,
|
|
345
|
+
`Advisor (${advisorLabel}) has no API key available.`,
|
|
346
|
+
`no API key for ${advisor.provider}`,
|
|
347
|
+
);
|
|
285
348
|
}
|
|
286
349
|
|
|
287
350
|
const { messages: sessionMessages } = buildSessionContext(
|
|
288
351
|
ctx.sessionManager.getEntries(),
|
|
289
352
|
ctx.sessionManager.getLeafId(),
|
|
290
353
|
);
|
|
291
|
-
const branchMessages = ensureUserTailForAdvisor(
|
|
354
|
+
const branchMessages = ensureUserTailForAdvisor(
|
|
355
|
+
stripInflightAdvisorCall(convertToLlm(sessionMessages)),
|
|
356
|
+
);
|
|
292
357
|
const inventoryMessage = getInventoryMessage(pi.getAllTools());
|
|
293
|
-
const messages: Message[] = inventoryMessage
|
|
358
|
+
const messages: Message[] = inventoryMessage
|
|
359
|
+
? [inventoryMessage, ...branchMessages]
|
|
360
|
+
: branchMessages;
|
|
294
361
|
|
|
295
362
|
onUpdate?.({
|
|
296
|
-
content: [
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: `Consulting advisor (${advisorLabel}${effort ? `, ${effort}` : ""})…`,
|
|
367
|
+
},
|
|
368
|
+
],
|
|
297
369
|
details: { advisorModel: advisorLabel, effort },
|
|
298
370
|
});
|
|
299
371
|
|
|
@@ -319,7 +391,12 @@ async function executeAdvisor(
|
|
|
319
391
|
|
|
320
392
|
if (response.stopReason === "error") {
|
|
321
393
|
return {
|
|
322
|
-
content: [
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: `Advisor call failed: ${response.errorMessage}`,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
323
400
|
details: {
|
|
324
401
|
advisorModel: advisorLabel,
|
|
325
402
|
effort,
|
|
@@ -331,8 +408,11 @@ async function executeAdvisor(
|
|
|
331
408
|
}
|
|
332
409
|
|
|
333
410
|
const advisorText = response.content
|
|
334
|
-
.filter(
|
|
335
|
-
|
|
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)
|
|
336
416
|
.join("\n")
|
|
337
417
|
.trim();
|
|
338
418
|
|
|
@@ -360,7 +440,11 @@ async function executeAdvisor(
|
|
|
360
440
|
};
|
|
361
441
|
} catch (err) {
|
|
362
442
|
const message = err instanceof Error ? err.message : String(err);
|
|
363
|
-
return buildErrorResult(
|
|
443
|
+
return buildErrorResult(
|
|
444
|
+
advisorLabel,
|
|
445
|
+
`Advisor call threw: ${message}`,
|
|
446
|
+
message,
|
|
447
|
+
);
|
|
364
448
|
}
|
|
365
449
|
}
|
|
366
450
|
|
|
@@ -428,7 +512,10 @@ function modelKey(m: { provider: string; id: string }): string {
|
|
|
428
512
|
return `${m.provider}:${m.id}`;
|
|
429
513
|
}
|
|
430
514
|
|
|
431
|
-
function isModelBlocked(
|
|
515
|
+
function isModelBlocked(
|
|
516
|
+
model: Model<Api> | undefined,
|
|
517
|
+
thinkingLevel?: string,
|
|
518
|
+
): boolean {
|
|
432
519
|
if (!model) return false;
|
|
433
520
|
const key = modelKey(model);
|
|
434
521
|
for (const entry of disabledForModelsCache) {
|
|
@@ -438,14 +525,19 @@ function isModelBlocked(model: Model<Api> | undefined, thinkingLevel?: string):
|
|
|
438
525
|
if (entry.model !== key) continue;
|
|
439
526
|
if (entry.minEffort === undefined) return true;
|
|
440
527
|
const thresholdOrdinal = EFFORT_ORDINAL.indexOf(entry.minEffort);
|
|
441
|
-
const executorOrdinal = EFFORT_ORDINAL.indexOf(
|
|
528
|
+
const executorOrdinal = EFFORT_ORDINAL.indexOf(
|
|
529
|
+
thinkingLevel as ThinkingLevel,
|
|
530
|
+
);
|
|
442
531
|
if (executorOrdinal >= thresholdOrdinal) return true;
|
|
443
532
|
}
|
|
444
533
|
}
|
|
445
534
|
return false;
|
|
446
535
|
}
|
|
447
536
|
|
|
448
|
-
function isExecutorBlocked(
|
|
537
|
+
function isExecutorBlocked(
|
|
538
|
+
ctx: ExtensionContext,
|
|
539
|
+
thinkingLevel?: string,
|
|
540
|
+
): boolean {
|
|
449
541
|
return isModelBlocked(ctx?.model, thinkingLevel);
|
|
450
542
|
}
|
|
451
543
|
|
|
@@ -501,7 +593,8 @@ const EFFORT_HEADER_PROSE =
|
|
|
501
593
|
|
|
502
594
|
function selectListTheme(theme: Theme) {
|
|
503
595
|
return {
|
|
504
|
-
selectedPrefix: (t: string) =>
|
|
596
|
+
selectedPrefix: (t: string) =>
|
|
597
|
+
theme.bg("selectedBg", theme.fg("accent", t)),
|
|
505
598
|
selectedText: (t: string) => theme.bg("selectedBg", theme.bold(t)),
|
|
506
599
|
description: (t: string) => theme.fg("muted", t),
|
|
507
600
|
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
@@ -509,7 +602,13 @@ function selectListTheme(theme: Theme) {
|
|
|
509
602
|
};
|
|
510
603
|
}
|
|
511
604
|
|
|
512
|
-
function buildSelectPanel(
|
|
605
|
+
function buildSelectPanel(
|
|
606
|
+
theme: Theme,
|
|
607
|
+
title: string,
|
|
608
|
+
proseLines: string[],
|
|
609
|
+
selectList: SelectList,
|
|
610
|
+
helpLines?: string[],
|
|
611
|
+
): Container {
|
|
513
612
|
const container = new Container();
|
|
514
613
|
const border = () => new DynamicBorder((s: string) => theme.fg("accent", s));
|
|
515
614
|
|
|
@@ -521,17 +620,19 @@ function buildSelectPanel(theme: Theme, title: string, proseLines: string[], sel
|
|
|
521
620
|
container.addChild(new Text(line, 1, 0));
|
|
522
621
|
container.addChild(new Spacer(1));
|
|
523
622
|
}
|
|
524
|
-
|
|
623
|
+
|
|
525
624
|
// 添加帮助信息(如果有)
|
|
526
625
|
if (helpLines && helpLines.length > 0) {
|
|
527
|
-
container.addChild(
|
|
626
|
+
container.addChild(
|
|
627
|
+
new Text(theme.fg("accent", theme.bold(helpLines[0])), 1, 0),
|
|
628
|
+
);
|
|
528
629
|
container.addChild(new Spacer(1));
|
|
529
630
|
for (let i = 1; i < helpLines.length; i++) {
|
|
530
631
|
container.addChild(new Text(helpLines[i], 1, 0));
|
|
531
632
|
}
|
|
532
633
|
container.addChild(new Spacer(1));
|
|
533
634
|
}
|
|
534
|
-
|
|
635
|
+
|
|
535
636
|
container.addChild(selectList);
|
|
536
637
|
container.addChild(new Spacer(1));
|
|
537
638
|
container.addChild(new Text(theme.fg("dim", NAV_HINT), 1, 0));
|
|
@@ -545,7 +646,11 @@ function showAdvisorPicker(
|
|
|
545
646
|
items: SelectItem[],
|
|
546
647
|
): Promise<string | null> {
|
|
547
648
|
return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
548
|
-
const selectList = new SelectList(
|
|
649
|
+
const selectList = new SelectList(
|
|
650
|
+
items,
|
|
651
|
+
Math.min(items.length, MAX_VISIBLE_ROWS),
|
|
652
|
+
selectListTheme(theme),
|
|
653
|
+
);
|
|
549
654
|
selectList.onSelect = (item) => done(item.value);
|
|
550
655
|
selectList.onCancel = () => done(null);
|
|
551
656
|
|
|
@@ -575,15 +680,28 @@ function showEffortPicker(
|
|
|
575
680
|
defaultEffort?: ThinkingLevel,
|
|
576
681
|
): Promise<string | null> {
|
|
577
682
|
return ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
578
|
-
const selectList = new SelectList(
|
|
579
|
-
|
|
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;
|
|
580
691
|
selectList.setSelectedIndex(
|
|
581
|
-
preferredIdx >= 0
|
|
692
|
+
preferredIdx >= 0
|
|
693
|
+
? preferredIdx
|
|
694
|
+
: items.findIndex((item) => item.value === defaultEffort),
|
|
582
695
|
);
|
|
583
696
|
selectList.onSelect = (item) => done(item.value);
|
|
584
697
|
selectList.onCancel = () => done(null);
|
|
585
698
|
|
|
586
|
-
const container = buildSelectPanel(
|
|
699
|
+
const container = buildSelectPanel(
|
|
700
|
+
theme,
|
|
701
|
+
EFFORT_HEADER_TITLE,
|
|
702
|
+
[EFFORT_HEADER_PROSE],
|
|
703
|
+
selectList,
|
|
704
|
+
);
|
|
587
705
|
|
|
588
706
|
return {
|
|
589
707
|
render: (w: number) => container.render(w),
|
|
@@ -617,7 +735,8 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
|
|
|
617
735
|
});
|
|
618
736
|
items.push({
|
|
619
737
|
value: NO_ADVISOR_VALUE,
|
|
620
|
-
label:
|
|
738
|
+
label:
|
|
739
|
+
currentKey === undefined ? `No advisor${CHECKMARK}` : "No advisor",
|
|
621
740
|
});
|
|
622
741
|
|
|
623
742
|
const choice = await showAdvisorPicker(ctx, items);
|
|
@@ -659,15 +778,26 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
|
|
|
659
778
|
{ value: OFF_VALUE, label: "off" },
|
|
660
779
|
...levels.map((level) => ({
|
|
661
780
|
value: level,
|
|
662
|
-
label:
|
|
781
|
+
label:
|
|
782
|
+
level === DEFAULT_EFFORT
|
|
783
|
+
? `${level}${RECOMMENDED_EFFORT_SUFFIX}`
|
|
784
|
+
: level,
|
|
663
785
|
})),
|
|
664
786
|
];
|
|
665
787
|
|
|
666
|
-
const effortResult = await showEffortPicker(
|
|
788
|
+
const effortResult = await showEffortPicker(
|
|
789
|
+
ctx,
|
|
790
|
+
effortItems,
|
|
791
|
+
getAdvisorEffort(),
|
|
792
|
+
DEFAULT_EFFORT,
|
|
793
|
+
);
|
|
667
794
|
if (!effortResult) {
|
|
668
795
|
return;
|
|
669
796
|
}
|
|
670
|
-
effortChoice =
|
|
797
|
+
effortChoice =
|
|
798
|
+
effortResult === OFF_VALUE
|
|
799
|
+
? undefined
|
|
800
|
+
: (effortResult as ThinkingLevel);
|
|
671
801
|
}
|
|
672
802
|
|
|
673
803
|
if (!saveAdvisorConfig(modelKey(picked), effortChoice)) {
|
|
@@ -680,15 +810,21 @@ function registerAdvisorCommand(pi: ExtensionAPI): void {
|
|
|
680
810
|
const activeToolsNow = pi.getActiveTools();
|
|
681
811
|
const activeHasNow = activeToolsNow.includes(ADVISOR_TOOL_NAME);
|
|
682
812
|
const blocked = isExecutorBlocked(ctx, pi.getThinkingLevel());
|
|
683
|
-
|
|
813
|
+
|
|
684
814
|
if (!activeHasNow && !blocked) {
|
|
685
815
|
pi.setActiveTools([...activeToolsNow, ADVISOR_TOOL_NAME]);
|
|
686
816
|
}
|
|
687
|
-
|
|
817
|
+
|
|
688
818
|
if (blocked) {
|
|
689
|
-
ctx.ui.notify(
|
|
819
|
+
ctx.ui.notify(
|
|
820
|
+
`Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""} (inactive for current executor)`,
|
|
821
|
+
"info",
|
|
822
|
+
);
|
|
690
823
|
} else {
|
|
691
|
-
ctx.ui.notify(
|
|
824
|
+
ctx.ui.notify(
|
|
825
|
+
`Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""}`,
|
|
826
|
+
"info",
|
|
827
|
+
);
|
|
692
828
|
}
|
|
693
829
|
},
|
|
694
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
|
+
}
|