@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +97 -69
- package/config/agent/extensions/aicode-advisor.ts +187 -64
- package/config/agent/extensions/aicode-banner.ts +101 -0
- package/config/agent/extensions/aicode-btw.ts +619 -182
- package/config/agent/extensions/aicode-cd.ts +246 -63
- package/config/agent/extensions/aicode-pets.ts +0 -11
- package/config/agent/extensions/aicode-plan.ts +2 -0
- package/config/agent/extensions/aicode-safety-guard.ts +3 -0
- package/config/agent/extensions/aicode-tips.ts +1 -16
- package/config/agent/extensions/context-monitor.ts +30 -15
- package/config/agent/extensions/context.ts +132 -60
- package/config/agent/extensions/custom-footer.ts +0 -5
- package/config/agent/extensions/custom.ts +41 -29
- package/config/agent/extensions/error-monitor.ts +37 -33
- package/config/agent/extensions/goal.ts +8 -6
- package/config/agent/extensions/msg-debug.ts +103 -112
- package/config/agent/extensions/review.ts +365 -164
- package/config/agent/extensions/working-msg.ts +2 -4
- package/config/agent/settings.json +15 -14
- package/config/agent/skills/hello/SKILL.md +1 -1
- package/config/agent/skills/refactor/SKILL.md +40 -0
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +8 -1
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +9 -2
- package/dist/config-loader.js.map +1 -1
- package/package.json +6 -7
- package/scripts/regression/README.md +224 -0
- package/scripts/regression/cases/01-version-and-deps.sh +94 -0
- package/scripts/regression/cases/02-extension-loading.sh +130 -0
- package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
- package/scripts/regression/cases/04-prompt-stream.sh +130 -0
- package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
- package/scripts/regression/cases/06-tool-execution.sh +115 -0
- package/scripts/regression/cases/07-pi-new-features.sh +141 -0
- package/scripts/regression/lib/common.sh +287 -0
- package/scripts/regression/results/.last_pid +1 -0
- package/scripts/regression/results/.last_workdir +1 -0
- package/scripts/regression/results/02-extension-loading.log +21 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +27 -0
- package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
- package/scripts/regression/results/05-tool-execution.log +8 -0
- package/scripts/regression/results/06-pi-new-features.log +27 -0
- package/scripts/regression/run-all.sh +187 -0
- package/config/agent/bin/fd.exe +0 -0
- package/config/agent/bin/rg.exe +0 -0
- package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
- package/config/agent/extensions/aicode-snake.ts +0 -354
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
- /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
- /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
- /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
- /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
- /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
- /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
- /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
- /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
- /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
|
@@ -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,21 +163,23 @@ 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
|
// ============== 工具清单序列化 ==============
|
|
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(
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
354
|
+
const branchMessages = ensureUserTailForAdvisor(
|
|
355
|
+
stripInflightAdvisorCall(convertToLlm(sessionMessages)),
|
|
356
|
+
);
|
|
305
357
|
const inventoryMessage = getInventoryMessage(pi.getAllTools());
|
|
306
|
-
const messages: Message[] = inventoryMessage
|
|
358
|
+
const messages: Message[] = inventoryMessage
|
|
359
|
+
? [inventoryMessage, ...branchMessages]
|
|
360
|
+
: branchMessages;
|
|
307
361
|
|
|
308
362
|
onUpdate?.({
|
|
309
|
-
content: [
|
|
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: [
|
|
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(
|
|
348
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
592
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
781
|
+
label:
|
|
782
|
+
level === DEFAULT_EFFORT
|
|
783
|
+
? `${level}${RECOMMENDED_EFFORT_SUFFIX}`
|
|
784
|
+
: level,
|
|
676
785
|
})),
|
|
677
786
|
];
|
|
678
787
|
|
|
679
|
-
const effortResult = await showEffortPicker(
|
|
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 =
|
|
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(
|
|
819
|
+
ctx.ui.notify(
|
|
820
|
+
`Advisor: ${modelKey(picked)}${effortChoice ? `, ${effortChoice}` : ""} (inactive for current executor)`,
|
|
821
|
+
"info",
|
|
822
|
+
);
|
|
703
823
|
} else {
|
|
704
|
-
ctx.ui.notify(
|
|
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
|
+
}
|