@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.
Files changed (144) hide show
  1. package/bin/cli.js +116 -74
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +42 -24
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/cases/08-cwd-resilience.sh +84 -0
  38. package/scripts/regression/lib/common.sh +287 -0
  39. package/scripts/regression/results/.last_pid +1 -0
  40. package/scripts/regression/results/.last_workdir +1 -0
  41. package/scripts/regression/results/02-extension-loading.log +19 -0
  42. package/scripts/regression/results/03-prompt-stream.log +16 -0
  43. package/scripts/regression/results/03-rpc-protocol.log +28 -0
  44. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  45. package/scripts/regression/results/05-tool-execution.log +8 -0
  46. package/scripts/regression/results/06-pi-new-features.log +27 -0
  47. package/scripts/regression/results/08-cwd-resilience.log +1 -0
  48. package/scripts/regression/run-all.sh +187 -0
  49. package/config/agent/bin/fd.exe +0 -0
  50. package/config/agent/bin/rg.exe +0 -0
  51. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  52. package/config/agent/extensions/aicode-snake.ts +0 -354
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  57. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  58. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  60. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  106. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  107. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  108. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  143. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  144. /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.session.model?.name || "未知";
59
- const workspace = ctx.session.workspace || "未知";
60
- const messages = ctx.session.messages?.length || 0;
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; // ← 保存 UI context
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("清理会话", `确定要清理当前会话的所有 ${msgCount} 条消息吗?`);
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(`✅ 已清理 ${msgCount} 条消息,进入新会话`, "success");
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("更新确认", "确定要更新 aicode-cli 吗?");
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, stdout, stderr) => {
147
+ exec("npm install -g @ia-ccun/code-agent-cli", (error) => {
147
148
  if (error) {
148
- uiContext.notify(`❌ 更新失败:${error.message},请重试`, "error");
149
+ uiContext.notify(
150
+ `❌ 更新失败:${error.message},请重试`,
151
+ "error",
152
+ );
149
153
  } else {
150
- uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "success");
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", { encoding: "utf8" }).trim();
195
- latestVersion = execSync(`npm view @ia-ccun/code-agent-cli version --registry=${registry}`, { encoding: "utf8" }).trim();
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 = latestParts[0] > localParts[0] ||
201
- (latestParts[0] === localParts[0] && latestParts[1] > localParts[1]) ||
202
- (latestParts[0] === localParts[0] && latestParts[1] === localParts[1] && latestParts[2] > localParts[2]);
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(date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
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(new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
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(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
47
- const diffDays = Math.floor((now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24));
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(message: string, context?: Record<string, unknown>): void {
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 = typeof result === "string" ? result : JSON.stringify(result);
102
+ const errorMsg =
103
+ typeof result === "string" ? result : JSON.stringify(result);
92
104
 
93
105
  // Append to error log file
94
- appendToErrorLog(
95
- `Tool Execution Failed: ${toolName}`,
96
- {
97
- toolCallId,
98
- toolName,
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, result } = event;
108
-
109
- if (result?.exitCode !== 0) {
110
- bashErrorCount++;
111
-
112
- // Append to error log file
113
- appendToErrorLog(
114
- `Bash Command Failed: ${command} - exit code ${result?.exitCode}`,
115
- {
116
- command,
117
- exitCode: result?.exitCode,
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
- if (params.status !== "complete") {
303
- return { content: [{ type: "text", text: "update_goal 仅接受 status=complete。" }], isError: true };
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: usage stats, response content, stop reason
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 { appendFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
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(date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
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(new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
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(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
55
- const diffDays = Math.floor((now.getTime() - fileDate.getTime()) / (1000 * 60 * 60 * 24));
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: any): string {
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.map((item: any) => {
82
- if (item.type === "text") {
83
- return item.text || "";
84
- } else if (item.type === "image") {
85
- return `[image: ${item.source?.type || "unknown"}]`;
86
- } else if (item.type === "tool_use") {
87
- return `[tool_use: ${item.name || "unknown"}]`;
88
- } else if (item.type === "tool_result") {
89
- return `[tool_result: ${item.tool_use_id || ""}]`;
90
- }
91
- return JSON.stringify(item);
92
- }).join("\n");
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
- // Filter messages to only include user and assistant
99
- function filterMessages(messages: any[]): any[] {
132
+ function filterMessages(
133
+ messages: unknown,
134
+ ): Array<{ role?: string; content?: unknown }> {
100
135
  if (!Array.isArray(messages)) return [];
101
- return messages.filter((msg) => ALLOWED_ROLES.includes(msg.role));
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 { provider, model, payload } = event;
108
-
109
- // Filter messages to only user and assistant
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?.length || 0;
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 || "unknown"}
118
- Model: ${model || "unknown"}
159
+ Provider: ${provider}
160
+ Model: ${model}
119
161
  Messages: ${filteredMessages.length} (filtered from ${originalCount}, showing user/assistant only)
120
162
 
121
163
  --- Messages ---
122
- ${filteredMessages.map((msg: any, i: number) =>
123
- `[${i}] ${formatMessage(msg)}`
124
- ).join("\n\n") || "No messages"}
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
- maxTokens: payload.max_tokens,
129
- temperature: payload.temperature,
130
- topP: payload.top_p,
131
- topK: payload.top_k,
132
- stopSequences: payload.stop_sequences,
133
- thinking: payload.thinking ? "enabled" : "disabled",
134
- budgetTokens: payload.budget_tokens
135
- }, null, 2)}
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 { response, usage, cached } = event;
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
- Cached: ${cached ? "YES" : "NO"}
172
-
173
- --- Usage Stats ---
174
- ${JSON.stringify(usage, null, 2)}
204
+ Status: ${status}
175
205
 
176
- --- Response Content ---
177
- ${response.content?.map((c: any, i: number) => {
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
  }