thincoder 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +2 -1
  2. package/bin/thincoder.mjs +25 -25
  3. package/package.json +1 -1
  4. package/src/agent/dispatch.mjs +17 -12
  5. package/src/agent/helpers.mjs +22 -11
  6. package/src/agent/setup.mjs +32 -10
  7. package/src/agent-tools/goal.mjs +7 -6
  8. package/src/agent-tools/plan.mjs +3 -3
  9. package/src/agent-tools/recent-changes.mjs +3 -3
  10. package/src/agent-tools/skill.mjs +6 -6
  11. package/src/agent-tools/subagent.mjs +19 -18
  12. package/src/agent-tools/task.mjs +4 -4
  13. package/src/agent-tools/verify.mjs +21 -19
  14. package/src/agent-tools.mjs +3 -3
  15. package/src/agent.mjs +58 -37
  16. package/src/cli/distill-command.mjs +2 -2
  17. package/src/cli/make-agent.mjs +9 -9
  18. package/src/cli/memory-command.mjs +1 -1
  19. package/src/cli/permission.mjs +3 -3
  20. package/src/cli/setup-wizard.mjs +15 -15
  21. package/src/config.mjs +55 -53
  22. package/src/context.mjs +59 -56
  23. package/src/distill.mjs +35 -35
  24. package/src/embedding.mjs +17 -17
  25. package/src/git/checkpoint.mjs +211 -38
  26. package/src/git/gitmem.mjs +21 -20
  27. package/src/markdown.mjs +13 -13
  28. package/src/mcp/helpers.mjs +6 -1
  29. package/src/mcp/transport-http.mjs +2 -1
  30. package/src/mcp/transport-stdio.mjs +3 -2
  31. package/src/mcp/transport-ws.mjs +3 -2
  32. package/src/mcp.mjs +5 -2
  33. package/src/memory/code-index.mjs +16 -14
  34. package/src/memory/code-sync.mjs +36 -26
  35. package/src/memory/core.mjs +42 -35
  36. package/src/memory/docs.mjs +24 -15
  37. package/src/memory/schema.mjs +28 -27
  38. package/src/memory.mjs +2 -2
  39. package/src/prompts/coder.md +1 -1
  40. package/src/prompts/discipline.md +3 -0
  41. package/src/prompts/main.md +1 -2
  42. package/src/prompts/system.md +21 -16
  43. package/src/provider/core.mjs +11 -6
  44. package/src/provider/index.mjs +2 -2
  45. package/src/provider/rate.mjs +11 -11
  46. package/src/session.mjs +73 -40
  47. package/src/skills.mjs +17 -17
  48. package/src/tools/checkpoint.md +6 -2
  49. package/src/tools/file.mjs +16 -12
  50. package/src/tools/git.mjs +83 -12
  51. package/src/tools/index.mjs +1 -1
  52. package/src/tools/patch.mjs +20 -18
  53. package/src/tools/repomap-parse.mjs +17 -17
  54. package/src/tools/repomap.mjs +29 -29
  55. package/src/tools/shared.mjs +49 -28
  56. package/src/tools/system.mjs +163 -118
  57. package/src/tools/web.mjs +6 -6
  58. package/src/tui/agent-turn.mjs +82 -26
  59. package/src/tui/ansi.mjs +4 -2
  60. package/src/tui/clipboard.mjs +2 -2
  61. package/src/tui/cmd-auto.mjs +1 -1
  62. package/src/tui/cmd-clear.mjs +1 -1
  63. package/src/tui/cmd-config.mjs +2 -2
  64. package/src/tui/cmd-exit.mjs +2 -2
  65. package/src/tui/cmd-extract.mjs +1 -1
  66. package/src/tui/cmd-goal.mjs +4 -4
  67. package/src/tui/cmd-help.mjs +1 -1
  68. package/src/tui/cmd-init.mjs +3 -3
  69. package/src/tui/cmd-mcp.mjs +5 -5
  70. package/src/tui/cmd-model.mjs +1 -1
  71. package/src/tui/cmd-new.mjs +1 -1
  72. package/src/tui/cmd-plan.mjs +1 -1
  73. package/src/tui/cmd-reindex.mjs +2 -2
  74. package/src/tui/cmd-restore.mjs +1 -1
  75. package/src/tui/cmd-session.mjs +1 -1
  76. package/src/tui/cmd-skills.mjs +1 -1
  77. package/src/tui/cmd-think.mjs +2 -2
  78. package/src/tui/config-helpers.mjs +6 -6
  79. package/src/tui/distill-cmd.mjs +3 -3
  80. package/src/tui/index.mjs +131 -72
  81. package/src/tui/interaction.mjs +13 -12
  82. package/src/tui/key-handler.mjs +31 -23
  83. package/src/tui/layout.mjs +22 -17
  84. package/src/tui/pickers.mjs +19 -19
  85. package/src/tui/render-frame.mjs +37 -11
  86. package/src/tui/render.mjs +25 -24
  87. package/src/tui/slash-commands.mjs +14 -14
  88. package/src/tui/startup.mjs +15 -14
  89. package/src/tui/wizard.mjs +11 -11
  90. package/src/tui.mjs +2 -2
  91. package/bin/thincoder.js +0 -4
  92. package/src/tools/bash.mjs +0 -144
  93. package/src/tools/glob.mjs +0 -51
  94. package/src/tools/grep.mjs +0 -100
  95. package/src/tools/ls.mjs +0 -36
  96. package/src/tools.mjs +0 -2
  97. package/src/tui-render.mjs +0 -4
