thincoder 0.6.0 → 0.7.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.
- package/README.md +19 -1
- package/bin/thincoder.mjs +25 -1
- package/package.json +1 -1
- package/src/SYSTEM_PROMPT.md +6 -3
- package/src/agent.mjs +147 -64
- package/src/checkpoint.mjs +6 -3
- package/src/coder-overlay.md +1 -1
- package/src/config.mjs +37 -27
- package/src/context.mjs +37 -8
- package/src/distill.mjs +6 -2
- package/src/embedding.mjs +11 -2
- package/src/gitmem.mjs +6 -2
- package/src/markdown.mjs +11 -4
- package/src/mcp.mjs +197 -24
- package/src/memory.mjs +242 -81
- package/src/provider.mjs +35 -14
- package/src/repomap.mjs +17 -6
- package/src/session.mjs +8 -5
- package/src/skills.mjs +6 -2
- package/src/tools/apply_patch.md +11 -0
- package/src/tools/bash.md +13 -1
- package/src/tools/checkpoint.md +11 -0
- package/src/tools/grep.md +3 -0
- package/src/tools/insert_after.md +13 -0
- package/src/tools/question.md +1 -0
- package/src/tools/syntax_check.md +10 -0
- package/src/tools/websearch.md +1 -0
- package/src/tools.mjs +483 -84
- package/src/tui.mjs +156 -55
package/src/tui.mjs
CHANGED
|
@@ -206,7 +206,6 @@ export function layoutInput(chars, cursor, width) {
|
|
|
206
206
|
return { lines, cursorLine, cursorCol }
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
/** 文本按宽度折行(保留 \n),返回行数组 */
|
|
210
209
|
/**
|
|
211
210
|
* 显示净化:控制字符会破坏终端网格数学(\r 回车覆盖、\t 宽度误判致整帧错位、ANSI/响铃冲屏)。
|
|
212
211
|
* 只动显示层——模型看到的工具结果原文不变;session 里已存的脏 display 回放时也经此净化。
|
|
@@ -222,6 +221,7 @@ export function sanitizeDisplay(s) {
|
|
|
222
221
|
.replace(/\n+$/, "")
|
|
223
222
|
}
|
|
224
223
|
|
|
224
|
+
/** 文本按宽度折行(保留 \n),返回行数组 */
|
|
225
225
|
export function wrapText(text, width) {
|
|
226
226
|
const lines = []
|
|
227
227
|
for (const rawLine of text.split("\n")) {
|
|
@@ -406,9 +406,17 @@ export async function startTUI(agent, opts = {}) {
|
|
|
406
406
|
let boxLines = inputLines
|
|
407
407
|
if (state.question) {
|
|
408
408
|
const q = state.question
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
if (q.options.length > 0) {
|
|
410
|
+
// 选项窗口:只显示选中项 ±2,选项过多时防输入框无限增高撑破锚定布局
|
|
411
|
+
const sel = q.selected ?? 0
|
|
412
|
+
const QWIN = 5
|
|
413
|
+
const start = Math.max(0, Math.min(sel - 2, q.options.length - QWIN))
|
|
414
|
+
boxLines = q.options
|
|
415
|
+
.slice(start, start + QWIN)
|
|
416
|
+
.map((opt, i) => (start + i === sel ? "▸ " : " ") + opt)
|
|
417
|
+
} else {
|
|
418
|
+
boxLines = ["▸ " + (q.answer ?? "")]
|
|
419
|
+
}
|
|
412
420
|
}
|
|
413
421
|
const inputBoxH = boxLines.length + 2
|
|
414
422
|
|
|
@@ -434,8 +442,18 @@ export async function startTUI(agent, opts = {}) {
|
|
|
434
442
|
const taskPanelH = visibleTasks.length
|
|
435
443
|
// 子 agent 流式输出占位(显示时占最多 2 行)
|
|
436
444
|
const subOutLen = (state.subOutput && state.processing) ? wrapText(state.subOutput, W - 8).slice(-2).length : 0
|
|
437
|
-
//
|
|
438
|
-
|
|
445
|
+
// 权限预览占位:字符数之外再封顶显示行数(rows-8),多行短行也能把帧撑过终端高度,破坏锚定布局
|
|
446
|
+
let permPreviewLines = []
|
|
447
|
+
if (state.permission) {
|
|
448
|
+
const maxLines = Math.max(1, rows - 8)
|
|
449
|
+
outer: for (const l of state.permissionPreview) {
|
|
450
|
+
for (const wrapped of wrapText(` ${l}`, W - 1)) {
|
|
451
|
+
if (permPreviewLines.length >= maxLines) break outer
|
|
452
|
+
permPreviewLines.push(wrapped)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const permPreviewLen = state.permission ? 1 + permPreviewLines.length : 0
|
|
439
457
|
const convH = Math.max(1, rows - headerH - inputBoxH - statusH - pickerH - taskPanelH - subOutLen - permPreviewLen)
|
|
440
458
|
|
|
441
459
|
// 对话区内容行(含流式缓冲);markdown 表格先按显示宽度重排
|
|
@@ -519,13 +537,11 @@ export async function startTUI(agent, opts = {}) {
|
|
|
519
537
|
}
|
|
520
538
|
}
|
|
521
539
|
|
|
522
|
-
//
|
|
540
|
+
// 权限审批内容预览(黄色,紧挨输入框上方);用上方已封顶的 permPreviewLines,渲染行数与占位一致
|
|
523
541
|
if (state.permission) {
|
|
524
542
|
out.push(`${ansi.bold}${C.warn}❯ 权限请求${ansi.reset}${ansi.clearLine}`)
|
|
525
|
-
for (const
|
|
526
|
-
|
|
527
|
-
out.push(`${C.warn}${wrapped}${ansi.reset}${ansi.clearLine}`)
|
|
528
|
-
}
|
|
543
|
+
for (const wrapped of permPreviewLines) {
|
|
544
|
+
out.push(`${C.warn}${wrapped}${ansi.reset}${ansi.clearLine}`)
|
|
529
545
|
}
|
|
530
546
|
}
|
|
531
547
|
|
|
@@ -534,7 +550,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
534
550
|
let title
|
|
535
551
|
if (state.question) {
|
|
536
552
|
borderColor = C.tool
|
|
537
|
-
title =
|
|
553
|
+
title = " Question "
|
|
538
554
|
} else if (state.permission) {
|
|
539
555
|
borderColor = C.warn
|
|
540
556
|
if (state.permission.name === "continue") {
|
|
@@ -697,8 +713,8 @@ export async function startTUI(agent, opts = {}) {
|
|
|
697
713
|
|
|
698
714
|
const callbacks = {
|
|
699
715
|
onToken: (t) => {
|
|
700
|
-
// 子 agent 流式输出:前缀匹配 explore/coder/plan
|
|
701
|
-
const subMatch = t.match(/^(explore|coder|plan)\//)
|
|
716
|
+
// 子 agent 流式输出:前缀匹配 explore/coder/plan/sub(无角色子 agent 用 sub/)的 token 进 subOutput
|
|
717
|
+
const subMatch = t.match(/^(explore|coder|plan|sub)\//)
|
|
702
718
|
if (subMatch) {
|
|
703
719
|
state.currentSub = subMatch[1]
|
|
704
720
|
state.subOutput = (state.subOutput + t.slice(subMatch[0].length)).slice(-300)
|
|
@@ -710,6 +726,14 @@ export async function startTUI(agent, opts = {}) {
|
|
|
710
726
|
scheduleRender()
|
|
711
727
|
},
|
|
712
728
|
onReasoning: (t) => {
|
|
729
|
+
// 子 agent 的思考 token 同样带 role/ 前缀,进 subOutput 滚动区,不污染主思考流
|
|
730
|
+
const subMatch = t.match(/^(explore|coder|plan|sub)\//)
|
|
731
|
+
if (subMatch) {
|
|
732
|
+
state.currentSub = subMatch[1]
|
|
733
|
+
state.subOutput = (state.subOutput + t.slice(subMatch[0].length)).slice(-300)
|
|
734
|
+
scheduleRender()
|
|
735
|
+
return
|
|
736
|
+
}
|
|
713
737
|
ensureAssistantLabel()
|
|
714
738
|
state.reasoning += t
|
|
715
739
|
scheduleRender()
|
|
@@ -722,8 +746,9 @@ export async function startTUI(agent, opts = {}) {
|
|
|
722
746
|
},
|
|
723
747
|
onToolResult: (name, result) => {
|
|
724
748
|
state.currentTool = null
|
|
725
|
-
// 子 agent
|
|
726
|
-
|
|
749
|
+
// 子 agent 结束(父 agent 侧的 subagent 工具结果带着最终报告):清空流式缓冲,报告进对话区。
|
|
750
|
+
// 注意只能用精确匹配——子 agent 内部工具调用不 relay 到 TUI(刷了满屏的教训)
|
|
751
|
+
const isSubagent = name === "subagent"
|
|
727
752
|
if (isSubagent) {
|
|
728
753
|
state.subOutput = ""
|
|
729
754
|
state.currentSub = null
|
|
@@ -877,6 +902,10 @@ export async function startTUI(agent, opts = {}) {
|
|
|
877
902
|
...cap(args.new_string ?? "", 500).split("\n").map((l) => `+ ${l}`),
|
|
878
903
|
]
|
|
879
904
|
}
|
|
905
|
+
if (base === "apply_patch") {
|
|
906
|
+
// 补丁本身就是可读的 diff,直接预览
|
|
907
|
+
return cap(args.patch ?? "", 1500).split("\n")
|
|
908
|
+
}
|
|
880
909
|
if (base === "delete") return [`${args.path}${args.force ? "(force:跟踪文件也删)" : ""}`]
|
|
881
910
|
if (base === "subagent") return cap(args.task ?? "", 500).split("\n")
|
|
882
911
|
if (base === "memory_put") return [`[${args.type ?? ""}] ${args.title ?? ""}`, ...cap(args.content ?? "", 500).split("\n")]
|
|
@@ -917,6 +946,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
917
946
|
{ name: "/model", group: "Agent", desc: "选择模型" },
|
|
918
947
|
{ name: "/goal", group: "Agent", desc: "设置/查看/取消长期目标" },
|
|
919
948
|
{ name: "/think", group: "Agent", desc: "思维模式与推理强度" },
|
|
949
|
+
{ name: "/init", group: "Tools", desc: "生成项目 AGENTS.md 骨架" },
|
|
920
950
|
{ name: "/skills", group: "Tools", desc: "列出项目技能" },
|
|
921
951
|
{ name: "/mcp", group: "Tools", desc: "管理 MCP server" },
|
|
922
952
|
{ name: "/provider", group: "Config", desc: "管理 provider(增/删/配 key)" },
|
|
@@ -1032,6 +1062,74 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1032
1062
|
case "/distill":
|
|
1033
1063
|
await runDistill()
|
|
1034
1064
|
return
|
|
1065
|
+
case "/init": {
|
|
1066
|
+
const { existsSync } = await import("node:fs")
|
|
1067
|
+
const { writeFile, readFile } = await import("node:fs/promises")
|
|
1068
|
+
const { join, basename } = await import("node:path")
|
|
1069
|
+
const agPath = join(agent.cwd, "AGENTS.md")
|
|
1070
|
+
if (existsSync(agPath)) {
|
|
1071
|
+
pushLine(`AGENTS.md 已存在: ${agPath}`, C.warn)
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// 探测项目类型与关键信息
|
|
1076
|
+
let name = basename(agent.cwd)
|
|
1077
|
+
let lang = "", cmds = ""
|
|
1078
|
+
|
|
1079
|
+
// Node.js
|
|
1080
|
+
try {
|
|
1081
|
+
const pkg = JSON.parse(await readFile(join(agent.cwd, "package.json"), "utf8"))
|
|
1082
|
+
if (pkg.name) name = pkg.name
|
|
1083
|
+
lang = "Node.js"
|
|
1084
|
+
const ks = Object.keys(pkg.scripts ?? {})
|
|
1085
|
+
if (ks.length) cmds = ks.slice(0, 5).map(k => `- \`npm run ${k}\``).join("\n")
|
|
1086
|
+
} catch {}
|
|
1087
|
+
|
|
1088
|
+
// Python
|
|
1089
|
+
if (!lang) {
|
|
1090
|
+
for (const f of ["requirements.txt", "pyproject.toml", "setup.py", "setup.cfg"]) {
|
|
1091
|
+
if (existsSync(join(agent.cwd, f))) { lang = "Python"; break }
|
|
1092
|
+
}
|
|
1093
|
+
if (lang) cmds = "- `pip install -r requirements.txt`\n- `python -m pytest`"
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Go
|
|
1097
|
+
if (!lang) {
|
|
1098
|
+
if (existsSync(join(agent.cwd, "go.mod"))) {
|
|
1099
|
+
lang = "Go"
|
|
1100
|
+
cmds = "- `go build ./...`\n- `go test ./...`"
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Rust
|
|
1105
|
+
if (!lang) {
|
|
1106
|
+
if (existsSync(join(agent.cwd, "Cargo.toml"))) {
|
|
1107
|
+
lang = "Rust"
|
|
1108
|
+
cmds = "- `cargo build`\n- `cargo test`"
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Java / Kotlin
|
|
1113
|
+
if (!lang) {
|
|
1114
|
+
if (existsSync(join(agent.cwd, "pom.xml"))) { lang = "Java (Maven)"; cmds = "- `mvn test`" }
|
|
1115
|
+
else if (existsSync(join(agent.cwd, "build.gradle")) || existsSync(join(agent.cwd, "build.gradle.kts"))) {
|
|
1116
|
+
lang = "Java/Kotlin (Gradle)"; cmds = "- `gradle test`"
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const lines = [`# ${name}`, ""]
|
|
1121
|
+
if (lang) {
|
|
1122
|
+
lines.push(`## 技术栈`, "", lang, "")
|
|
1123
|
+
if (cmds) lines.push(`## 命令`, "", cmds, "")
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const template = lines.join("\n")
|
|
1127
|
+
await writeFile(agPath, template, "utf8")
|
|
1128
|
+
pushLabel(`❯ Init`, ansi.bold + C.tool)
|
|
1129
|
+
pushLine(`已生成 AGENTS.md → ${agPath}${lang ? ` (${lang})` : ""}`, C.tool)
|
|
1130
|
+
if (lang) pushLine("可继续告诉我项目信息,我来补充约定和结构", C.dim)
|
|
1131
|
+
return
|
|
1132
|
+
}
|
|
1035
1133
|
case "/rewind": {
|
|
1036
1134
|
const { listCheckpoints, rewind, isGitRepo } = await import("./checkpoint.mjs")
|
|
1037
1135
|
if (!isGitRepo(agent.cwd)) {
|
|
@@ -1132,60 +1230,52 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1132
1230
|
const servers = agent.config?.mcp?.servers ?? []
|
|
1133
1231
|
pushLabel(`❯ MCP Servers`, ansi.bold + C.tool)
|
|
1134
1232
|
if (servers.length === 0) {
|
|
1135
|
-
pushLine("(无 MCP server——使用 /mcp add <name> <command>
|
|
1233
|
+
pushLine("(无 MCP server——使用 /mcp add <name> <url|command> 添加)", C.dim)
|
|
1136
1234
|
}
|
|
1137
1235
|
for (const srv of servers) {
|
|
1138
1236
|
const connected = agent.tools.some((t) => t._mcpName === srv.name)
|
|
1139
1237
|
const mark = connected ? "●" : "○"
|
|
1140
1238
|
const color = connected ? C.tool : C.dim
|
|
1141
1239
|
const toolCount = agent.tools.filter((t) => t._mcpName === srv.name).length
|
|
1142
|
-
const desc = srv.url ? srv.url : `${srv.command} ${(srv.args ?? []).join(" ")}`
|
|
1240
|
+
const desc = srv.wsUrl ? srv.wsUrl : srv.url ? srv.url : `${srv.command} ${(srv.args ?? []).join(" ")}`
|
|
1143
1241
|
pushLine(` ${mark} ${srv.name}: ${desc} (${toolCount} tools)`, color)
|
|
1144
1242
|
}
|
|
1145
1243
|
pushLabel(`❯ 操作`, ansi.bold + C.tool)
|
|
1146
|
-
pushLine(
|
|
1147
|
-
pushLine(
|
|
1148
|
-
pushLine(
|
|
1149
|
-
pushLine(
|
|
1150
|
-
pushLine(
|
|
1244
|
+
pushLine("/mcp add <name> <url|command> [args|headers...]", C.dim)
|
|
1245
|
+
pushLine(" URL 自动识别: https://… → HTTP, ws://… → WebSocket, 其他 → stdio 命令", C.dim)
|
|
1246
|
+
pushLine(" 例: /mcp add myapi https://api.example.com/mcp Authorization=\"Bearer x\"", C.dim)
|
|
1247
|
+
pushLine(" 例: /mcp add github npx -y @modelcontextprotocol/server-github", C.dim)
|
|
1248
|
+
pushLine(`/mcp remove <name> 断开并移除`, C.dim)
|
|
1249
|
+
pushLine(`/mcp connect <name> 重连已配置的 server`, C.dim)
|
|
1151
1250
|
return
|
|
1152
1251
|
}
|
|
1153
|
-
// ---- /mcp add <name> <command> [args...] (
|
|
1154
|
-
|
|
1252
|
+
// ---- /mcp add <name> <url|command> [args|headers...] (统一入口,自动识别传输类型) ----
|
|
1253
|
+
// url / ws 子命令作为别名保留(兼容旧配置)
|
|
1254
|
+
if (sub === "add" || sub === "url" || sub === "ws") {
|
|
1155
1255
|
const args = rest.slice(1)
|
|
1156
1256
|
if (args.length < 2) {
|
|
1157
|
-
pushLine("用法: /mcp add <name> <command> [args...]", C.error)
|
|
1158
|
-
pushLine("
|
|
1257
|
+
pushLine("用法: /mcp add <name> <url|command> [args|headers...]", C.error)
|
|
1258
|
+
pushLine(" URL 自动识别: https://… → HTTP, ws://… → WebSocket, 其他 → stdio 命令", C.dim)
|
|
1159
1259
|
return
|
|
1160
1260
|
}
|
|
1161
1261
|
const name = args[0]
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1262
|
+
const second = args[1]
|
|
1263
|
+
const extras = args.slice(2)
|
|
1164
1264
|
const existing = (agent.config?.mcp?.servers ?? []).find((s) => s.name === name)
|
|
1165
1265
|
if (existing) { pushLine(`[mcp] "${name}" 已存在,用 /mcp remove ${name} 先移除`, C.error); return }
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
const name = args[0]
|
|
1179
|
-
const url = args[1]
|
|
1180
|
-
const headerPairs = args.slice(2)
|
|
1181
|
-
const existing = (agent.config?.mcp?.servers ?? []).find((s) => s.name === name)
|
|
1182
|
-
if (existing) { pushLine(`[mcp] "${name}" 已存在,用 /mcp remove ${name} 先移除`, C.error); return }
|
|
1183
|
-
const headers = {}
|
|
1184
|
-
for (const pair of headerPairs) {
|
|
1185
|
-
const eq = pair.indexOf("=")
|
|
1186
|
-
if (eq > 0) headers[pair.slice(0, eq)] = pair.slice(eq + 1).replace(/^["']|["']$/g, "")
|
|
1266
|
+
|
|
1267
|
+
const isWS = /^wss?:\/\//.test(second)
|
|
1268
|
+
const isHTTP = /^https?:\/\//.test(second)
|
|
1269
|
+
let srv
|
|
1270
|
+
if (isWS || sub === "ws") {
|
|
1271
|
+
const headers = parseHeaders(extras)
|
|
1272
|
+
srv = { name, wsUrl: second, headers: Object.keys(headers).length > 0 ? headers : undefined }
|
|
1273
|
+
} else if (isHTTP || sub === "url") {
|
|
1274
|
+
const headers = parseHeaders(extras)
|
|
1275
|
+
srv = { name, url: second, headers: Object.keys(headers).length > 0 ? headers : undefined }
|
|
1276
|
+
} else {
|
|
1277
|
+
srv = { name, command: second, args: extras.length > 0 ? extras : undefined }
|
|
1187
1278
|
}
|
|
1188
|
-
const srv = { name, url, headers: Object.keys(headers).length > 0 ? headers : undefined }
|
|
1189
1279
|
await addAndConnect(srv)
|
|
1190
1280
|
return
|
|
1191
1281
|
}
|
|
@@ -1206,7 +1296,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1206
1296
|
const name = rest[1]
|
|
1207
1297
|
if (!name) { pushLine("用法: /mcp connect <name>", C.error); return }
|
|
1208
1298
|
const srv = (agent.config?.mcp?.servers ?? []).find((s) => s.name === name)
|
|
1209
|
-
if (!srv) { pushLine(`[mcp] "${name}" 未在配置中找到(先用 /mcp add
|
|
1299
|
+
if (!srv) { pushLine(`[mcp] "${name}" 未在配置中找到(先用 /mcp add 添加)`, C.error); return }
|
|
1210
1300
|
const { removeMcpTools, connectMcpServer } = await import("./mcp.mjs")
|
|
1211
1301
|
removeMcpTools(agent, name)
|
|
1212
1302
|
try {
|
|
@@ -1220,16 +1310,27 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1220
1310
|
}
|
|
1221
1311
|
return
|
|
1222
1312
|
}
|
|
1223
|
-
pushLine(`未知子命令: ${sub}(/mcp list | add |
|
|
1313
|
+
pushLine(`未知子命令: ${sub}(/mcp list | add | remove | connect)`, C.error)
|
|
1224
1314
|
return
|
|
1225
1315
|
}
|
|
1226
1316
|
|
|
1317
|
+
// ---- header 解析(/mcp add 共享)----
|
|
1318
|
+
function parseHeaders(pairs) {
|
|
1319
|
+
const headers = {}
|
|
1320
|
+
for (const pair of pairs) {
|
|
1321
|
+
const eq = pair.indexOf("=")
|
|
1322
|
+
if (eq > 0) headers[pair.slice(0, eq)] = pair.slice(eq + 1).replace(/^["']|["']$/g, "")
|
|
1323
|
+
}
|
|
1324
|
+
return headers
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1227
1327
|
// ---- /mcp 共享 helper: 保存配置 + 连接 ----
|
|
1228
1328
|
async function addAndConnect(srv) {
|
|
1229
1329
|
await persistRaw((raw) => {
|
|
1230
1330
|
raw.mcp ??= { servers: [] }
|
|
1231
1331
|
const entry = { name: srv.name }
|
|
1232
1332
|
if (srv.url) { entry.url = srv.url; if (srv.headers) entry.headers = srv.headers }
|
|
1333
|
+
else if (srv.wsUrl) { entry.wsUrl = srv.wsUrl; if (srv.headers) entry.headers = srv.headers }
|
|
1233
1334
|
else { entry.command = srv.command; if (srv.args) entry.args = srv.args }
|
|
1234
1335
|
raw.mcp.servers.push(entry)
|
|
1235
1336
|
})
|
|
@@ -1242,7 +1343,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1242
1343
|
const tools = await connectMcpServer(srv)
|
|
1243
1344
|
agent.tools.push(...tools)
|
|
1244
1345
|
pushLabel(`❯ MCP`, ansi.bold + C.tool)
|
|
1245
|
-
const desc = srv.url ? srv.url : `${srv.command} ${(srv.args ?? []).join(" ")}`
|
|
1346
|
+
const desc = srv.wsUrl ? srv.wsUrl : srv.url ? srv.url : `${srv.command} ${(srv.args ?? []).join(" ")}`
|
|
1246
1347
|
pushLine(`${srv.name} (${desc}) 已连接,${tools.length} 个工具:`, C.tool)
|
|
1247
1348
|
for (const t of tools) pushLine(` ${t.name}: ${t.description.slice(0, 100)}`, C.dim)
|
|
1248
1349
|
} catch (error) {
|
|
@@ -1557,7 +1658,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
1557
1658
|
if (cmd === "/config" && argIndex === 0) return match(["embedkey", "set"])
|
|
1558
1659
|
if (cmd === "/goal" && argIndex === 0) return match(["set", "cancel"])
|
|
1559
1660
|
if (cmd === "/mcp") {
|
|
1560
|
-
if (argIndex === 0) return match(["add", "url", "remove", "connect", "list"])
|
|
1661
|
+
if (argIndex === 0) return match(["add", "url", "ws", "remove", "connect", "list"])
|
|
1561
1662
|
if (argIndex === 1 && (parts[1] === "remove" || parts[1] === "connect")) return match((agent.config?.mcp?.servers ?? []).map((s) => s.name))
|
|
1562
1663
|
}
|
|
1563
1664
|
return []
|