thincoder 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/bin/thincoder.mjs +25 -25
- package/package.json +1 -1
- 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 +12 -11
- package/src/agent-tools/plan.mjs +5 -6
- 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 +7 -26
- package/src/agent-tools/verify.mjs +21 -19
- package/src/agent-tools.mjs +3 -3
- package/src/agent.mjs +82 -92
- 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 +56 -53
- package/src/context.mjs +59 -61
- 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 +0 -6
- package/src/prompts/discipline.md +9 -0
- package/src/prompts/main.md +24 -24
- 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 -42
- package/src/skills.mjs +17 -17
- package/src/tools/checkpoint.md +6 -2
- package/src/tools/file.mjs +20 -14
- package/src/tools/git.mjs +83 -12
- package/src/tools/index.mjs +1 -1
- package/src/tools/ls.md +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 +55 -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 +5 -3
- package/src/tui/clipboard.mjs +2 -2
- package/src/tui/cmd-auto.mjs +3 -12
- package/src/tui/cmd-clear.mjs +1 -1
- package/src/tui/cmd-config.mjs +108 -23
- package/src/tui/cmd-exit.mjs +2 -2
- package/src/tui/cmd-extract.mjs +11 -3
- package/src/tui/cmd-goal.mjs +3 -12
- package/src/tui/cmd-help.mjs +2 -2
- package/src/tui/cmd-init.mjs +3 -3
- package/src/tui/cmd-mcp.mjs +38 -25
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +33 -13
- package/src/tui/cmd-plan.mjs +3 -12
- 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 +4 -11
- package/src/tui/config-helpers.mjs +6 -6
- package/src/tui/distill-cmd.mjs +4 -3
- package/src/tui/index.mjs +131 -72
- package/src/tui/interaction.mjs +13 -12
- package/src/tui/key-handler.mjs +34 -26
- 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 +22 -22
- 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/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
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** /auto 命令:切换 auto-approve 模式。
|
|
4
|
-
* ctx: { agent, pushLine, pushLabel } */
|
|
1
|
+
/** /auto command: toggle auto-approve mode.
|
|
2
|
+
* ctx: { agent } */
|
|
5
3
|
export async function handleAutoCommand(ctx) {
|
|
6
|
-
const { agent
|
|
4
|
+
const { agent } = ctx
|
|
7
5
|
agent.autoApprove = !agent.autoApprove
|
|
8
6
|
agent._pendingReminders = agent._pendingReminders ?? []
|
|
9
7
|
if (agent.autoApprove) {
|
|
@@ -11,11 +9,4 @@ export async function handleAutoCommand(ctx) {
|
|
|
11
9
|
} else {
|
|
12
10
|
agent._pendingReminders.push("[System reminder: AUTO mode is now OFF. Destructive tool calls now require user approval again. Confirm before writing files, running commands, or spawning subagents.]")
|
|
13
11
|
}
|
|
14
|
-
pushLabel(`❯ Auto`, ansi.bold + (agent.autoApprove ? C.warn : C.tool))
|
|
15
|
-
pushLine(
|
|
16
|
-
agent.autoApprove
|
|
17
|
-
? `AUTO ON: all tool calls (write/bash/subagent) auto-approved. For long tasks. /auto to disable.`
|
|
18
|
-
: `AUTO OFF: destructive tool calls require per-use approval again.`,
|
|
19
|
-
agent.autoApprove ? C.warn : C.dim,
|
|
20
|
-
)
|
|
21
12
|
}
|
package/src/tui/cmd-clear.mjs
CHANGED
package/src/tui/cmd-config.mjs
CHANGED
|
@@ -1,30 +1,46 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs"
|
|
2
2
|
import { ansi, C } from "./ansi.mjs"
|
|
3
3
|
|
|
4
|
-
/** /config
|
|
5
|
-
*
|
|
4
|
+
/** /config command: view and set agent/embedding 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
|
|
9
9
|
const { configPath } = await import("../config.mjs")
|
|
10
|
-
const cp = configPath
|
|
11
10
|
const ac = agent.config?.agent ?? {}
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
11
|
+
const ec = agent.config?.embedding ?? {}
|
|
12
|
+
|
|
13
|
+
function cfgSummary() {
|
|
14
|
+
const tn = `${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`
|
|
15
|
+
const vg = ac.verifyGuard === true ? "on" : "off"
|
|
16
|
+
return `agent.maxTurns=${ac.maxTurns ?? 100} | compactThreshold=${tn} | verifyGuard=${vg} | embedding=${agent.memory?.embedder ? "on" : "off"}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const mainEntries = [
|
|
20
|
+
{ type: "header", text: `Config: ${cfgSummary()}` },
|
|
21
|
+
{ type: "item", text: `agent.maxTurns = ${ac.maxTurns ?? 100}`, action: "agent.maxTurns" },
|
|
22
|
+
{ type: "item", text: `agent.subagentTurns = ${ac.subagentTurns ?? 100}`, action: "agent.subagentTurns" },
|
|
23
|
+
{ type: "item", text: `agent.compactThreshold = ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, action: "agent.compactThreshold" },
|
|
24
|
+
{ type: "item", text: `agent.verifyGuard = ${ac.verifyGuard === true ? "on" : "off"}`, action: "agent.verifyGuard" },
|
|
25
|
+
{ type: "item", text: "Set embedding API key", action: "embedkey" },
|
|
26
|
+
{ type: "item", text: `embedding.model = ${ec.model ?? "BAAI/bge-m3"}`, action: "embedding.model" },
|
|
27
|
+
{ type: "item", text: "View full config", action: "view" },
|
|
16
28
|
]
|
|
29
|
+
|
|
17
30
|
openPicker({
|
|
18
31
|
title: "Config",
|
|
19
|
-
entries,
|
|
32
|
+
entries: mainEntries,
|
|
20
33
|
onSelect: async (e) => {
|
|
21
34
|
if (e.action === "view") {
|
|
35
|
+
const cp = configPath
|
|
22
36
|
pushLabel(`❯ Config`, ansi.bold + C.tool)
|
|
23
37
|
pushLine(`Active: ${agent.activeProvider} / ${agent.provider.model}`, C.dim)
|
|
24
38
|
pushLine(`Key: ${maskKey(agent.provider.apiKey)}`, C.dim)
|
|
25
|
-
|
|
26
|
-
pushLine(`agent:
|
|
27
|
-
pushLine(`
|
|
39
|
+
pushLine(`agent.maxTurns: ${ac.maxTurns ?? 100}`, C.dim)
|
|
40
|
+
pushLine(`agent.subagentTurns: ${ac.subagentTurns ?? 100}`, C.dim)
|
|
41
|
+
pushLine(`agent.compactThreshold: ${ac.compactThreshold ?? 100000}${agent.config?.agent?.compactThresholdAuto ? " (auto)" : ""}`, C.dim)
|
|
42
|
+
pushLine(`agent.verifyGuard: ${ac.verifyGuard === true ? "on" : "off"}`, C.dim)
|
|
43
|
+
pushLine(`embedding: ${agent.memory?.embedder ? `enabled (${ec.model ?? ""})` : "disabled (FTS only)"}`, C.dim)
|
|
28
44
|
pushLine(`Config file: ${cp}`, C.dim)
|
|
29
45
|
return
|
|
30
46
|
}
|
|
@@ -42,30 +58,99 @@ export async function handleConfigCommand(ctx) {
|
|
|
42
58
|
pushLine(`Embedding key saved, vector search enabled`, C.tool)
|
|
43
59
|
return
|
|
44
60
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const parts = settext.split(/\s+/)
|
|
49
|
-
const [path, value] = [parts[0], parts.slice(1).join(" ")]
|
|
50
|
-
if (!path || !value) { pushLine("Usage: <path> <value> e.g. agent.maxTurns 80", C.error); return }
|
|
61
|
+
// Boolean toggle: agent.verifyGuard
|
|
62
|
+
if (e.action === "agent.verifyGuard") {
|
|
63
|
+
const newVal = ac.verifyGuard !== true // toggle: undefined/false → true, true → false
|
|
51
64
|
try {
|
|
52
|
-
const { configPath, loadConfig, saveConfig } = await import("../config.mjs")
|
|
53
65
|
const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : {}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
obj[keys[keys.length - 1]] = isNaN(value) ? value : Number(value)
|
|
66
|
+
raw.agent ??= {}
|
|
67
|
+
raw.agent.verifyGuard = newVal
|
|
68
|
+
const { saveConfig, loadConfig } = await import("../config.mjs")
|
|
58
69
|
saveConfig(raw)
|
|
59
70
|
const cfg = loadConfig()
|
|
60
71
|
agent.provider = cfg.provider
|
|
61
72
|
agent.providers = cfg.providersList
|
|
62
73
|
agent.activeProvider = cfg.activeProvider
|
|
63
74
|
agent.config = cfg
|
|
75
|
+
agent.config.agent ??= {}
|
|
64
76
|
pushLabel(`❯ Config`, ansi.bold + C.tool)
|
|
65
|
-
pushLine(`
|
|
77
|
+
pushLine(`agent.verifyGuard = ${newVal ? "on" : "off"}`, C.tool)
|
|
66
78
|
} catch (error) {
|
|
67
79
|
pushLine(`Save failed: ${error.message}`, C.error)
|
|
68
80
|
}
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
// embedding.model picker
|
|
84
|
+
if (e.action === "embedding.model") {
|
|
85
|
+
const models = [
|
|
86
|
+
{ label: "BAAI/bge-m3 (multilingual, 1024d)", value: "BAAI/bge-m3" },
|
|
87
|
+
{ label: "BAAI/bge-large-zh-v1.5 (Chinese, 1024d)", value: "BAAI/bge-large-zh-v1.5" },
|
|
88
|
+
{ label: "BAAI/bge-large-en-v1.5 (English, 1024d)", value: "BAAI/bge-large-en-v1.5" },
|
|
89
|
+
{ label: "text-embedding-3-small (OpenAI, 1536d)", value: "text-embedding-3-small" },
|
|
90
|
+
{ label: "text-embedding-3-large (OpenAI, 3072d)", value: "text-embedding-3-large" },
|
|
91
|
+
]
|
|
92
|
+
const currentVal = ec.model ?? "BAAI/bge-m3"
|
|
93
|
+
openPicker({
|
|
94
|
+
title: `Embedding model (current: ${currentVal})`,
|
|
95
|
+
entries: [
|
|
96
|
+
{ type: "header", text: `Current: ${currentVal}` },
|
|
97
|
+
...models.map((m) => ({ type: "item", text: m.label, action: m.value })),
|
|
98
|
+
],
|
|
99
|
+
onSelect: async (sel) => {
|
|
100
|
+
try {
|
|
101
|
+
const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : {}
|
|
102
|
+
raw.embedding ??= {}
|
|
103
|
+
raw.embedding.model = sel.action
|
|
104
|
+
const { saveConfig, loadConfig } = await import("../config.mjs")
|
|
105
|
+
saveConfig(raw)
|
|
106
|
+
const cfg = loadConfig()
|
|
107
|
+
agent.provider = cfg.provider
|
|
108
|
+
agent.providers = cfg.providersList
|
|
109
|
+
agent.activeProvider = cfg.activeProvider
|
|
110
|
+
agent.config = cfg
|
|
111
|
+
agent.config.agent ??= {}
|
|
112
|
+
pushLabel(`❯ Config`, ansi.bold + C.tool)
|
|
113
|
+
pushLine(`embedding.model = ${sel.action}`, C.tool)
|
|
114
|
+
} catch (error) {
|
|
115
|
+
pushLine(`Save failed: ${error.message}`, C.error)
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
// Numeric config items: ask for value, parse as number
|
|
122
|
+
const isNumeric = e.action.startsWith("agent.")
|
|
123
|
+
const label = e.action
|
|
124
|
+
const current = e.action === "agent.maxTurns" ? (ac.maxTurns ?? 100)
|
|
125
|
+
: e.action === "agent.subagentTurns" ? (ac.subagentTurns ?? 100)
|
|
126
|
+
: e.action === "agent.compactThreshold" ? (ac.compactThreshold ?? 100000)
|
|
127
|
+
: ""
|
|
128
|
+
const prompt = `${label} (current: ${current}):`
|
|
129
|
+
const val = await askQuestion(prompt)
|
|
130
|
+
if (!val) return
|
|
131
|
+
try {
|
|
132
|
+
const { configPath, loadConfig, saveConfig } = await import("../config.mjs")
|
|
133
|
+
const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf8")) : {}
|
|
134
|
+
if (isNumeric) {
|
|
135
|
+
const keys = label.split(".")
|
|
136
|
+
let obj = raw
|
|
137
|
+
for (let i = 0; i < keys.length - 1; i++) { obj[keys[i]] ??= {}; obj = obj[keys[i]] }
|
|
138
|
+
const num = Number(val)
|
|
139
|
+
if (isNaN(num)) { pushLine("Value must be a number", C.error); return }
|
|
140
|
+
obj[keys[keys.length - 1]] = num
|
|
141
|
+
}
|
|
142
|
+
saveConfig(raw)
|
|
143
|
+
const cfg = loadConfig()
|
|
144
|
+
agent.provider = cfg.provider
|
|
145
|
+
agent.providers = cfg.providersList
|
|
146
|
+
agent.activeProvider = cfg.activeProvider
|
|
147
|
+
agent.config = cfg
|
|
148
|
+
agent.config.agent ??= {}
|
|
149
|
+
pushLabel(`❯ Config`, ansi.bold + C.tool)
|
|
150
|
+
pushLine(`${label} = ${val}`, C.tool)
|
|
151
|
+
pushLine("(restart to apply to existing agent state)", C.dim)
|
|
152
|
+
} catch (error) {
|
|
153
|
+
pushLine(`Save failed: ${error.message}`, C.error)
|
|
69
154
|
}
|
|
70
155
|
},
|
|
71
156
|
})
|
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
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { C } from "./ansi.mjs"
|
|
2
|
+
|
|
3
|
+
/** /extract command: extract knowledge from current session.
|
|
4
|
+
* ctx: { runDistill, state, pushLine } */
|
|
3
5
|
export async function handleExtractCommand(ctx) {
|
|
4
|
-
|
|
6
|
+
const { runDistill, state, pushLine } = ctx
|
|
7
|
+
pushLine("[extract] Analyzing session...", C.dim)
|
|
8
|
+
const count = await runDistill()
|
|
9
|
+
const msg = count > 0
|
|
10
|
+
? `Knowledge extracted: ${count} candidate(s) saved to memory (use /skills to list, agent will recall via memory_search)`
|
|
11
|
+
: "No new knowledge found in this session."
|
|
12
|
+
pushLine(msg, count > 0 ? C.tool : C.dim)
|
|
5
13
|
}
|
package/src/tui/cmd-goal.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** /goal 命令:设置/查看/取消长期目标。
|
|
4
|
-
* 从 slash-commands.mjs 抽出。
|
|
1
|
+
/** /goal command: set/view/cancel long-term goal.
|
|
2
|
+
* Extracted from slash-commands.mjs.
|
|
5
3
|
* ctx: { agent, pushLine, pushLabel, openPicker, askQuestion } */
|
|
6
4
|
export async function handleGoalCommand(ctx) {
|
|
7
5
|
const { agent, pushLine, pushLabel, openPicker, askQuestion } = ctx
|
|
@@ -19,7 +17,6 @@ export async function handleGoalCommand(ctx) {
|
|
|
19
17
|
onSelect: async (e) => {
|
|
20
18
|
if (e.action === "view") {
|
|
21
19
|
const statusText = { active: "active", complete: "completed", blocked: "blocked" }[agent.goal.status] ?? agent.goal.status
|
|
22
|
-
pushLabel(`❯ Goal`, ansi.bold + C.warn)
|
|
23
20
|
pushLine(`Goal: ${agent.goal.objective}`, C.tool)
|
|
24
21
|
if (agent.goal.criteria) pushLine(` Criteria: ${agent.goal.criteria}`, C.dim)
|
|
25
22
|
pushLine(` Status: ${statusText} │ Turns used: ${agent.goal.turnsUsed ?? 0} │ Set at: ${new Date(agent.goal.setAt).toLocaleString()}`, C.dim)
|
|
@@ -27,21 +24,15 @@ export async function handleGoalCommand(ctx) {
|
|
|
27
24
|
}
|
|
28
25
|
if (e.action === "cancel") {
|
|
29
26
|
agent.goal = null
|
|
30
|
-
pushLabel(`❯ Goal`, ansi.bold + C.dim)
|
|
31
|
-
pushLine(`Goal cancelled.`, C.dim)
|
|
32
27
|
return
|
|
33
28
|
}
|
|
34
|
-
// set —
|
|
29
|
+
// set — requires entering goal text
|
|
35
30
|
const goalText = await askQuestion("Enter goal description (; separates criteria)")
|
|
36
31
|
if (!goalText) return
|
|
37
32
|
const semi = goalText.indexOf(";") >= 0 ? ";" : goalText.indexOf(";") >= 0 ? ";" : null
|
|
38
33
|
const objective = semi ? goalText.slice(0, semi).trim() : goalText.trim()
|
|
39
34
|
const criteria = semi ? goalText.slice(semi + 1).trim() : ""
|
|
40
35
|
agent.goal = { objective, criteria, setAt: Date.now(), status: "active", turnsUsed: 0, _blockTally: null }
|
|
41
|
-
pushLabel(`❯ Goal`, ansi.bold + C.warn)
|
|
42
|
-
pushLine(`Goal set: ${objective}`, C.tool)
|
|
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)
|
|
45
36
|
},
|
|
46
37
|
})
|
|
47
38
|
}
|
package/src/tui/cmd-help.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
|
|
7
7
|
const aliasList = { "/help": "/h", "/exit": "/x", "/model": "/m", "/plan": "/p", "/think": "/t", "/clear": "/c", "/new": "/n" }
|
|
8
|
-
const order = ["Agent", "Session", "
|
|
8
|
+
const order = ["Agent", "Session", "Project", "System"]
|
|
9
9
|
const byGroup = new Map()
|
|
10
10
|
for (const c of SLASH_COMMANDS) {
|
|
11
11
|
if (!c.group) continue
|
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)
|
|
@@ -120,26 +120,39 @@ export async function handleMcpCommand(ctx) {
|
|
|
120
120
|
return
|
|
121
121
|
}
|
|
122
122
|
if (e.action === "add") {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
123
|
+
// Pick transport type first, then ask name + URL/command
|
|
124
|
+
openPicker({
|
|
125
|
+
title: "MCP Transport",
|
|
126
|
+
entries: [
|
|
127
|
+
{ type: "header", text: "Select server transport" },
|
|
128
|
+
{ type: "item", text: "HTTP (https://…)", action: "http" },
|
|
129
|
+
{ type: "item", text: "WebSocket (ws://…)", action: "ws" },
|
|
130
|
+
{ type: "item", text: "stdio (local command)", action: "stdio" },
|
|
131
|
+
],
|
|
132
|
+
onSelect: async (te) => {
|
|
133
|
+
const name = await askQuestion("Server name:")
|
|
134
|
+
if (!name) return
|
|
135
|
+
const existing = (agent.config?.mcp?.servers ?? []).find((s) => s.name === name)
|
|
136
|
+
if (existing) { pushLine(`[mcp] "${name}" already exists`, C.error); return }
|
|
137
|
+
if (te.action === "stdio") {
|
|
138
|
+
const cmd = await askQuestion("Command (e.g. npx, python):")
|
|
139
|
+
if (!cmd) return
|
|
140
|
+
const argsInput = await askQuestion("Arguments (space-separated, or leave empty):")
|
|
141
|
+
const args = argsInput ? argsInput.split(/\s+/) : undefined
|
|
142
|
+
await addAndConnect(ctx, { name, command: cmd, args })
|
|
143
|
+
} else {
|
|
144
|
+
const urlPrompt = te.action === "ws" ? "WebSocket URL (ws://…):" : "HTTP URL (https://…):"
|
|
145
|
+
const url = await askQuestion(urlPrompt)
|
|
146
|
+
if (!url) return
|
|
147
|
+
const headersInput = await askQuestion("Headers (key=value, space-separated, or leave empty):")
|
|
148
|
+
const headers = headersInput ? parseHeaders(headersInput.split(/\s+/)) : undefined
|
|
149
|
+
const srv = te.action === "ws"
|
|
150
|
+
? { name, wsUrl: url, headers: Object.keys(headers ?? {}).length > 0 ? headers : undefined }
|
|
151
|
+
: { name, url, headers: Object.keys(headers ?? {}).length > 0 ? headers : undefined }
|
|
152
|
+
await addAndConnect(ctx, srv)
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
})
|
|
143
156
|
}
|
|
144
157
|
},
|
|
145
158
|
})
|
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,18 +1,38 @@
|
|
|
1
1
|
import { clearSession } from "../session.mjs"
|
|
2
2
|
import { C } from "./ansi.mjs"
|
|
3
3
|
|
|
4
|
-
/** /new
|
|
5
|
-
* ctx: { agent, state, pushLine } */
|
|
4
|
+
/** /new command: start new session (old session archived to slot).
|
|
5
|
+
* ctx: { agent, state, pushLine, openPicker, render } */
|
|
6
6
|
export async function handleNewCommand(ctx) {
|
|
7
|
-
const { agent, state, pushLine } = ctx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
const { agent, state, pushLine, openPicker, render } = ctx
|
|
8
|
+
|
|
9
|
+
const doNewSession = () => {
|
|
10
|
+
agent.history = []
|
|
11
|
+
agent.tasks = []
|
|
12
|
+
agent.planMode = false
|
|
13
|
+
agent.goal = null
|
|
14
|
+
agent._pendingReminders = []
|
|
15
|
+
state.tasks = []
|
|
16
|
+
state.lines = []
|
|
17
|
+
state.streaming = ""
|
|
18
|
+
clearSession(agent.cwd)
|
|
19
|
+
render()
|
|
20
|
+
pushLine("New session started (old session archived to slot; /session to view)", C.dim)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (agent.history.length > 0) {
|
|
24
|
+
openPicker({
|
|
25
|
+
title: "Start new session?",
|
|
26
|
+
entries: [
|
|
27
|
+
{ type: "item", text: "Yes, archive current and start new", action: "yes" },
|
|
28
|
+
{ type: "item", text: "Cancel", action: "no" },
|
|
29
|
+
],
|
|
30
|
+
defaultIndex: 1,
|
|
31
|
+
onSelect: (e) => {
|
|
32
|
+
if (e.action === "yes") doNewSession()
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
doNewSession()
|
|
18
38
|
}
|
package/src/tui/cmd-plan.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** /plan 命令:切换 plan mode(只读探索 → 设计 → 实现)。
|
|
4
|
-
* ctx: { agent, pushLine, pushLabel } */
|
|
1
|
+
/** /plan command: toggle plan mode (read-only explore → design → implement).
|
|
2
|
+
* ctx: { agent } */
|
|
5
3
|
export async function handlePlanCommand(ctx) {
|
|
6
|
-
const { agent
|
|
4
|
+
const { agent } = ctx
|
|
7
5
|
agent.planMode = !agent.planMode
|
|
8
6
|
agent._pendingReminders = agent._pendingReminders ?? []
|
|
9
7
|
if (agent.planMode) {
|
|
@@ -11,11 +9,4 @@ export async function handlePlanCommand(ctx) {
|
|
|
11
9
|
} else {
|
|
12
10
|
agent._pendingReminders.push("[System reminder: plan mode is now OFF. You may edit files, run commands, and implement changes.]")
|
|
13
11
|
}
|
|
14
|
-
pushLabel(`❯ Plan`, ansi.bold + (agent.planMode ? C.tool : C.dim))
|
|
15
|
-
pushLine(
|
|
16
|
-
agent.planMode
|
|
17
|
-
? `Plan mode ON: read-only tools only. Design first, then implement. /plan again to exit.`
|
|
18
|
-
: `Plan mode OFF: you may now edit files and run commands.`,
|
|
19
|
-
agent.planMode ? C.tool : C.dim,
|
|
20
|
-
)
|
|
21
12
|
}
|
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,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 从 slash-commands.mjs 抽出。
|
|
5
|
-
* ctx: { agent, pushLine, pushLabel, openPicker, syncProviderField } */
|
|
1
|
+
/** /think command: toggle thinking mode, set reasoning effort.
|
|
2
|
+
* Extracted from slash-commands.mjs.
|
|
3
|
+
* ctx: { agent, openPicker, syncProviderField } */
|
|
6
4
|
export async function handleThinkCommand(ctx) {
|
|
7
|
-
const { agent,
|
|
5
|
+
const { agent, openPicker, syncProviderField } = ctx
|
|
8
6
|
const cur = agent.provider
|
|
9
7
|
const thinkingEnabled = cur.thinking?.type === "enabled" || cur.thinking?.type === undefined
|
|
10
8
|
const { specForModel } = await import("../config.mjs")
|
|
@@ -30,8 +28,6 @@ export async function handleThinkCommand(ctx) {
|
|
|
30
28
|
if (e.action === "effort") {
|
|
31
29
|
cur.reasoningEffort = e.level
|
|
32
30
|
await syncProviderField("reasoningEffort", e.level)
|
|
33
|
-
pushLabel(`❯ Think`, ansi.bold + C.tool)
|
|
34
|
-
pushLine(`Reasoning effort set to ${e.level}`, C.tool)
|
|
35
31
|
} else {
|
|
36
32
|
const enable = e.action === "on"
|
|
37
33
|
if (isEffortOnly) {
|
|
@@ -47,9 +43,6 @@ export async function handleThinkCommand(ctx) {
|
|
|
47
43
|
if (!enable) await syncProviderField("reasoningEffort", undefined)
|
|
48
44
|
else await syncProviderField("reasoningEffort", cur.reasoningEffort)
|
|
49
45
|
}
|
|
50
|
-
pushLabel(`❯ Think`, ansi.bold + C.tool)
|
|
51
|
-
pushLine(`Thinking mode ${enable ? "On" : "Off"}`, C.tool)
|
|
52
|
-
if (enable) pushLine(`Reasoning effort: ${cur.reasoningEffort}`, C.dim)
|
|
53
46
|
}
|
|
54
47
|
},
|
|
55
48
|
})
|
|
@@ -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 "***"
|