thincoder 0.8.1 → 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.
- package/README.md +2 -1
- package/bin/thincoder.cjs +4 -0
- package/bin/thincoder.mjs +25 -25
- package/package.json +2 -2
- package/src/agent/dispatch.mjs +17 -12
- package/src/agent/helpers.mjs +22 -11
- package/src/agent/setup.mjs +32 -10
- package/src/agent-tools/goal.mjs +7 -6
- package/src/agent-tools/plan.mjs +3 -3
- package/src/agent-tools/recent-changes.mjs +3 -3
- package/src/agent-tools/skill.mjs +6 -6
- package/src/agent-tools/subagent.mjs +19 -18
- package/src/agent-tools/task.mjs +4 -4
- package/src/agent-tools/verify.mjs +21 -19
- package/src/agent-tools.mjs +3 -3
- package/src/agent.mjs +58 -37
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +9 -9
- package/src/cli/memory-command.mjs +1 -1
- package/src/cli/permission.mjs +3 -3
- package/src/cli/setup-wizard.mjs +15 -15
- package/src/config.mjs +55 -53
- package/src/context.mjs +59 -56
- package/src/distill.mjs +35 -35
- package/src/embedding.mjs +17 -17
- package/src/git/checkpoint.mjs +211 -38
- package/src/git/gitmem.mjs +21 -20
- package/src/markdown.mjs +13 -13
- package/src/mcp/helpers.mjs +6 -1
- package/src/mcp/transport-http.mjs +2 -1
- package/src/mcp/transport-stdio.mjs +3 -2
- package/src/mcp/transport-ws.mjs +3 -2
- package/src/mcp.mjs +5 -2
- package/src/memory/code-index.mjs +16 -14
- package/src/memory/code-sync.mjs +36 -26
- package/src/memory/core.mjs +42 -35
- package/src/memory/docs.mjs +24 -15
- package/src/memory/schema.mjs +28 -27
- package/src/memory.mjs +2 -2
- package/src/prompts/coder.md +1 -1
- package/src/prompts/discipline.md +3 -0
- package/src/prompts/main.md +1 -2
- package/src/prompts/system.md +21 -16
- package/src/provider/core.mjs +11 -6
- package/src/provider/index.mjs +2 -2
- package/src/provider/rate.mjs +11 -11
- package/src/session.mjs +73 -40
- package/src/skills.mjs +17 -17
- package/src/tools/checkpoint.md +6 -2
- package/src/tools/file.mjs +16 -12
- package/src/tools/git.mjs +83 -12
- package/src/tools/index.mjs +1 -1
- package/src/tools/patch.mjs +20 -18
- package/src/tools/repomap-parse.mjs +17 -17
- package/src/tools/repomap.mjs +29 -29
- package/src/tools/shared.mjs +49 -28
- package/src/tools/system.mjs +163 -118
- package/src/tools/web.mjs +6 -6
- package/src/tui/agent-turn.mjs +82 -26
- package/src/tui/ansi.mjs +4 -2
- package/src/tui/clipboard.mjs +2 -2
- package/src/tui/cmd-auto.mjs +1 -1
- package/src/tui/cmd-clear.mjs +1 -1
- package/src/tui/cmd-config.mjs +2 -2
- package/src/tui/cmd-exit.mjs +2 -2
- package/src/tui/cmd-extract.mjs +1 -1
- package/src/tui/cmd-goal.mjs +4 -4
- package/src/tui/cmd-help.mjs +1 -1
- package/src/tui/cmd-init.mjs +3 -3
- package/src/tui/cmd-mcp.mjs +5 -5
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +1 -1
- package/src/tui/cmd-plan.mjs +1 -1
- package/src/tui/cmd-reindex.mjs +2 -2
- package/src/tui/cmd-restore.mjs +1 -1
- package/src/tui/cmd-session.mjs +1 -1
- package/src/tui/cmd-skills.mjs +1 -1
- package/src/tui/cmd-think.mjs +2 -2
- package/src/tui/config-helpers.mjs +6 -6
- package/src/tui/distill-cmd.mjs +3 -3
- package/src/tui/index.mjs +131 -72
- package/src/tui/interaction.mjs +13 -12
- package/src/tui/key-handler.mjs +31 -23
- package/src/tui/layout.mjs +22 -17
- package/src/tui/pickers.mjs +19 -19
- package/src/tui/render-frame.mjs +37 -11
- package/src/tui/render.mjs +25 -24
- package/src/tui/slash-commands.mjs +14 -14
- package/src/tui/startup.mjs +15 -14
- package/src/tui/wizard.mjs +11 -11
- package/src/tui.mjs +2 -2
- package/bin/thincoder.js +0 -4
- package/src/tools/bash.mjs +0 -144
- package/src/tools/glob.mjs +0 -51
- package/src/tools/grep.mjs +0 -100
- package/src/tools/ls.mjs +0 -36
- package/src/tools.mjs +0 -2
- package/src/tui-render.mjs +0 -4
package/src/tui/agent-turn.mjs
CHANGED
|
@@ -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
|
-
/**
|
|
7
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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`,
|
package/src/tui/clipboard.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** Ctrl+V / Alt+V
|
|
4
|
-
*
|
|
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
|
package/src/tui/cmd-auto.mjs
CHANGED
package/src/tui/cmd-clear.mjs
CHANGED
package/src/tui/cmd-config.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs"
|
|
2
2
|
import { ansi, C } from "./ansi.mjs"
|
|
3
3
|
|
|
4
|
-
/** /config
|
|
5
|
-
*
|
|
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
|
package/src/tui/cmd-exit.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** /exit
|
|
2
|
-
* ctx: { exit } — 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
|
}
|
package/src/tui/cmd-extract.mjs
CHANGED
package/src/tui/cmd-goal.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ansi, C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /goal
|
|
4
|
-
*
|
|
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
|
|
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
|
}
|
package/src/tui/cmd-help.mjs
CHANGED
|
@@ -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
|
package/src/tui/cmd-init.mjs
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
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
|
|
package/src/tui/cmd-mcp.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ansi, C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /mcp
|
|
4
|
-
*
|
|
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
|
|
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)
|
package/src/tui/cmd-model.mjs
CHANGED
|
@@ -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))
|
package/src/tui/cmd-new.mjs
CHANGED
|
@@ -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
|
package/src/tui/cmd-plan.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ansi, C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /plan
|
|
3
|
+
/** /plan command: toggle plan mode (read-only explore → design → implement).
|
|
4
4
|
* ctx: { agent, pushLine, pushLabel } */
|
|
5
5
|
export async function handlePlanCommand(ctx) {
|
|
6
6
|
const { agent, pushLine, pushLabel } = ctx
|
package/src/tui/cmd-reindex.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /reindex
|
|
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
|
-
//
|
|
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, {
|
package/src/tui/cmd-restore.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ansi, C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /restore
|
|
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
|
package/src/tui/cmd-session.mjs
CHANGED
|
@@ -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
|
package/src/tui/cmd-skills.mjs
CHANGED
package/src/tui/cmd-think.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ansi, C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /think
|
|
4
|
-
*
|
|
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
|
-
*
|
|
5
|
-
* createConfigHelpers(agent)
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 "***"
|
package/src/tui/distill-cmd.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C } from "./ansi.mjs"
|
|
2
2
|
|
|
3
|
-
/** /distill
|
|
4
|
-
*
|
|
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
|
|
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)
|