@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.4
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 +116 -74
- 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 +42 -24
- 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/cases/08-cwd-resilience.sh +84 -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 +19 -0
- package/scripts/regression/results/03-prompt-stream.log +16 -0
- package/scripts/regression/results/03-rpc-protocol.log +28 -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/results/08-cwd-resilience.log +1 -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,12 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
13
12
|
|
|
14
13
|
export default function (pi: ExtensionAPI) {
|
|
15
|
-
// === 记录开始时间 ===
|
|
16
|
-
let agentStartTime = 0;
|
|
17
|
-
|
|
18
14
|
// === 拦截工具调用 ===
|
|
19
15
|
|
|
20
16
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -33,13 +29,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
33
29
|
} catch (e) {
|
|
34
30
|
// 忽略错误(ctx 可能已过期)
|
|
35
31
|
}
|
|
32
|
+
return;
|
|
36
33
|
});
|
|
37
34
|
|
|
38
35
|
// === 注册自定义命令 ===
|
|
39
36
|
|
|
40
37
|
pi.registerCommand("hello", {
|
|
41
38
|
description: "打招呼命令",
|
|
42
|
-
usage: "/hello [名字]",
|
|
43
39
|
handler: async (args, ctx) => {
|
|
44
40
|
try {
|
|
45
41
|
const name = args || "大佬,您吃了吗";
|
|
@@ -55,15 +51,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
55
51
|
handler: async (_args, ctx) => {
|
|
56
52
|
try {
|
|
57
53
|
// 保存需要的数据,避免多次触发 getter
|
|
58
|
-
const model = ctx.
|
|
59
|
-
const
|
|
60
|
-
const messages =
|
|
61
|
-
|
|
54
|
+
const model = ctx.model?.name || "未知";
|
|
55
|
+
const entries = ctx.sessionManager.getEntries();
|
|
56
|
+
const messages = entries.filter((e) => e.type === "message").length;
|
|
57
|
+
|
|
62
58
|
const info = `
|
|
63
59
|
📊 会话状态
|
|
64
60
|
|
|
65
61
|
- 当前模型:${model}
|
|
66
|
-
- 项目目录:${workspace}
|
|
67
62
|
- 消息数量:${messages}
|
|
68
63
|
`.trim();
|
|
69
64
|
ctx.ui.notify(info, "info");
|
|
@@ -75,13 +70,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
75
70
|
|
|
76
71
|
pi.registerCommand("clear", {
|
|
77
72
|
description: "清理当前会话上下文",
|
|
78
|
-
usage: "/clear",
|
|
79
73
|
handler: async (_args, ctx) => {
|
|
80
74
|
try {
|
|
81
75
|
// 获取当前会话条目数(在 await 前完成所有 ctx 访问)
|
|
82
76
|
const entries = ctx.sessionManager.getBranch();
|
|
83
|
-
const msgCount = entries.filter(e => e.type === "message").length;
|
|
84
|
-
const uiContext = ctx.ui;
|
|
77
|
+
const msgCount = entries.filter((e) => e.type === "message").length;
|
|
78
|
+
const uiContext = ctx.ui; // ← 保存 UI context
|
|
85
79
|
|
|
86
80
|
if (msgCount === 0) {
|
|
87
81
|
uiContext.notify("会话已经是空的", "info");
|
|
@@ -89,7 +83,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
// 确认清理
|
|
92
|
-
const ok = await uiContext.confirm(
|
|
86
|
+
const ok = await uiContext.confirm(
|
|
87
|
+
"清理会话",
|
|
88
|
+
`确定要清理当前会话的所有 ${msgCount} 条消息吗?`,
|
|
89
|
+
);
|
|
93
90
|
if (!ok) {
|
|
94
91
|
uiContext.notify("已取消", "info");
|
|
95
92
|
return;
|
|
@@ -98,7 +95,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
98
95
|
// 创建新会话来清空上下文
|
|
99
96
|
await ctx.newSession({
|
|
100
97
|
withSession: async (newCtx) => {
|
|
101
|
-
newCtx.ui.notify(
|
|
98
|
+
newCtx.ui.notify(
|
|
99
|
+
`✅ 已清理 ${msgCount} 条消息,进入新会话`,
|
|
100
|
+
"info",
|
|
101
|
+
);
|
|
102
102
|
},
|
|
103
103
|
});
|
|
104
104
|
} catch (e) {
|
|
@@ -111,7 +111,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
111
|
|
|
112
112
|
pi.registerCommand("exit", {
|
|
113
113
|
description: "退出当前会话",
|
|
114
|
-
usage: "/exit",
|
|
115
114
|
handler: async (_args, ctx) => {
|
|
116
115
|
try {
|
|
117
116
|
ctx.ui.notify("👋 再见!", "info");
|
|
@@ -126,28 +125,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
126
125
|
|
|
127
126
|
pi.registerCommand("update", {
|
|
128
127
|
description: "更新 aicode-cli 工具",
|
|
129
|
-
usage: "/update",
|
|
130
128
|
handler: async (_args, ctx) => {
|
|
131
129
|
try {
|
|
132
130
|
// ← 在 await 前保存 UI context
|
|
133
131
|
const uiContext = ctx.ui;
|
|
134
|
-
|
|
135
|
-
const ok = await uiContext.confirm(
|
|
132
|
+
|
|
133
|
+
const ok = await uiContext.confirm(
|
|
134
|
+
"更新确认",
|
|
135
|
+
"确定要更新 aicode-cli 吗?",
|
|
136
|
+
);
|
|
136
137
|
if (!ok) {
|
|
137
138
|
uiContext.notify("已取消", "info");
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
uiContext.notify("🔄 正在更新 aicode-cli...", "info");
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
// 使用 Promise 包装 exec
|
|
144
145
|
const { exec } = await import("child_process");
|
|
145
146
|
await new Promise<void>((resolve) => {
|
|
146
|
-
exec("npm install -g @ia-ccun/code-agent-cli", (error
|
|
147
|
+
exec("npm install -g @ia-ccun/code-agent-cli", (error) => {
|
|
147
148
|
if (error) {
|
|
148
|
-
uiContext.notify(
|
|
149
|
+
uiContext.notify(
|
|
150
|
+
`❌ 更新失败:${error.message},请重试`,
|
|
151
|
+
"error",
|
|
152
|
+
);
|
|
149
153
|
} else {
|
|
150
|
-
uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "
|
|
154
|
+
uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "info");
|
|
151
155
|
}
|
|
152
156
|
resolve();
|
|
153
157
|
});
|
|
@@ -167,7 +171,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
167
171
|
|
|
168
172
|
pi.registerCommand("version", {
|
|
169
173
|
description: "查看当前版本和最新版本",
|
|
170
|
-
usage: "/version",
|
|
171
174
|
handler: async (_args, ctx) => {
|
|
172
175
|
const { execSync } = await import("child_process");
|
|
173
176
|
const { readFileSync, existsSync } = await import("fs");
|
|
@@ -191,15 +194,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
191
194
|
let latestVersion = "未知";
|
|
192
195
|
let updateMsg = "";
|
|
193
196
|
try {
|
|
194
|
-
const registry = execSync("npm config get registry", {
|
|
195
|
-
|
|
197
|
+
const registry = execSync("npm config get registry", {
|
|
198
|
+
encoding: "utf8",
|
|
199
|
+
}).trim();
|
|
200
|
+
latestVersion = execSync(
|
|
201
|
+
`npm view @ia-ccun/code-agent-cli version --registry=${registry}`,
|
|
202
|
+
{ encoding: "utf8" },
|
|
203
|
+
).trim();
|
|
196
204
|
|
|
197
205
|
// 比较版本
|
|
198
206
|
const localParts = localVersion.split(".").map(Number);
|
|
199
207
|
const latestParts = latestVersion.split(".").map(Number);
|
|
200
|
-
const needsUpdate =
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
const needsUpdate =
|
|
209
|
+
latestParts[0] > localParts[0] ||
|
|
210
|
+
(latestParts[0] === localParts[0] &&
|
|
211
|
+
latestParts[1] > localParts[1]) ||
|
|
212
|
+
(latestParts[0] === localParts[0] &&
|
|
213
|
+
latestParts[1] === localParts[1] &&
|
|
214
|
+
latestParts[2] > localParts[2]);
|
|
203
215
|
|
|
204
216
|
if (needsUpdate) {
|
|
205
217
|
updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行:npm install -g @ia-ccun/code-agent-cli`;
|
|
@@ -22,7 +22,9 @@ function formatBeijingTime(date: Date = new Date()): string {
|
|
|
22
22
|
|
|
23
23
|
// 格式化为文件名日期字符串 (北京时间)
|
|
24
24
|
function getLogFileName(date: Date = new Date()): string {
|
|
25
|
-
const beijingDate = new Date(
|
|
25
|
+
const beijingDate = new Date(
|
|
26
|
+
date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
27
|
+
);
|
|
26
28
|
const year = beijingDate.getFullYear();
|
|
27
29
|
const month = String(beijingDate.getMonth() + 1).padStart(2, "0");
|
|
28
30
|
const day = String(beijingDate.getDate()).padStart(2, "0");
|
|
@@ -34,7 +36,9 @@ function cleanOldLogs(): void {
|
|
|
34
36
|
if (!fs.existsSync(ERROR_LOG_DIR)) return;
|
|
35
37
|
|
|
36
38
|
const files = fs.readdirSync(ERROR_LOG_DIR);
|
|
37
|
-
const now = new Date(
|
|
39
|
+
const now = new Date(
|
|
40
|
+
new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
41
|
+
);
|
|
38
42
|
|
|
39
43
|
for (const file of files) {
|
|
40
44
|
if (!file.startsWith("aicode-error-") || !file.endsWith(".log")) continue;
|
|
@@ -43,8 +47,14 @@ function cleanOldLogs(): void {
|
|
|
43
47
|
const match = file.match(/aicode-error-(\d{4})-(\d{2})-(\d{2})\.log/);
|
|
44
48
|
if (!match) continue;
|
|
45
49
|
|
|
46
|
-
const fileDate = new Date(
|
|
47
|
-
|
|
50
|
+
const fileDate = new Date(
|
|
51
|
+
parseInt(match[1]),
|
|
52
|
+
parseInt(match[2]) - 1,
|
|
53
|
+
parseInt(match[3]),
|
|
54
|
+
);
|
|
55
|
+
const diffDays = Math.floor(
|
|
56
|
+
(now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24),
|
|
57
|
+
);
|
|
48
58
|
|
|
49
59
|
if (diffDays > KEEP_DAYS) {
|
|
50
60
|
fs.unlinkSync(path.join(ERROR_LOG_DIR, file));
|
|
@@ -63,7 +73,10 @@ function ensureLogDir(): void {
|
|
|
63
73
|
* @author: xujianjiang
|
|
64
74
|
* Append error message to log file
|
|
65
75
|
*/
|
|
66
|
-
function appendToErrorLog(
|
|
76
|
+
function appendToErrorLog(
|
|
77
|
+
message: string,
|
|
78
|
+
context?: Record<string, unknown>,
|
|
79
|
+
): void {
|
|
67
80
|
ensureLogDir();
|
|
68
81
|
cleanOldLogs(); // 每次写日志时清理过期文件
|
|
69
82
|
const timestamp = formatBeijingTime();
|
|
@@ -80,47 +93,38 @@ export default function (pi: ExtensionAPI) {
|
|
|
80
93
|
// Counter for error tracking
|
|
81
94
|
let toolErrorCount = 0;
|
|
82
95
|
let bashErrorCount = 0;
|
|
83
|
-
const startTime = Date.now();
|
|
84
|
-
|
|
85
96
|
// Monitor tool execution end for errors
|
|
86
97
|
pi.on("tool_execution_end", (event) => {
|
|
87
98
|
const { toolCallId, toolName, result, isError } = event;
|
|
88
99
|
|
|
89
100
|
if (isError) {
|
|
90
101
|
toolErrorCount++;
|
|
91
|
-
const errorMsg =
|
|
102
|
+
const errorMsg =
|
|
103
|
+
typeof result === "string" ? result : JSON.stringify(result);
|
|
92
104
|
|
|
93
105
|
// Append to error log file
|
|
94
|
-
appendToErrorLog(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
errorMessage: errorMsg?.substring(0, 500),
|
|
100
|
-
}
|
|
101
|
-
);
|
|
106
|
+
appendToErrorLog(`Tool Execution Failed: ${toolName}`, {
|
|
107
|
+
toolCallId,
|
|
108
|
+
toolName,
|
|
109
|
+
errorMessage: errorMsg?.substring(0, 500),
|
|
110
|
+
});
|
|
102
111
|
}
|
|
103
112
|
});
|
|
104
113
|
|
|
105
114
|
// Monitor user bash events for command failures
|
|
115
|
+
// pi 1.1.0: UserBashEvent 不再含 result 字段, 只在执行前发; 实际退出码在 tool_execution_end 监听
|
|
106
116
|
pi.on("user_bash", (event) => {
|
|
107
|
-
const { command,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
stderr: result?.stderr?.substring(0, 500),
|
|
119
|
-
stdout: result?.stdout?.substring(0, 500),
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
}
|
|
117
|
+
const { command, excludeFromContext, cwd } = event;
|
|
118
|
+
// 记录用户执行的命令(不判断成功/失败,失败已在 tool_execution_end 记录)
|
|
119
|
+
bashErrorCount++;
|
|
120
|
+
appendToErrorLog(
|
|
121
|
+
`Bash Command Executed: ${command}${excludeFromContext ? " (excluded from context)" : ""}`,
|
|
122
|
+
{
|
|
123
|
+
command,
|
|
124
|
+
cwd,
|
|
125
|
+
excludeFromContext,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
123
128
|
});
|
|
124
|
-
|
|
125
129
|
// Session shutdown - silent (errors already logged to file)
|
|
126
130
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Box, Spacer, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
|
|
4
4
|
const CUSTOM_TYPE = "aicode-goal";
|
|
@@ -161,8 +161,8 @@ function syncGoalTools(pi: ExtensionAPI) {
|
|
|
161
161
|
for (const name of GOAL_TOOL_NAMES) (want ? active.add(name) : active.delete(name));
|
|
162
162
|
pi.setActiveTools(Array.from(active));
|
|
163
163
|
}
|
|
164
|
-
|
|
165
|
-
function persist(pi: ExtensionAPI, ctx: ExtensionContext, next: GoalState | null) {
|
|
164
|
+
// pi 1.1.0: ExtensionToolContext (used in CustomTool.execute) extends ExtensionContext
|
|
165
|
+
function persist(pi: ExtensionAPI, ctx: ExtensionContext | ExtensionToolContext, next: GoalState | null) {
|
|
166
166
|
goal = next;
|
|
167
167
|
pi.appendEntry(CUSTOM_TYPE, { goal: next, statusBarEnabled });
|
|
168
168
|
updateStatusBar(ctx);
|
|
@@ -299,11 +299,13 @@ export default function piGoal(pi: ExtensionAPI) {
|
|
|
299
299
|
additionalProperties: false,
|
|
300
300
|
} as any,
|
|
301
301
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
// pi 1.1.0: AgentToolResult.details is required (T = JsonValue | undefined)
|
|
303
|
+
// pi 1.1.0: params 在 execute 里是 unknown, 需要 narrow
|
|
304
|
+
if ((params as { status?: string }).status !== "complete") {
|
|
305
|
+
return { content: [{ type: "text", text: "update_goal 仅接受 status=complete。" }], details: undefined, isError: true };
|
|
304
306
|
}
|
|
305
307
|
if (!goal) {
|
|
306
|
-
return { content: [{ type: "text", text: "未设置目标。" }], isError: true };
|
|
308
|
+
return { content: [{ type: "text", text: "未设置目标。" }], details: undefined, isError: true };
|
|
307
309
|
}
|
|
308
310
|
const now = Date.now();
|
|
309
311
|
const next: GoalState = { ...goal, status: "complete", updatedAt: now };
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @author: xujianjiang
|
|
3
3
|
* Msg Debug Extension - Log LLM request/response messages to file
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* This extension logs all requests and responses sent to/from the LLM provider
|
|
6
6
|
* for debugging purposes. Logs are saved to ~/.aicode-cli/logs/
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Features:
|
|
9
9
|
* - Logs before_provider_request: messages, parameters, raw payload
|
|
10
|
-
* - Logs after_provider_response:
|
|
11
|
-
* - Logs errors with context
|
|
12
|
-
* - Console output removed (logs to file only)
|
|
10
|
+
* - Logs after_provider_response: HTTP status, response headers
|
|
13
11
|
* - Session-based log files (one file per session)
|
|
12
|
+
*
|
|
13
|
+
* NOTE: pi 1.1.0 event payloads changed:
|
|
14
|
+
* - BeforeProviderRequestEvent now only has `payload: unknown` (no provider/model).
|
|
15
|
+
* Provider/model are read from `ctx.model` instead.
|
|
16
|
+
* - AfterProviderResponseEvent now only has `status: number, headers: Record<string,string>`.
|
|
17
|
+
* Response/usage/cached fields are gone (use `provider_stream_event` or message events).
|
|
18
|
+
* - The "error" event no longer exists.
|
|
14
19
|
*/
|
|
15
20
|
|
|
16
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
appendFileSync,
|
|
23
|
+
existsSync,
|
|
24
|
+
mkdirSync,
|
|
25
|
+
readdirSync,
|
|
26
|
+
unlinkSync,
|
|
27
|
+
} from "node:fs";
|
|
17
28
|
import { join } from "node:path";
|
|
18
29
|
import os from "node:os";
|
|
19
30
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -25,7 +36,9 @@ function formatBeijingTime(date: Date = new Date()): string {
|
|
|
25
36
|
|
|
26
37
|
// 格式化为文件名日期字符串 (北京时间)
|
|
27
38
|
function formatDateStr(date: Date = new Date()): string {
|
|
28
|
-
const beijingDate = new Date(
|
|
39
|
+
const beijingDate = new Date(
|
|
40
|
+
date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
41
|
+
);
|
|
29
42
|
const year = beijingDate.getFullYear();
|
|
30
43
|
const month = String(beijingDate.getMonth() + 1).padStart(2, "0");
|
|
31
44
|
const day = String(beijingDate.getDate()).padStart(2, "0");
|
|
@@ -46,19 +59,43 @@ function ensureLogDir(): void {
|
|
|
46
59
|
// 清理过期日志文件
|
|
47
60
|
function cleanOldLogs(): void {
|
|
48
61
|
if (!existsSync(LOG_DIR)) return;
|
|
49
|
-
const now = new Date(
|
|
62
|
+
const now = new Date(
|
|
63
|
+
new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
|
|
64
|
+
);
|
|
50
65
|
for (const file of readdirSync(LOG_DIR)) {
|
|
51
66
|
if (!file.startsWith("llm-requests-") || !file.endsWith(".log")) continue;
|
|
52
67
|
const match = file.match(/llm-requests-(\d{4})-(\d{2})-(\d{2})\.log/);
|
|
53
68
|
if (!match) continue;
|
|
54
|
-
const fileDate = new Date(
|
|
55
|
-
|
|
69
|
+
const fileDate = new Date(
|
|
70
|
+
parseInt(match[1]),
|
|
71
|
+
parseInt(match[2]) - 1,
|
|
72
|
+
parseInt(match[3]),
|
|
73
|
+
);
|
|
74
|
+
const diffDays = Math.floor(
|
|
75
|
+
(now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24),
|
|
76
|
+
);
|
|
56
77
|
if (diffDays > KEEP_DAYS) {
|
|
57
78
|
unlinkSync(join(LOG_DIR, file));
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
// 1.1.0 BeforeProviderRequestEvent.payload is `unknown`; narrow to a useful shape.
|
|
84
|
+
type ProviderPayload = {
|
|
85
|
+
messages?: Array<{ role?: string; content?: unknown }>;
|
|
86
|
+
max_tokens?: number;
|
|
87
|
+
temperature?: number;
|
|
88
|
+
top_p?: number;
|
|
89
|
+
top_k?: number;
|
|
90
|
+
stop_sequences?: string[];
|
|
91
|
+
thinking?: unknown;
|
|
92
|
+
budget_tokens?: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function isProviderPayload(value: unknown): value is ProviderPayload {
|
|
96
|
+
return typeof value === "object" && value !== null;
|
|
97
|
+
}
|
|
98
|
+
|
|
62
99
|
export default function (pi: ExtensionAPI) {
|
|
63
100
|
ensureLogDir();
|
|
64
101
|
cleanOldLogs();
|
|
@@ -69,156 +106,110 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
106
|
// Filter to only log user and assistant messages
|
|
70
107
|
const ALLOWED_ROLES = ["user", "assistant"];
|
|
71
108
|
|
|
72
|
-
function formatMessage(message:
|
|
109
|
+
function formatMessage(message: { role?: string; content?: unknown }): string {
|
|
73
110
|
if (!message) return "undefined";
|
|
74
|
-
|
|
75
111
|
const role = message.role || "unknown";
|
|
76
112
|
let content = "";
|
|
77
|
-
|
|
78
113
|
if (typeof message.content === "string") {
|
|
79
114
|
content = message.content;
|
|
80
115
|
} else if (Array.isArray(message.content)) {
|
|
81
|
-
content = message.content
|
|
82
|
-
|
|
83
|
-
return item.text || "";
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
content = message.content
|
|
117
|
+
.map((item: any) => {
|
|
118
|
+
if (item.type === "text") return item.text || "";
|
|
119
|
+
if (item.type === "image")
|
|
120
|
+
return `[image: ${item.source?.type || "unknown"}]`;
|
|
121
|
+
if (item.type === "tool_use")
|
|
122
|
+
return `[tool_use: ${item.name || "unknown"}]`;
|
|
123
|
+
if (item.type === "tool_result")
|
|
124
|
+
return `[tool_result: ${item.tool_use_id || ""}]`;
|
|
125
|
+
return JSON.stringify(item);
|
|
126
|
+
})
|
|
127
|
+
.join("\n");
|
|
93
128
|
}
|
|
94
|
-
|
|
95
129
|
return `[${role}]: ${content}`;
|
|
96
130
|
}
|
|
97
131
|
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
function filterMessages(
|
|
133
|
+
messages: unknown,
|
|
134
|
+
): Array<{ role?: string; content?: unknown }> {
|
|
100
135
|
if (!Array.isArray(messages)) return [];
|
|
101
|
-
return messages.filter((msg) =>
|
|
136
|
+
return (messages as Array<{ role?: string }>).filter((msg) =>
|
|
137
|
+
ALLOWED_ROLES.includes(msg.role ?? ""),
|
|
138
|
+
);
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
// Log before request is sent
|
|
105
|
-
pi.on("before_provider_request", (event) => {
|
|
142
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
106
143
|
const timestamp = formatBeijingTime();
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
144
|
+
const payload = isProviderPayload(event.payload) ? event.payload : {};
|
|
145
|
+
// Provider/model are no longer on the event payload in 1.1.0; read from ctx.
|
|
146
|
+
const modelId = (ctx.model as { id?: string } | undefined)?.id;
|
|
147
|
+
const provider = modelId?.split("/")[0] ?? "unknown";
|
|
148
|
+
const model = modelId ?? "unknown";
|
|
149
|
+
|
|
110
150
|
const filteredMessages = filterMessages(payload.messages);
|
|
111
|
-
const originalCount = payload.messages
|
|
112
|
-
|
|
151
|
+
const originalCount = Array.isArray(payload.messages)
|
|
152
|
+
? payload.messages.length
|
|
153
|
+
: 0;
|
|
154
|
+
|
|
113
155
|
const logEntry = `
|
|
114
156
|
================================================================================
|
|
115
157
|
[${timestamp}] BEFORE PROVIDER REQUEST
|
|
116
158
|
================================================================================
|
|
117
|
-
Provider: ${provider
|
|
118
|
-
Model: ${model
|
|
159
|
+
Provider: ${provider}
|
|
160
|
+
Model: ${model}
|
|
119
161
|
Messages: ${filteredMessages.length} (filtered from ${originalCount}, showing user/assistant only)
|
|
120
162
|
|
|
121
163
|
--- Messages ---
|
|
122
|
-
${
|
|
123
|
-
|
|
124
|
-
|
|
164
|
+
${
|
|
165
|
+
filteredMessages
|
|
166
|
+
.map((msg, i: number) => `[${i}] ${formatMessage(msg)}`)
|
|
167
|
+
.join("\n\n") || "No messages"
|
|
168
|
+
}
|
|
125
169
|
|
|
126
170
|
--- Request Parameters ---
|
|
127
|
-
${JSON.stringify(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
171
|
+
${JSON.stringify(
|
|
172
|
+
{
|
|
173
|
+
maxTokens: payload.max_tokens,
|
|
174
|
+
temperature: payload.temperature,
|
|
175
|
+
topP: payload.top_p,
|
|
176
|
+
topK: payload.top_k,
|
|
177
|
+
stopSequences: payload.stop_sequences,
|
|
178
|
+
thinking: payload.thinking ? "enabled" : "disabled",
|
|
179
|
+
budgetTokens: payload.budget_tokens,
|
|
180
|
+
},
|
|
181
|
+
null,
|
|
182
|
+
2,
|
|
183
|
+
)}
|
|
136
184
|
|
|
137
185
|
--- Raw Payload (filtered) ---
|
|
138
186
|
${JSON.stringify({ ...payload, messages: filteredMessages }, null, 2)}
|
|
139
187
|
|
|
140
188
|
================================================================================
|
|
141
189
|
`;
|
|
142
|
-
|
|
190
|
+
|
|
143
191
|
// Save to file
|
|
144
192
|
appendFileSync(logFile, logEntry, "utf8");
|
|
145
193
|
});
|
|
146
194
|
|
|
147
|
-
// Log after response is received
|
|
195
|
+
// Log after response is received (HTTP-level, not assistant message)
|
|
148
196
|
pi.on("after_provider_response", (event) => {
|
|
149
197
|
const timestamp = formatBeijingTime();
|
|
150
|
-
const {
|
|
151
|
-
|
|
152
|
-
// Check if response is defined
|
|
153
|
-
if (!response) {
|
|
154
|
-
const logEntry = `
|
|
155
|
-
================================================================================
|
|
156
|
-
[${timestamp}] AFTER PROVIDER RESPONSE
|
|
157
|
-
================================================================================
|
|
158
|
-
Cached: ${cached ? "YES" : "NO"}
|
|
159
|
-
Error: Response is undefined
|
|
198
|
+
const { status, headers } = event;
|
|
160
199
|
|
|
161
|
-
================================================================================
|
|
162
|
-
`;
|
|
163
|
-
appendFileSync(logFile, logEntry, "utf8");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
200
|
const logEntry = `
|
|
168
201
|
================================================================================
|
|
169
202
|
[${timestamp}] AFTER PROVIDER RESPONSE
|
|
170
203
|
================================================================================
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
--- Usage Stats ---
|
|
174
|
-
${JSON.stringify(usage, null, 2)}
|
|
204
|
+
Status: ${status}
|
|
175
205
|
|
|
176
|
-
--- Response
|
|
177
|
-
${
|
|
178
|
-
if (c.type === "text") {
|
|
179
|
-
return `[${i}] text: ${c.text}`;
|
|
180
|
-
} else if (c.type === "tool_use") {
|
|
181
|
-
return `[${i}] tool_use: ${c.name}(${JSON.stringify(c.input)})`;
|
|
182
|
-
}
|
|
183
|
-
return `[${i}] ${c.type}: ${JSON.stringify(c)}`;
|
|
184
|
-
}).join("\n") || "No content"}
|
|
185
|
-
|
|
186
|
-
--- Stop Reason ---
|
|
187
|
-
${response.stopReason || "unknown"}
|
|
188
|
-
|
|
189
|
-
--- Raw Response ---
|
|
190
|
-
${JSON.stringify(response, null, 2)}
|
|
206
|
+
--- Response Headers ---
|
|
207
|
+
${JSON.stringify(headers, null, 2)}
|
|
191
208
|
|
|
192
209
|
================================================================================
|
|
193
|
-
|
|
194
210
|
`;
|
|
195
|
-
|
|
196
|
-
// Save to file
|
|
197
|
-
appendFileSync(logFile, logEntry, "utf8");
|
|
198
|
-
});
|
|
199
211
|
|
|
200
|
-
// Log request errors
|
|
201
|
-
pi.on("error", (event) => {
|
|
202
|
-
const timestamp = formatBeijingTime();
|
|
203
|
-
const { error, context } = event;
|
|
204
|
-
|
|
205
|
-
const logEntry = `
|
|
206
|
-
################################################################################
|
|
207
|
-
[${timestamp}] ERROR
|
|
208
|
-
################################################################################
|
|
209
|
-
Error: ${error.message || String(error)}
|
|
210
|
-
Stack: ${error.stack || "No stack trace"}
|
|
211
|
-
|
|
212
|
-
Context:
|
|
213
|
-
${JSON.stringify(context, null, 2)}
|
|
214
|
-
|
|
215
|
-
################################################################################
|
|
216
|
-
|
|
217
|
-
`;
|
|
218
|
-
|
|
219
212
|
// Save to file
|
|
220
213
|
appendFileSync(logFile, logEntry, "utf8");
|
|
221
214
|
});
|
|
222
|
-
|
|
223
|
-
// Extension enabled silently (logs to file only, no console output)
|
|
224
215
|
}
|