@ia-ccun/code-agent-cli 0.0.51-beta.9 → 0.0.52-beta.1

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.
@@ -80,6 +80,10 @@ const EXECUTION_TRIGGER_PROMPT =
80
80
  // ============== 工具函数 ==============
81
81
 
82
82
  function isSafeReadOnlyCommand(command: string): boolean {
83
+ // 临时移除错误重定向 (如 2>/dev/null, 1>&2 等),避免误判
84
+ // 错误重定向是只读操作,不应被拦截
85
+ const normalizedCommand = command.replace(/\d+>[&]?\s*[^\s|;&]+/g, "").trim();
86
+
83
87
  const DESTRUCTIVE_PATTERNS = [
84
88
  /\brm\b/i,
85
89
  /\brmdir\b/i,
@@ -113,7 +117,8 @@ function isSafeReadOnlyCommand(command: string): boolean {
113
117
  /\bshutdown\b/i,
114
118
  /\bsystemctl\s+(start|stop|restart|enable|disable)\b/i,
115
119
  /\bservice\s+\S+\s+(start|stop|restart)\b/i,
116
- /\b(vim?|nano|emacs|code|subl)\b/i,
120
+ // 编辑器命令 - 只拦截命令开头的编辑器调用,不拦截参数中的匹配
121
+ /^\s*(vim?|nano|emacs|code|subl)\b/i,
117
122
  ];
118
123
 
119
124
  const SAFE_PATTERNS = [
@@ -165,9 +170,10 @@ function isSafeReadOnlyCommand(command: string): boolean {
165
170
  /^\s*fd\b/,
166
171
  ];
167
172
 
168
- const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command));
173
+ // 使用标准化后的命令进行检查(已移除错误重定向)
174
+ const isDestructive = DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(normalizedCommand));
169
175
  if (isDestructive) return false;
170
- return SAFE_PATTERNS.some((pattern) => pattern.test(command));
176
+ return SAFE_PATTERNS.some((pattern) => pattern.test(normalizedCommand));
171
177
  }
172
178
 
173
179
  function normalizeArg(input: string): string {
@@ -558,7 +564,7 @@ export default function aicodePlan(pi: ExtensionAPI) {
558
564
  }
559
565
 
560
566
  const statusText = planModeEnabled
561
- ? ctx.ui.theme.fg("warning", "⏸ 计划") + ctx.ui.theme.fg("dim", " | plan (CTRL+ALT+P)")
567
+ ? ctx.ui.theme.fg("warning", "⏸ 计划") + ctx.ui.theme.fg("dim", " | plan (Ctrl+Alt+P)")
562
568
  : undefined;
563
569
  ctx.ui.setStatus(STATUS_KEY, statusText);
564
570
  updateTodoWidget(ctx);
@@ -583,8 +589,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
583
589
  executionMode = false;
584
590
  pi.setActiveTools(planTools);
585
591
  planModeEnabled = true;
592
+ pi.events.emit("aicode-plan:mode-changed", { enabled: true });
586
593
  setStatus(ctx);
587
- pi.events.emit("aicode-plan:mode-changed", { planModeEnabled: true });
588
594
  ctx.ui.notify(`计划模式已启用(只读): ${planTools.join(", ")}`, "info");
589
595
  };
590
596
 
@@ -606,7 +612,7 @@ export default function aicodePlan(pi: ExtensionAPI) {
606
612
  }
607
613
 
608
614
  planModeEnabled = false;
609
- pi.events.emit("aicode-plan:mode-changed", { planModeEnabled: false });
615
+ pi.events.emit("aicode-plan:mode-changed", { enabled: false });
610
616
  restoreNormalTools();