@@ -3,8 +3,8 @@ import { saveSession } from "../session.mjs"
3
3
  import { sliceByWidth } from "./render.mjs"
4
4
  import { ansi, C } from "./ansi.mjs"
5
5
 
6
- /** 执行一轮 agent 对话(从 submit 或队列取出调用)。
7
- * index.mjs 抽出:agent 循环 + 回调构建 + 错误处理 + 队列处理。
6
+ /** Execute one agent conversation turn (triggered by submit or queue).
7
+ * Extracted from index.mjs: agent loop + callback construction + error handling + queue processing.
8
8
  * ctx: { agent, state, pushLine, pushLabel, render, scheduleRender,
9
9
  * ensureAssistantLabel, askPermission, askQuestion,
10
10
  * handleSlash, summarize } */
@@ -13,12 +13,12 @@ export async function runAgentTurn(ctx, text) {
13
13
  pushLabel(`❯ You:`, ansi.bold + C.user)
14
14
  pushLine(text, C.text)
15
15
 
16
- // 任务开始前自动打存档点 (git 仓库内;失败静默,不挡任务)
16
+ // Auto-checkpoint before task (git repo only; failure is silent, doesn't block the task)
17
17
  try {
18
18
  const { createCheckpoint } = await import("../git/checkpoint.mjs")
19
19
  await createCheckpoint(agent.cwd)
20
20
  } catch {
21
- // 存档失败不影响任务
21
+ // Checkpoint failure doesn't block the task
22
22
  }
23
23
 
24
24
  ctx.assistantLabeled = false
@@ -30,7 +30,7 @@ export async function runAgentTurn(ctx, text) {
30
30
  state.currentTool = null
31
31
  state.processingStarted = Date.now()
32
32
  state.controller = new AbortController()
33
- // 处理中每秒刷新一次状态栏 (运行计时)
33
+ // Refresh status bar every second during processing (elapsed timer)
34
34
  const ticker = setInterval(() => {
35
35
  if (state.processing) render()
36
36
  }, 1000)
@@ -49,7 +49,7 @@ export async function runAgentTurn(ctx, text) {
49
49
 
50
50
  const callbacks = {
51
51
  onToken: (t) => {
52
- // agent 流式输出:前缀格式 role#id/ → 提取 id,更新对应 subTask 的流式文本
52
+ // Subagent streaming: prefix format role#id/ → extract id, update subTask streaming text
53
53
  const subMatch = t.match(/^(\w+)#(\d+)\//)
54
54
  if (subMatch) {
55
55
  const key = `${subMatch[1]}#${subMatch[2]}`
@@ -69,7 +69,7 @@ export async function runAgentTurn(ctx, text) {
69
69
  scheduleRender()
70
70
  },
71
71
  onReasoning: (t) => {
72
- // agent 的思考 token 同样带 role#id/ 前缀,进 subTasks 面板
72
+ // Subagent reasoning tokens also carry role#id/ prefix, go into subTasks panel
73
73
  const subMatch = t.match(/^(\w+)#(\d+)\//)
74
74
  if (subMatch) {
75
75
  const key = `${subMatch[1]}#${subMatch[2]}`
@@ -84,7 +84,7 @@ export async function runAgentTurn(ctx, text) {
84
84
  scheduleRender()
85
85
  },
86
86
  onToolCall: (name, args) => {
87
- // agent 工具调用:前缀 role#id/toolName → 更新对应 subTask 的当前工具
87
+ // Subagent tool call: prefix role#id/toolName → update subTask current tool
88
88
  const subMatch = name.match(/^(\w+)#(\d+)\//)
89
89
  if (subMatch) {
90
90
  const key = `${subMatch[1]}#${subMatch[2]}`
@@ -105,7 +105,7 @@ export async function runAgentTurn(ctx, text) {
105
105
  },
106
106
  onToolResult: (name, result) => {
107
107
  state.currentTool = null
108
- // agent 结束:标记最早创建的 running subTask done
108
+ // Subagent complete: mark earliest running subTask as done
109
109
  const isSubagent = name === "subagent"
110
110
  if (isSubagent) {
111
111
  const running = Object.entries(state.subTasks)
@@ -116,12 +116,12 @@ export async function runAgentTurn(ctx, text) {
116
116
  state.subTasks[key].done = true
117
117
  state.subTasks[key].tool = null
118
118
  }
119
- // agent 报告摘要 (最多 8 行)直接展示在对话区
119
+ // Subagent report preview (max 8 lines) displayed directly in conversation
120
120
  const lines = result.split("\n")
121
121
  const preview = lines.slice(0, 8).map((l) => l.slice(0, 120)).join("\n")
122
122
  if (preview) pushLine(preview, C.dim)
123
123
  if (lines.length > 8) pushLine(` ... (${lines.length - 8} more lines)`, C.dim)
124
- // 3 秒后清除面板中 done 的条目
124
+ // Clear done entries from panel after 3 seconds
125
125
  setTimeout(() => {
126
126
  for (const key of Object.keys(state.subTasks)) {
127
127
  if (state.subTasks[key].done) delete state.subTasks[key]
@@ -130,22 +130,47 @@ export async function runAgentTurn(ctx, text) {
130
130
  }, 3000)
131
131
  }
132
132
  const stream = state.toolStreams[name]
133
- if (stream) {
133
+ const panel = state.outputPanels[name]
134
+ if (panel) {
135
+ // Tool with output panel: mark panel as done, conversation gets only summary
136
+ panel.done = true
137
+ delete state.toolStreams[name]
138
+ const summary = formatPanelSummary(name, result)
139
+ if (summary) pushLine(` ${summary}`, C.dim)
140
+ // Clear panel after 3 seconds
141
+ setTimeout(() => {
142
+ delete state.outputPanels[name]
143
+ if (state.processing) render()
144
+ }, 3000)
145
+ } else if (stream) {
134
146
  const tail = stream.trimEnd().slice(-4000)
135
147
  if (tail) pushLine(tail, C.dim)
136
148
  delete state.toolStreams[name]
137
149
  }
138
- if (!isSubagent) {
150
+ if (!isSubagent && !panel) {
139
151
  const first = result.split("\n")[0]
140
152
  pushLine(` [done] ${name} → ${sliceByWidth(first, 100)}`, C.dim)
141
153
  }
142
154
  },
143
155
  onToolOutput: (name, chunk) => {
144
- state.toolStreams[name] = (state.toolStreams[name] ?? "") + chunk
156
+ // Route streaming output to a panel if one exists or was requested via outputPanel flag.
157
+ let panel = state.outputPanels[name]
158
+ if (!panel) {
159
+ // Lazy-create panel: defensive against race conditions where setupOutputPanel
160
+ // hasn't fired yet or the callbacks chain dropped it (subagent relay, reconnect, etc.)
161
+ state.outputPanels[name] = { text: "", done: false }
162
+ panel = state.outputPanels[name]
163
+ }
164
+ panel.text = (panel.text ?? "") + chunk
165
+ if (panel.text.length > 4000) panel.text = panel.text.slice(-4000)
145
166
  scheduleRender()
146
167
  },
147
168
  onPermissionRequest: (name, args) => askPermission(name, args),
148
169
  onQuestion: (text, options) => askQuestion(text, options),
170
+ setupOutputPanel: (name) => {
171
+ state.outputPanels[name] = { text: "", done: false }
172
+ scheduleRender()
173
+ },
149
174
  onCompress: () => {
150
175
  pushLine(" [context] Context too long, auto-compacted (early conversation summarized by LLM, task state preserved)", C.warn)
151
176
  },
@@ -155,7 +180,7 @@ export async function runAgentTurn(ctx, text) {
155
180
  state.tokens.cacheHit += usage.prompt_cache_hit_tokens ?? 0
156
181
  state.tokens.cacheMiss += usage.prompt_cache_miss_tokens ?? 0
157
182
  },
158
- // 节流等待 (主动闸门 / 429 退避):状态栏明示,防用户以为卡死
183
+ // Throttle wait (active gate / 429 backoff): show in status bar so user knows it's not frozen
159
184
  onWait: ({ phase, seconds }) => {
160
185
  state.status = phase === "gate" ? `TPM throttle wait ~${seconds}s` : `Rate-limited 429, retry in ${seconds}s`
161
186
  render()
@@ -163,12 +188,12 @@ export async function runAgentTurn(ctx, text) {
163
188
  onTaskUpdate: (items) => {
164
189
  state.tasks = items
165
190
  const done = items.filter((i) => i.status === "done").length
166
- // 留痕带上current任务标题:回看历史时知道进行到哪一项
191
+ // Leave trace with current task title: reviewing history shows what was in progress
167
192
  const current = items.find((i) => i.status === "in_progress")
168
193
  pushLine(` [task] ${done}/${items.length}${current ? ` ▶ ${current.title}` : ""}`, C.dim)
169
194
  render()
170
195
  },
171
- // 增量保存:每 5 个工具 turn 落一次盘,中途崩溃丢失窗口从一整轮缩到几轮
196
+ // Incremental save: flush to disk every 5 tool turns — mid-crash loss window shrinks from an entire round to a few turns
172
197
  onTurnEnd: (() => {
173
198
  let n = 0
174
199
  return () => {
@@ -182,7 +207,7 @@ export async function runAgentTurn(ctx, text) {
182
207
  try {
183
208
  await runAgent(agent, text, callbacks, { signal: state.controller.signal, resume })
184
209
  flushStream()
185
- break // 正常完成,退出循环
210
+ break // Normal completion, exit loop
186
211
  } catch (error) {
187
212
  flushStream()
188
213
  if (error.name === "AbortError" || state.controller?.signal.aborted) {
@@ -192,7 +217,7 @@ export async function runAgentTurn(ctx, text) {
192
217
  if (error instanceof ContinueError) {
193
218
  pushLabel(`❯ Continue`, ansi.bold + C.warn)
194
219
  pushLine(`Ran ${error.turn} turns (limit ${error.turn}). Continue?`, C.warn)
195
- // 暂停询问:复用 permission 机制
220
+ // Pause to ask: reuse permission mechanism
196
221
  const willContinue = await new Promise((resolve) => {
197
222
  state.permission = {
198
223
  name: "continue",
@@ -208,7 +233,7 @@ export async function runAgentTurn(ctx, text) {
208
233
  break
209
234
  }
210
235
  pushLine("[continuing…]", C.tool)
211
- // 重创新 AbortController:旧 signal 一旦 abort 过,resume 会立即失败 (防御性,current路径不可达但耦合紧)
236
+ // Recreate AbortController: once aborted, resume immediately fails (defensive; current path unreachable but tightly coupled)
212
237
  state.controller = new AbortController()
213
238
  continue
214
239
  }
@@ -222,26 +247,26 @@ export async function runAgentTurn(ctx, text) {
222
247
  state.subTasks = {}
223
248
  state.controller = null
224
249
  state.status = "Ready"
225
- // 全部完成时自动收起 todo 面板 (对齐 kimi-code TUIagent.tasks 本身保留)
250
+ // Auto-collapse todo panel when all tasks done (matching kimi-code TUI; agent.tasks are preserved)
226
251
  if (state.tasks.length > 0 && state.tasks.every((t) => t.status === "done")) {
227
252
  state.tasks = []
228
253
  }
229
- // 每轮结束后保存会话 (崩溃也不丢)
254
+ // Save session after every turn (survives crashes)
230
255
  try {
231
256
  saveSession(agent, state.lines)
232
257
  } catch {
233
- // 存失败不打断使用
258
+ // Save failure doesn't interrupt usage
234
259
  }
235
260
  render()
236
261
 
237
- // 队列里有待执行消息:自动取下一条执行
262
+ // Queued messages: auto-process next one
238
263
  if (state.queue.length > 0) {
239
264
  const next = state.queue.shift()
240
- // 队列里的斜杠命令直接执行
265
+ // Queued slash commands execute directly
241
266
  if (next.text.startsWith("/")) {
242
267
  await handleSlash(next.text)
243
268
  render()
244
- // 斜杠命令执行完也继续检查队列
269
+ // After slash command completes, continue checking queue
245
270
  if (state.queue.length > 0 && !state.processing) {
246
271
  const next2 = state.queue.shift()
247
272
  await runAgentTurn(ctx, next2.text)
@@ -252,3 +277,34 @@ export async function runAgentTurn(ctx, text) {
252
277
  }
253
278
  }
254
279
  }
280
+
281
+ /** Extract a one-line summary from a panel tool's output */
282
+ function formatPanelSummary(name, result) {
283
+ if (name === "verify") return _verifySummary(result)
284
+ // Default: first non-empty line
285
+ const first = result.split("\n").find((l) => l.trim())
286
+ return first ? `${name}: ${first.slice(0, 100)}` : null
287
+ }
288
+
289
+ function _verifySummary(result) {
290
+ const lines = result.split("\n")
291
+ const summary = []
292
+ // Changed files count
293
+ const changed = lines.find((l) => l.startsWith("Changed files:"))
294
+ if (changed) {
295
+ const m = changed.match(/files changed/) ? changed.replace(/^Changed files \(.*?\)/, "Changed files") : changed
296
+ summary.push(m)
297
+ }
298
+ // Syntax check results
299
+ const syntax = lines.filter((l) => l.startsWith(" ✗"))
300
+ if (syntax.length > 0) {
301
+ summary.push(`${syntax.length} syntax error(s)`)
302
+ }
303
+ // Test results
304
+ const testLine = lines.find((l) => l.startsWith("✓ Tests passed.") || l.startsWith("✗ Tests FAILED"))
305
+ if (testLine) summary.push(testLine.trim())
306
+ // Task list
307
+ const taskLine = lines.find((l) => l.startsWith("Task list:"))
308
+ if (taskLine) summary.push(taskLine)
309
+ return summary.length > 0 ? `verify: ${summary.join(" — ")}` : ""
310
+ }
package/src/tui/ansi.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * tui-ansi.mjs — ANSI 转义序列与终端颜色常量
3
- * 零依赖,纯常量导出。
2
+ * tui-ansi.mjs — ANSI escape sequences and terminal color constants.
3
+ * Zero dependencies, pure constant exports.
4
4
  */
5
5
 
6
6
  export const ESC = "\x1b"
@@ -11,6 +11,8 @@ export const ansi = {
11
11
  mainBuffer: `${ESC}[?1049l`,
12
12
  mouseOn: `${ESC}[?1000h${ESC}[?1006h`,
13
13
  mouseOff: `${ESC}[?1000l${ESC}[?1006l`,
14
+ bracketedPasteOn: `${ESC}[?2004h`,
15
+ bracketedPasteOff: `${ESC}[?2004l`,
14
16
  home: `${ESC}[H`,
15
17
  clearLine: `${ESC}[K`,
16
18
  reset: `${ESC}[0m`,
@@ -1,7 +1,7 @@
1
1
  import { C } from "./ansi.mjs"
2
2
 
3
- /** Ctrl+V / Alt+V:读取剪贴板图片写入工作目录临时文件输入框插入 read_image 命令。
4
- * index.mjs 抽出。
3
+ /** Ctrl+V / Alt+V: read clipboard image write temp file in working directory insert read_image command into input box.
4
+ * Extracted from index.mjs.
5
5
  * ctx: { agent, state, pushLine, render } */
6
6
  export async function pasteClipboardImage(ctx) {
7
7
  const { agent, state, pushLine, render } = ctx
@@ -1,6 +1,6 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /auto 命令:切换 auto-approve 模式。
3
+ /** /auto command: toggle auto-approve mode.
4
4
  * ctx: { agent, pushLine, pushLabel } */
5
5
  export async function handleAutoCommand(ctx) {
6
6
  const { agent, pushLine, pushLabel } = ctx
@@ -1,4 +1,4 @@
1
- /** /clear 命令:清屏(二次确认防误触)。
1
+ /** /clear command: clear screen (confirm to prevent accidental trigger).
2
2
  * ctx: { state, openPicker, render } */
3
3
  export async function handleClearCommand(ctx) {
4
4
  const { state, openPicker, render } = ctx
@@ -1,8 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs"
2
2
  import { ansi, C } from "./ansi.mjs"
3
3
 
4
- /** /config 命令:设置 embedding key / 高级 path=value 配置。
5
- * slash-commands.mjs 抽出。
4
+ /** /config command: set embedding key / advanced path=value config.
5
+ * Extracted from slash-commands.mjs.
6
6
  * ctx: { agent, pushLine, pushLabel, openPicker, askQuestion, persistRaw, maskKey, ansi, C } */
7
7
  export async function handleConfigCommand(ctx) {
8
8
  const { agent, pushLine, pushLabel, openPicker, askQuestion, persistRaw, maskKey } = ctx
@@ -1,5 +1,5 @@
1
- /** /exit 命令:退出 TUI
2
- * ctx: { exit } — exit index.mjs 注入的 cleanup + process.exit 回调 */
1
+ /** /exit command: exit TUI.
2
+ * ctx: { exit } — exit is the cleanup + process.exit callback injected by index.mjs */
3
3
  export async function handleExitCommand(ctx) {
4
4
  ctx.exit()
5
5
  }
@@ -1,4 +1,4 @@
1
- /** /extract 命令:从当前会话提取知识。
1
+ /** /extract command: extract knowledge from current session.
2
2
  * ctx: { runDistill } */
3
3
  export async function handleExtractCommand(ctx) {
4
4
  await ctx.runDistill()
@@ -1,7 +1,7 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /goal 命令:设置/查看/取消长期目标。
4
- * slash-commands.mjs 抽出。
3
+ /** /goal command: set/view/cancel long-term goal.
4
+ * Extracted from slash-commands.mjs.
5
5
  * ctx: { agent, pushLine, pushLabel, openPicker, askQuestion } */
6
6
  export async function handleGoalCommand(ctx) {
7
7
  const { agent, pushLine, pushLabel, openPicker, askQuestion } = ctx
@@ -31,7 +31,7 @@ export async function handleGoalCommand(ctx) {
31
31
  pushLine(`Goal cancelled.`, C.dim)
32
32
  return
33
33
  }
34
- // set — 需要输入目标文本
34
+ // set — requires entering goal text
35
35
  const goalText = await askQuestion("Enter goal description (; separates criteria)")
36
36
  if (!goalText) return
37
37
  const semi = goalText.indexOf(";") >= 0 ? ";" : goalText.indexOf(";") >= 0 ? ";" : null
@@ -41,7 +41,7 @@ export async function handleGoalCommand(ctx) {
41
41
  pushLabel(`❯ Goal`, ansi.bold + C.warn)
42
42
  pushLine(`Goal set: ${objective}`, C.tool)
43
43
  if (criteria) pushLine(` Criteria: ${criteria}`, C.dim)
44
- else pushLine(` ⚠ No criteria — agent will be asked to provide verifiable criteria when using goal set`, C.warn)
44
+ else pushLine(` ⚠ No criteria provided the agent will determine its own criteria as it works on this goal`, C.warn)
45
45
  },
46
46
  })
47
47
  }
@@ -1,6 +1,6 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /help 命令:列出所有斜杠命令及别名。
3
+ /** /help command: list all slash commands and aliases.
4
4
  * ctx: { pushLine, pushLabel, SLASH_COMMANDS } */
5
5
  export async function handleHelpCommand(ctx) {
6
6
  const { pushLine, pushLabel, SLASH_COMMANDS } = ctx
@@ -2,8 +2,8 @@ import { existsSync } from "node:fs"
2
2
  import { basename } from "node:path"
3
3
  import { ansi, C } from "./ansi.mjs"
4
4
 
5
- /** /init 命令:探测项目类型,生成 AGENTS.md 骨架。
6
- * slash-commands.mjs 抽出。
5
+ /** /init command: detect project type, generate AGENTS.md skeleton.
6
+ * Extracted from slash-commands.mjs.
7
7
  * ctx: { agent, pushLine, pushLabel } */
8
8
  export async function handleInitCommand(ctx) {
9
9
  const { agent, pushLine, pushLabel } = ctx
@@ -15,7 +15,7 @@ export async function handleInitCommand(ctx) {
15
15
  return
16
16
  }
17
17
 
18
- // 探测项目类型与关键信息
18
+ // Detect project type and key information
19
19
  let name = basename(agent.cwd)
20
20
  let lang = "", cmds = ""
21
21
 
@@ -1,7 +1,7 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /mcp 命令处理器:查看/添加/删除/重连 MCP server
4
- * slash-commands.mjs 抽出,含 /mcp 专用的 parseHeaders / addAndConnect helper。
3
+ /** /mcp command handler: view/add/remove/reconnect MCP server.
4
+ * Extracted from slash-commands.mjs, includes /mcp-specific parseHeaders / addAndConnect helpers.
5
5
  * ctx: { agent, pushLine, pushLabel, openPicker, askQuestion, persistRaw, ansi, C } */
6
6
 
7
7
  function parseHeaders(pairs) {
@@ -13,7 +13,7 @@ function parseHeaders(pairs) {
13
13
  return headers
14
14
  }
15
15
 
16
- /** /mcp 共享 helper: 保存 Config + Connecting (persistRaw ctx 获取) */
16
+ /** /mcp shared helper: save config + connect (persistRaw obtained from ctx) */
17
17
  async function addAndConnect(ctx, srv) {
18
18
  const { agent, pushLine, pushLabel, persistRaw } = ctx
19
19
  await persistRaw((raw) => {
@@ -83,10 +83,10 @@ export async function handleMcpCommand(ctx) {
83
83
  title: "Remove MCP Server",
84
84
  entries: removeEntries,
85
85
  onSelect: async (se) => {
86
- // 从配置删除
86
+ // Remove from config
87
87
  agent.config.mcp.servers = servers.filter((s) => s.name !== se.name)
88
88
  await persistRaw((raw) => { raw.mcp.servers = agent.config.mcp.servers })
89
- // 从工具列表删除
89
+ // Remove from tool list
90
90
  const { removeMcpTools } = await import("../mcp.mjs")
91
91
  removeMcpTools(agent, se.name)
92
92
  pushLine(`[mcp] ${se.name} removed`, C.tool)
@@ -1,6 +1,6 @@
1
1
  import { C } from "./ansi.mjs"
2
2
 
3
- /** /model 命令:打开模型选择器。
3
+ /** /model command: open model picker.
4
4
  * ctx: { openModelPicker, pushLine } */
5
5
  export async function handleModelCommand(ctx) {
6
6
  ctx.openModelPicker().catch((e) => ctx.pushLine(`[error] ${e.message}`, C.error))
@@ -1,7 +1,7 @@
1
1
  import { clearSession } from "../session.mjs"
2
2
  import { C } from "./ansi.mjs"
3
3
 
4
- /** /new 命令:新建会话(旧会话归档到槽位)。
4
+ /** /new command: start new session (old session archived to slot).
5
5
  * ctx: { agent, state, pushLine } */
6
6
  export async function handleNewCommand(ctx) {
7
7
  const { agent, state, pushLine } = ctx
@@ -1,6 +1,6 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /plan 命令:切换 plan mode(只读探索设计实现)。
3
+ /** /plan command: toggle plan mode (read-only explore designimplement).
4
4
  * ctx: { agent, pushLine, pushLabel } */
5
5
  export async function handlePlanCommand(ctx) {
6
6
  const { agent, pushLine, pushLabel } = ctx
@@ -1,6 +1,6 @@
1
1
  import { C } from "./ansi.mjs"
2
2
 
3
- /** /reindex 命令:重建记忆索引(files + code_chunks + doc_chunks)。
3
+ /** /reindex command: rebuild memory index (files + code_chunks + doc_chunks).
4
4
  * ctx: { agent, distillOpts, pushLine } */
5
5
  export async function handleReindexCommand(ctx) {
6
6
  const { agent, distillOpts, pushLine } = ctx
@@ -20,7 +20,7 @@ export async function handleReindexCommand(ctx) {
20
20
  total += s.added
21
21
  pushLine(` team: +${s.added} ~${s.updated} -${s.removed}`, C.dim)
22
22
  }
23
- // 重建代码索引和文档索引并行(读写不同表,WAL 支持)
23
+ // Rebuild code index and doc index in parallel (read/write different tables, WAL supports)
24
24
  pushLine(` [code+doc] Rebuilding indexes...`, C.tool)
25
25
  const [cr, dr] = await Promise.all([
26
26
  codeSync(agent.memory, agent.cwd, {
@@ -1,6 +1,6 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /restore 命令:列出 git checkpoint 并回滚到选中快照。
3
+ /** /restore command: list git checkpoints and roll back to selected snapshot.
4
4
  * ctx: { agent, openPicker, pushLine, pushLabel } */
5
5
  export async function handleRestoreCommand(ctx) {
6
6
  const { agent, openPicker, pushLine, pushLabel } = ctx
@@ -1,7 +1,7 @@
1
1
  import { listSlots, switchToSlot, applySession } from "../session.mjs"
2
2
  import { ansi, C } from "./ansi.mjs"
3
3
 
4
- /** /session 命令:列出/切换归档会话槽位。
4
+ /** /session command: list/switch archived session slots.
5
5
  * ctx: { agent, state, openPicker, pushLine, pushLabel, render } */
6
6
  export async function handleSessionCommand(ctx) {
7
7
  const { agent, state, openPicker, pushLine, pushLabel, render } = ctx
@@ -1,6 +1,6 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /skills 命令:列出项目技能。
3
+ /** /skills command: list project skills.
4
4
  * ctx: { agent, pushLine, pushLabel } */
5
5
  export async function handleSkillsCommand(ctx) {
6
6
  const { agent, pushLine, pushLabel } = ctx
@@ -1,7 +1,7 @@
1
1
  import { ansi, C } from "./ansi.mjs"
2
2
 
3
- /** /think 命令:切换思考模式、设置 reasoning effort
4
- * slash-commands.mjs 抽出。
3
+ /** /think command: toggle thinking mode, set reasoning effort.
4
+ * Extracted from slash-commands.mjs.
5
5
  * ctx: { agent, pushLine, pushLabel, openPicker, syncProviderField } */
6
6
  export async function handleThinkCommand(ctx) {
7
7
  const { agent, pushLine, pushLabel, openPicker, syncProviderField } = ctx
@@ -1,10 +1,10 @@
1
1
  import { existsSync, readFileSync } from "node:fs"
2
2
 
3
- /** persistRaw / syncProviderField / maskKey:配置读写辅助。
4
- * index.mjs 抽出,供 slash-commands / wizard / pickers 共用。
5
- * createConfigHelpers(agent) 返回 { persistRaw, syncProviderField, maskKey } */
3
+ /** persistRaw / syncProviderField / maskKey: config read/write helpers.
4
+ * Extracted from index.mjs for shared use by slash-commands, wizard, and pickers.
5
+ * createConfigHelpers(agent) returns { persistRaw, syncProviderField, maskKey } */
6
6
  export function createConfigHelpers(agent) {
7
- /** 读取 config.json → mutate → 落盘 */
7
+ /** Read config.json → mutate → persist to disk */
8
8
  async function persistRaw(mutate) {
9
9
  const { saveConfig, configPath } = await import("../config.mjs")
10
10
  const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : {}
@@ -12,7 +12,7 @@ export function createConfigHelpers(agent) {
12
12
  saveConfig(raw)
13
13
  }
14
14
 
15
- /** 同步当前 provider 的某个字段到 providers 数组并落盘 */
15
+ /** Sync a field of the current provider to the providers array and persist to disk */
16
16
  async function syncProviderField(field, value) {
17
17
  const target = agent.providers.find((p) => p.name === agent.activeProvider)
18
18
  if (!target) return
@@ -23,7 +23,7 @@ export function createConfigHelpers(agent) {
23
23
  })
24
24
  }
25
25
 
26
- /** API key 脱敏显示 */
26
+ /** Mask API key for display */
27
27
  function maskKey(key) {
28
28
  if (!key) return "(none)"
29
29
  if (key.length <= 8) return "***"
@@ -1,7 +1,7 @@
1
1
  import { C } from "./ansi.mjs"
2
2
 
3
- /** /distill 命令:从当前会话提取候选,逐条 y/n 确认后入库。
4
- * index.mjs 抽出。
3
+ /** /distill command: extract candidates from current session, confirm each y/n, then save to memory.
4
+ * Extracted from index.mjs.
5
5
  * ctx: { agent, state, pushLine, render, askPermission, distillOpts } */
6
6
  export async function runDistill(ctx) {
7
7
  const { agent, state, pushLine, render, askPermission, distillOpts } = ctx
@@ -24,7 +24,7 @@ export async function runDistill(ctx) {
24
24
  for (const c of candidates) {
25
25
  pushLine(`── Candidate [${c.type}] ${c.title} (scope: ${c.scope ?? "personal"})`, C.warn)
26
26
  for (const line of c.content.split("\n").slice(0, 6)) pushLine(` ${line}`, C.dim)
27
- if (c.type === "rule") pushLine(" (rule type — consider writing manually; press y to extract)", C.warn)
27
+ if (c.type === "rule") pushLine(" (rule type — consider writing manually; press y to extract)", C.warn)
28
28
  const accept = await askPermission("distill-save", { title: c.title })
29
29
  if (!accept) {
30
30
  pushLine(" skipped", C.dim)