611
617
  if (options.resetProgress) {
612
618
  executionMode = false;
@@ -658,7 +664,7 @@ export default function aicodePlan(pi: ExtensionAPI) {
658
664
  ];
659
665
  if (!planModeEnabled && !executionMode) {
660
666
  statusLines.push("");
661
- statusLines.push("快捷键:CTRL+ALT+P 快速切换计划模式");
667
+ statusLines.push("快捷键:Alt+Shift+P 快速切换计划模式");
662
668
  }
663
669
  ctx.ui.notify(statusLines.join("\n"), "info");
664
670
  return;
@@ -693,6 +699,7 @@ export default function aicodePlan(pi: ExtensionAPI) {
693
699
  pi.registerShortcut("ctrl+alt+p", {
694
700
  description: "切换计划模式",
695
701
  handler: async (ctx) => {
702
+ const wasEnabled = planModeEnabled; // 保存当前状态
696
703
  if (planModeEnabled) {
697
704
  exitPlanMode(ctx, "计划模式已禁用。返回直接执行模式。", {
698
705
  resetProgress: true,
@@ -700,7 +707,8 @@ export default function aicodePlan(pi: ExtensionAPI) {
700
707
  } else {
701
708
  enterPlanMode(ctx);
702
709
  }
703
- ctx.ui.notify(`计划模式已${planModeEnabled ? "禁用" : "启用"}`, "info");
710
+ // 显示切换后的状态
711
+ ctx.ui.notify(wasEnabled ? "计划模式已关闭 ✓" : "计划模式已开启 ✓");
704
712
  },
705
713
  });
706
714
 
@@ -10,11 +10,13 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
10
10
 
11
11
  const AICODE_TIPS = [
12
12
  "@ 快速搜索项目文件",
13
- "Ctrl+L 快速切换模型",
13
+ "Ctrl+L 快速切换备选模型,
14
14
  "Ctrl+P/Shift+Ctrl+P 切换范围模型",
15
- "Shift+Tab 切换思考级别",
15
+ "Shift+Tab 切换思考级别( 需 models.json配置 reasoning 启用)",
16
16
  "Ctrl+O 展开/折叠工具输出",
17
- "Ctrl+T 展开/折叠思考块",
17
+ "Ctrl+U 清空行内光标前的内容",
18
+ "Ctrl+K 清空行内光标后的内容",
19
+ "Ctrl+G 使用高级编辑器进行输入(如vscode), 适合手工输入多行、多段文字的场景",
18
20
  "Alt+Enter 队列跟进消息",
19
21
  "Shift+Enter 输入多行",
20
22
  "/ 查看所有命令",
@@ -34,6 +36,7 @@ const AICODE_TIPS = [
34
36
  "/resume 恢复会话",
35
37
  "/fork 创建会话新分支",
36
38
  "Ctrl+C 清空编辑器",
39
+ "CTRL+ALT+SHIFT+P 开启/关闭 计划模式(只读模式)",
37
40
  "双击 Esc 打开树视图",
38
41
  "拖拽文件到终端附加",
39
42
  // 扩展命令提示
@@ -43,6 +46,7 @@ const AICODE_TIPS = [
43
46
  "/clear 清理当前会话",
44
47
  "/quit 退出当前会话",
45
48
  // 常用 Prompt 提示
49
+ "解读下这个图片内容 /home/user/pic/plan.png",
46
50
  "/code-simplifier 自动简化刚修改的代码,提升清晰度和一致性",
47
51
  "/feat 新功能开发,支持需求规划→讨论迭代→执行实施完整流程(推荐使用)",
48
52
  "/git-commit 分析 Git 改动并生成 Conventional Commits 规范提交信息(推荐使用)",
@@ -94,11 +98,36 @@ const AICODE_TIPS = [
94
98
  "/btw 并行侧边对话,可在不中断主流程的情况下进行临时讨论或查询 ",
95
99
  "/rewind 回退到任意工具执行前的状态,支持快照恢复和撤销重做 ",
96
100
  "/mcp 连接现有的 MCP 服务器 (如 xx 平台、企业微信等等),扩展 AI 能力支持多种外部工具和服务 ",
101
+ // 新增扩展功能提示
102
+ "Alt+/ 或 Ctrl+Alt+W 快速切换到 BTW 侧边对话窗口",
103
+ "BTW 侧边对话:独立上下文,不影响主会话,适合临时查询和讨论",
104
+ "/advisor 可向更强的审查模型征求第二意见,用于复杂决策和方案评估 ",
105
+ "/plan 进入计划模式,强制只读分析流程,生成结构化实施计划(推荐使用)",
106
+ "计划模式禁止写入操作,确保方案经过充分分析后再执行",
107
+ "计划模式自动生成 Todo 列表,跟踪实施步骤和完成进度",
108
+ "/goal 设置长期目标,跟踪进度、时间和 token 消耗",
109
+ "/goal --tokens=50K 设置带 token 预算限制的目标",
110
+ "Goal 扩展支持 paused/resumed 状态,方便暂停和恢复长期任务 (推荐使用)",,
111
+ "Goal 扩展显示实时进度:已完成百分比、已用时间和 token 消耗 (推荐使用)",
112
+ "⚠️ 使用 advisor 获取第二视角评估,避免决策盲点",
113
+ "⚠️ 使用 plan 模式强制分析流程,避免冲动式编码(推荐使用)",
114
+ "⚠️ 使用 goal 跟踪长期任务,避免AI自以为是类的完成,AI若完成必须给出证据",
115
+ "⚠️ BTW 侧边对话适合临时讨论,不会污染主会话上下文",
116
+ // 使用场景和最佳实践
117
+ "💡 大型重构任务:先用 /plan 制定计划,再用 /advisor 评估方案风险",
118
+ "💡 长期开发任务:用 /goal 设置目标并跟踪进度,避免 token 超支",
119
+ "💡 临时查询需求:打开 BTW 侧边对话,不影响主会话",
120
+ "💡 复杂决策场景:用 advisor 工具获取第二视角,避免思维盲点",
121
+ "💡 代码审查流程:用 /skill:code-review + advisor 双重审查确保质量",
122
+ "💡 新功能开发:用 /feat prompt 启动完整需求规划流程",
123
+ "💡 性能优化:用 /skill:audit 全面审计界面质量和性能瓶颈",
124
+ "💡 安全审计:用 /skill:owasp-security 检查 OWASP Top 10 漏洞",
125
+ "💡 文档生成:用 /skill:writing-plans 生成详细实施文档",
126
+ "💡 测试覆盖:用 /skill:test-runner 编写和运行多框架测试",
127
+ "💡 会话整理:用 /clear 清理上下文,用 /compact 压缩历史消息",
97
128
  // 消息传递模式提示
98
129
  "Enter 插话消息 - 在工具执行后立即发送,中断 AI 思考流程",
99
- "Alt+Enter 排队消息 - 等待 AI 完全结束后发送,不中断 AI 工作",
100
130
  "Escape 中断当前 AI 并恢复编辑器中的排队消息",
101
- "Alt+Up 查看并编辑所有排队的消息",
102
131
  "插话消息会打断 AI 正在执行的工具,等待响应",
103
132
  "排队消息静默等待,AI 空闲时自动发送",
104
133
  "可在 /settings 中调整消息发送模式和行为偏好",
@@ -106,11 +135,18 @@ const AICODE_TIPS = [
106
135
  "💡 记忆功能自动记录会话中的关键经验,下次会话自动应用",
107
136
  "💡 在对话中说'记住这个'可手动保存经验到记忆库",
108
137
  "💡 记忆功能支持选择性注入 (selective),只注入相关记忆避免干扰",
109
- "💡 说'忘记 XXX'可删除特定记忆条目",
138
+ "💡 说'忘记 XXX '可删除特定记忆条目",
110
139
  "💡 记忆会在会话结束时自动整合优化,提炼更有价值的经验",
111
140
  "💡 记忆功能示例:记住代码规范后,后续开发自动遵循该规范",
112
141
  "💡 记忆功能示例:记录常见错误模式,主动避免重复犯错",
113
142
  "💡 记忆功能示例:保存项目特定配置,新会话自动加载上下文",
143
+ // 安全和最佳实践提示
144
+ "⚠️ 目前模型算力有限,部分任务可能存在排队等待的情况,若出现卡顿请稍后重试",
145
+ "⚠️ 执行 shell 命令前会自动检测是否为 safe command,非安全命令需要确认",
146
+ "⚠️ Banner 启动时显示当前版本、模型配置和常用命令,方便快速查看环境",
147
+ "⚠️ 使用 !! 前缀执行 bash 命令不会将输出发送给 AI,适合敏感操作",
148
+ // CodeGraph 使用方法(推荐使用)
149
+ "🔗 创建本地索引目录并构建知识图谱,操作说明 参考 UniDoc《2026.5 aicode+codegraph 安装与使用》 ",
114
150
  // Subagent 使用方法
115
151
  "🤖 说'使用 subagent 探索当前代码关于权限校验的实现'可快速定位相关代码",
116
152
  "🤖 说'使用 subagent 为这个功能制定实施计划'可生成详细的开发步骤",
@@ -119,6 +155,7 @@ const AICODE_TIPS = [
119
155
  "🤖 说'使用 subagent 给这个方案提点意见'可获得第二视角的评估建议",
120
156
  // 联系方式提示
121
157
  "⚠️ 任何疑问或需求可联系 xujianjiang,支持需求对接开发、系统集成与扩展",
158
+ "⚠️ aicode 图像识别参考 UniDoc《2026.03 AICode Agent CLI 编码智能体使用手册》的model.json模型配置",
122
159
  "⚠️ 内网手册参考 UniDoc《2026.03 AICode Agent CLI 编码智能体使用手册》",
123
160
  "⚠️ LLM API 4xx/5xx 错误时请确认模型配置是否正确,或在企微群咨询 jiangchi、maliang",
124
161
  ];
@@ -32,7 +32,7 @@ export default function (pi: ExtensionAPI) {
32
32
 
33
33
  // 监听 plan 模式变化事件(全局监听,只注册一次)
34
34
  pi.events.on("aicode-plan:mode-changed", (data: any) => {
35
- planModeEnabled = data.planModeEnabled;
35
+ planModeEnabled = data.enabled;
36
36
  footerTui?.requestRender();
37
37
  });
38
38
 
@@ -160,7 +160,6 @@ export default function (pi: ExtensionAPI) {
160
160
 
161
161
  // quots
162
162
  const quotes = [
163
- "代码是有灵魂的",
164
163
  "敬畏生产,双人复核,稳妥变更",
165
164
  "让代码更有温度",
166
165
  "民生所系,支付所向",
@@ -198,9 +197,9 @@ export default function (pi: ExtensionAPI) {
198
197
 
199
198
  // ── Plan mode segment ────────────────────────────────────
200
199
  if (planModeEnabled) {
201
- segments.push(theme.fg("success", "⏸ plan mode on"));
200
+ segments.push(theme.fg("success", "⏸ 计划模式"));
202
201
  }else{
203
- segments.push(theme.fg("error", "> yolo mode on"));
202
+ segments.push(theme.fg("error", "▶ 自动模式"));
204
203
  }
205
204
 
206
205
  segments.push(theme.fg("muted", quote));
@@ -2,7 +2,7 @@
2
2
  * @author: xujianjiang
3
3
  * 自定义扩展示例
4
4
  *
5
- * 放置位置: config/agent/extensions/
5
+ * 放置位置:config/agent/extensions/
6
6
  * 安装后会复制到 ~/.pi/agent/extensions/
7
7
  *
8
8
  * 可以拦截内置工具、注册自定义工具和命令
@@ -10,7 +10,6 @@
10
10
 
11
11
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
12
12
  import type { AssistantMessage } from "@earendil-works/pi-ai";
13
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
14
13
 
15
14
  export default function (pi: ExtensionAPI) {
16
15
  // === 记录开始时间 ===
@@ -51,16 +50,20 @@ export default function (pi: ExtensionAPI) {
51
50
  // === 拦截工具调用 ===
52
51
 
53
52
  pi.on("tool_call", async (event, ctx) => {
54
- // 拦截 login 工具 - 阻止执行
55
- if (event.toolName === "login") {
56
- await ctx.ui.notify("⚠️ login 功能已禁用", "warning");
57
- return { block: true, reason: "login 功能已禁用" };
58
- }
53
+ try {
54
+ // 拦截 login 工具 - 阻止执行
55
+ if (event.toolName === "login") {
56
+ await ctx.ui.notify("⚠️ login 功能已禁用", "warning");
57
+ return { block: true, reason: "login 功能已禁用" };
58
+ }
59
59
 
60
- // 拦截 logout 工具 - 阻止执行
61
- if (event.toolName === "logout") {
62
- await ctx.ui.notify("⚠️ logout 功能已禁用", "warning");
63
- return { block: true, reason: "logout 功能已禁用" };
60
+ // 拦截 logout 工具 - 阻止执行
61
+ if (event.toolName === "logout") {
62
+ await ctx.ui.notify("⚠️ logout 功能已禁用", "warning");
63
+ return { block: true, reason: "logout 功能已禁用" };
64
+ }
65
+ } catch (e) {
66
+ // 忽略错误(ctx 可能已过期)
64
67
  }
65
68
  });
66
69
 
@@ -70,22 +73,35 @@ export default function (pi: ExtensionAPI) {
70
73
  description: "打招呼命令",
71
74
  usage: "/hello [名字]",
72
75
  handler: async (args, ctx) => {
73
- const name = args || "大佬,您吃了吗";
74
- ctx.ui.notify(`你好,${name}!🤝`, "info");
76
+ try {
77
+ const name = args || "大佬,您吃了吗";
78
+ ctx.ui.notify(`你好,${name}!🤝`, "info");
79
+ } catch (e) {
80
+ // 忽略错误
81
+ }
75
82
  },
76
83
  });
77
84
 
78
85
  pi.registerCommand("status", {
79
86
  description: "显示当前会话状态",
80
87
  handler: async (_args, ctx) => {
81
- const info = `
88
+ try {
89
+ // 保存需要的数据,避免多次触发 getter
90
+ const model = ctx.session.model?.name || "未知";
91
+ const workspace = ctx.session.workspace || "未知";
92
+ const messages = ctx.session.messages?.length || 0;
93
+
94
+ const info = `
82
95
  📊 会话状态
83
96
 
84
- - 当前模型: ${ctx.session.model?.name || "未知"}
85
- - 项目目录: ${ctx.session.workspace || "未知"}
86
- - 消息数量: ${ctx.session.messages?.length || 0}
97
+ - 当前模型:${model}
98
+ - 项目目录:${workspace}
99
+ - 消息数量:${messages}
87
100
  `.trim();
88
- ctx.ui.notify(info, "info");
101
+ ctx.ui.notify(info, "info");
102
+ } catch (e) {
103
+ // 忽略错误(ctx 可能已过期)
104
+ }
89
105
  },
90
106
  });
91
107
 
@@ -93,35 +109,33 @@ export default function (pi: ExtensionAPI) {
93
109
  description: "清理当前会话上下文",
94
110
  usage: "/clear",
95
111
  handler: async (_args, ctx) => {
96
- // 获取当前会话条目数
97
- const entries = ctx.sessionManager.getBranch();
98
- const msgCount = entries.filter(e => e.type === "message").length;
99
-
100
- if (msgCount === 0) {
101
- ctx.ui.notify("会话已经是空的", "info");
102
- return;
103
- }
104
-
105
- // 确认清理
106
- const ok = await ctx.ui.confirm("清理会话", `确定要清理当前会话的所有 ${msgCount} 条消息吗?`);
107
- if (!ok) {
108
- ctx.ui.notify("已取消", "info");
109
- return;
110
- }
112
+ try {
113
+ // 获取当前会话条目数(在 await 前完成所有 ctx 访问)
114
+ const entries = ctx.sessionManager.getBranch();
115
+ const msgCount = entries.filter(e => e.type === "message").length;
116
+ const uiContext = ctx.ui; // ← 保存 UI context
117
+
118
+ if (msgCount === 0) {
119
+ uiContext.notify("会话已经是空的", "info");
120
+ return;
121
+ }
111
122
 
112
- // 创建新会话来清空上下文
113
- const result = await ctx.newSession({
114
- setup: async (_sm) => {
115
- // 新会话默认为空
116
- },
117
- });
123
+ // 确认清理
124
+ const ok = await uiContext.confirm("清理会话", `确定要清理当前会话的所有 ${msgCount} 条消息吗?`);
125
+ if (!ok) {
126
+ uiContext.notify("已取消", "info");
127
+ return;
128
+ }
118
129
 
119
- if (result.cancelled) {
120
- ctx.ui.notify("已取消", "info");
121
- return;
130
+ // 创建新会话来清空上下文
131
+ await ctx.newSession({
132
+ withSession: async (newCtx) => {
133
+ newCtx.ui.notify(`✅ 已清理 ${msgCount} 条消息,进入新会话`, "success");
134
+ },
135
+ });
136
+ } catch (e) {
137
+ // 忽略错误(ctx 可能已过期)
122
138
  }
123
-
124
- ctx.ui.notify(`✅ 已清理 ${msgCount} 条消息,进入新会话`, "success");
125
139
  },
126
140
  });
127
141
 
@@ -131,8 +145,12 @@ export default function (pi: ExtensionAPI) {
131
145
  description: "退出当前会话",
132
146
  usage: "/exit",
133
147
  handler: async (_args, ctx) => {
134
- ctx.ui.notify("👋 再见!", "info");
135
- ctx.shutdown();
148
+ try {
149
+ ctx.ui.notify("👋 再见!", "info");
150
+ ctx.shutdown();
151
+ } catch (e) {
152
+ // 忽略错误
153
+ }
136
154
  },
137
155
  });
138
156
 
@@ -142,28 +160,40 @@ export default function (pi: ExtensionAPI) {
142
160
  description: "更新 aicode-cli 工具",
143
161
  usage: "/update",
144
162
  handler: async (_args, ctx) => {
145
- const ok = await ctx.ui.confirm("更新确认", "确定要更新 aicode-cli 吗?");
146
- if (!ok) {
147
- ctx.ui.notify("已取消", "info");
148
- return;
149
- }
150
-
151
- ctx.ui.notify("🔄 正在更新 aicode-cli...", "info");
152
- const { exec } = await import("child_process");
153
- exec("npm install -g @ia-ccun/code-agent-cli", (error, stdout, stderr) => {
154
- if (error) {
155
- ctx.ui.notify(`❌ 更新失败: ${error.message},请重试`, "error");
156
- } else {
157
- ctx.ui.notify("✅ aicode-cli 更新成功!请重启 CLI", "success");
163
+ try {
164
+ // ← 在 await 前保存 UI context
165
+ const uiContext = ctx.ui;
166
+
167
+ const ok = await uiContext.confirm("更新确认", "确定要更新 aicode-cli 吗?");
168
+ if (!ok) {
169
+ uiContext.notify("已取消", "info");
170
+ return;
158
171
  }
159
- });
172
+
173
+ uiContext.notify("🔄 正在更新 aicode-cli...", "info");
174
+
175
+ // 使用 Promise 包装 exec
176
+ const { exec } = await import("child_process");
177
+ await new Promise<void>((resolve) => {
178
+ exec("npm install -g @ia-ccun/code-agent-cli", (error, stdout, stderr) => {
179
+ if (error) {
180
+ uiContext.notify(`❌ 更新失败:${error.message},请重试`, "error");
181
+ } else {
182
+ uiContext.notify("✅ aicode-cli 更新成功!请重启 CLI", "success");
183
+ }
184
+ resolve();
185
+ });
186
+ });
187
+ } catch (e) {
188
+ // 忽略错误(ctx 可能已过期)
189
+ }
160
190
  },
161
191
  });
162
192
 
163
193
  // === 注册自定义工具 ===
164
194
 
165
- // 注意: 注册自定义工具需要更复杂的参数定义
166
- // 详情请参考: https://github.com/mariozechner/pi-coding-agent/docs/extensions.md
195
+ // 注意:注册自定义工具需要更复杂的参数定义
196
+ // 详情请参考:https://github.com/mariozechner/pi-coding-agent/docs/extensions.md
167
197
 
168
198
  // === 注册 /version 命令 ===
169
199
 
@@ -204,7 +234,7 @@ export default function (pi: ExtensionAPI) {
204
234
  (latestParts[0] === localParts[0] && latestParts[1] === localParts[1] && latestParts[2] > localParts[2]);
205
235
 
206
236
  if (needsUpdate) {
207
- updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行: npm install -g @ia-ccun/code-agent-cli`;
237
+ updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行:npm install -g @ia-ccun/code-agent-cli`;
208
238
  } else {
209
239
  updateMsg = "\n\n✅ 已是最新版本";
210
240
  }
@@ -215,8 +245,8 @@ export default function (pi: ExtensionAPI) {
215
245
  const info = `
216
246
  📦 版本信息
217
247
 
218
- 当前版本: v${localVersion}
219
- 最新版本: v${latestVersion}
248
+ 当前版本:v${localVersion}
249
+ 最新版本:v${latestVersion}
220
250
  ${updateMsg}
221
251
  `.trim();
222
252
 
@@ -3,7 +3,7 @@
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
- * for debugging purposes. Logs are saved to ./.aicode-cli/debug-logs/
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
@@ -13,8 +13,9 @@
13
13
  * - Session-based log files (one file per session)
14
14
  */
15
15
 
16
- import { appendFileSync, existsSync, mkdirSync } from "node:fs";
16
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
17
17
  import { join } from "node:path";
18
+ import os from "node:os";
18
19
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
20
 
20
21
  // 北京时间格式化函数 (UTC+8)
@@ -31,16 +32,39 @@ function formatDateStr(date: Date = new Date()): string {
31
32
  return `${year}-${month}-${day}`;
32
33
  }
33
34
 
34
- export default function (pi: ExtensionAPI) {
35
- // Create log directory in project root: ./.aicode-cli/debug-logs
36
- const logDir = join(process.cwd(), ".aicode-cli", "debug-logs");
37
- if (!existsSync(logDir)) {
38
- mkdirSync(logDir, { recursive: true });
35
+ // 日志目录配置(与 error-monitor.ts 保持一致)
36
+ const LOG_DIR = join(os.homedir(), ".aicode-cli", "logs");
37
+ const KEEP_DAYS = 2; // 保留最近2天
38
+
39
+ // 确保日志目录存在
40
+ function ensureLogDir(): void {
41
+ if (!existsSync(LOG_DIR)) {
42
+ mkdirSync(LOG_DIR, { recursive: true });
39
43
  }
40
-
44
+ }
45
+
46
+ // 清理过期日志文件
47
+ function cleanOldLogs(): void {
48
+ if (!existsSync(LOG_DIR)) return;
49
+ const now = new Date(new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }));
50
+ for (const file of readdirSync(LOG_DIR)) {
51
+ if (!file.startsWith("llm-requests-") || !file.endsWith(".log")) continue;
52
+ const match = file.match(/llm-requests-(\d{4})-(\d{2})-(\d{2})\.log/);
53
+ 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));
56
+ if (diffDays > KEEP_DAYS) {
57
+ unlinkSync(join(LOG_DIR, file));
58
+ }
59
+ }
60
+ }
61
+
62
+ export default function (pi: ExtensionAPI) {
63
+ ensureLogDir();
64
+ cleanOldLogs();
41
65
  // Format date as YYYY-MM-DD for daily log files
42
66
  const dateStr = formatDateStr();
43
- const logFile = join(logDir, `llm-requests-${dateStr}.log`);
67
+ const logFile = join(LOG_DIR, `llm-requests-${dateStr}.log`);
44
68
 
45
69
  // Filter to only log user and assistant messages
46
70
  const ALLOWED_ROLES = ["user", "assistant"];
@@ -47,6 +47,7 @@ const workingMessages = [
47
47
  let intervalId: ReturnType<typeof setInterval> | null = null;
48
48
  let startTime = 0;
49
49
  let totalTokens = 0;
50
+ let currentUI: any = null; // 保存当前有效的 UI context
50
51
 
51
52
  function getNextMessage(): string {
52
53
  const randomIndex = Math.floor(Math.random() * workingMessages.length);
@@ -66,39 +67,58 @@ function formatTokens(tokens: number): string {
66
67
  }
67
68
 
68
69
  export default function workingMessageExtension(pi: ExtensionAPI) {
70
+ // 监听 session_shutdown 事件,清理 interval
71
+ pi.on("session_shutdown", async (_event, ctx) => {
72
+ if (intervalId) {
73
+ clearInterval(intervalId);
74
+ intervalId = null;
75
+ }
76
+ currentUI = null;
77
+ });
78
+
69
79
  // turn_start 时开始循环切换消息
70
80
  pi.on("turn_start", async (_event, ctx) => {
71
81
  if (!ctx.ui) return;
72
82
 
83
+ // 保存当前有效的 UI context
84
+ currentUI = ctx.ui;
85
+
73
86
  // 记录开始时间
74
87
  startTime = Date.now();
75
88
  totalTokens = 0;
76
89
 
77
- // 立即设置第一条
78
- const updateMessage = () => {
79
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
80
- const timeStr = formatTime(elapsed);
81
- const tokensStr = formatTokens(totalTokens);
82
- // ✶ 和单词显示为主题色
83
- const msg = `${accentColor} ✶ ${getNextMessage()}…${reset} (${timeStr} · ↓ ${tokensStr} tokens)`;
84
- ctx.ui?.setWorkingMessage(msg);
85
- };
86
-
87
- updateMessage();
90
+ // 立即设置第一条消息
91
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
92
+ const timeStr = formatTime(elapsed);
93
+ const tokensStr = formatTokens(totalTokens);
94
+ const msg = `${accentColor} ✶ ${getNextMessage()}…${reset} (${timeStr} · ↓ ${tokensStr} tokens)`;
95
+ currentUI.setWorkingMessage(msg);
88
96
 
89
- // 每3秒更新一次
97
+ // 每3秒更新一次 - 使用 try-catch 处理 stale context
90
98
  if (!intervalId) {
91
99
  intervalId = setInterval(() => {
92
- // 获取当前 tokens 使用量
93
- for (const entry of ctx.sessionManager.getBranch()) {
94
- if (entry.type === "message" && (entry as any).message?.role === "assistant") {
95
- const msg = (entry as any).message as AssistantMessage;
96
- if (msg.usage?.output) {
97
- totalTokens = msg.usage.output;
98
- }
100
+ try {
101
+ // 使用保存的 currentUI 而不是捕获的 ctx.ui
102
+ if (!currentUI) return;
103
+
104
+ // 获取当前 tokens 使用量
105
+ // 注意:这里不再访问 ctx.sessionManager,避免 stale context 错误
106
+ // tokens 数量通过其他方式获取,或者保持为 0
107
+
108
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
109
+ const timeStr = formatTime(elapsed);
110
+ const tokensStr = formatTokens(totalTokens);
111
+ const msg = `${accentColor} ✶ ${getNextMessage()}…${reset} (${timeStr} · ↓ ${tokensStr} tokens)`;
112
+ currentUI.setWorkingMessage(msg);
113
+ } catch (error) {
114
+ // 如果出现错误(比如 stale context),清理 interval
115
+ console.error('working-msg extension error:', error);
116
+ if (intervalId) {
117
+ clearInterval(intervalId);
118
+ intervalId = null;
99
119
  }
120
+ currentUI = null;
100
121
  }
101
- updateMessage();
102
122
  }, 3000);
103
123
  }
104
124
  });
@@ -110,6 +130,7 @@ export default function workingMessageExtension(pi: ExtensionAPI) {
110
130
  intervalId = null;
111
131
  }
112
132
  // 恢复默认消息
113
- ctx.ui?.setWorkingMessage(undefined);
133
+ currentUI?.setWorkingMessage(undefined);
134
+ currentUI = null;
114
135
  });
115
136
  }
@@ -4,6 +4,29 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## 📖 帮助命令
8
+
9
+ 查看 aicode-cli 的使用帮助:
10
+
11
+ ```bash
12
+ /help
13
+ ```
14
+
15
+ 快速帮助:
16
+ - `/help` - 查看本完整文档
17
+ - `/models` - 查看模型列表
18
+ - `/tools` - 查看可用工具
19
+ - `/skill:<name>` - 激活指定技能
20
+ - `Ctrl+Alt+P` - 切换计划模式
21
+ - `/goal` - 设置持续目标
22
+
23
+ ---
24
+ # aicode-cli 使用指南
25
+
26
+ > 全面掌握 AI 编程助手的各项功能与配置
27
+
28
+ ---
29
+
7
30
  ## 目录
8
31
 
9
32
  1. [快速开始](#1-快速开始)
@@ -447,13 +470,6 @@ aicode-cli --verbose
447
470
  | `--theme <name>` | 指定主题 |
448
471
  | `--model <name>` | 指定模型 |
449
472
  | `--clear` | 启动时清理会话 |
450
-
451
- ### 相关资源
452
-
453
- - **项目仓库**: https://github.com/ia-ccun/code-agent-cli
454
- - **npm 包**: https://www.npmjs.com/package/@ia-ccun/code-agent-cli
455
- - **pi-coding-agent**: 底层 Agent 框架
456
-
457
473
  ---
458
474
 
459
475
  *世界那么哒 · AI 实践落地 @2026*