thincoder 0.12.60 → 0.12.61
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/CHANGELOG.md +24 -1
- package/README.md +8 -6
- package/bin/thincoder.mjs +27 -122
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +36 -134
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +124 -329
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +3 -2
- package/src/agent/completion.mjs +1 -1
- package/src/agent/dispatch.mjs +47 -12
- package/src/agent/helpers.mjs +71 -13
- package/src/agent/record-results.mjs +13 -5
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +21 -6
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +26 -9
- package/src/agent-tools/advisor-async.mjs +70 -180
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +69 -20
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +23 -10
- package/src/agent-tools/design-token.mjs +14 -1
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +3 -3
- package/src/agent-tools/escalate-async.mjs +16 -13
- package/src/agent-tools/read-history.mjs +13 -3
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +15 -6
- package/src/agent-tools/subagent-async.mjs +66 -14
- package/src/agent-tools/subagent-panel.mjs +22 -15
- package/src/agent-tools/subagent-run.mjs +9 -6
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +63 -16
- package/src/agent-tools/subagent.mjs +175 -49
- package/src/agent-tools/verify.mjs +13 -34
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +38 -21
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +23 -7
- package/src/cli/memory-command.mjs +2 -2
- package/src/cli/setup-wizard.mjs +29 -9
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +132 -63
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +7 -2
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +70 -31
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +10 -6
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/prompt-overlays.mjs +73 -16
- package/src/prompts/advisor-design.md +18 -8
- package/src/prompts/advisor-round1.md +7 -1
- package/src/prompts/advisor-round2.md +13 -2
- package/src/prompts/advisor-round3.md +13 -2
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +13 -32
- package/src/provider/errors.mjs +26 -1
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-slots.mjs +7 -2
- package/src/session.mjs +59 -38
- package/src/token-ttl.mjs +2 -1
- package/src/tools/{system.mjs → bash.mjs} +6 -243
- package/src/tools/file.mjs +30 -10
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +15 -34
- package/src/tools/index.mjs +4 -2
- package/src/tools/ops.mjs +20 -7
- package/src/tools/question.md +1 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -1
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tui/agent-turn.mjs +32 -13
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +108 -37
- package/src/tui/cmd-eng.mjs +11 -27
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/index.mjs +38 -37
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +47 -10
- package/src/tui/pickers.mjs +28 -410
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +5 -19
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/subagent-blocks.mjs +69 -90
- package/src/tui/subagent-children.mjs +50 -64
- package/src/tui/subagent-freeze.mjs +40 -43
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +28 -82
- package/src/tui/tool-args.mjs +4 -2
- package/src/tui/tool-display.mjs +4 -3
- package/src/tui/tool-events.mjs +23 -16
- package/src/tui/tui-lifecycle.mjs +18 -5
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -13
- package/src/prompts/discipline.md +0 -84
- package/src/prompts/eng-coder.md +0 -19
- package/src/prompts/engineering-sub.md +0 -14
- package/src/prompts/engineering.md +0 -87
- package/src/prompts/explore.md +0 -12
- package/src/prompts/main.md +0 -34
- package/src/prompts/methodology-template.md +0 -38
- package/src/prompts/plan.md +0 -9
- package/src/prompts/system.md +0 -44
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
1
|
+
import { isAbsolute, join } from "node:path"
|
|
2
2
|
import { loadConfig, configPath } from "../config.mjs"
|
|
3
3
|
import { createMemory } from "../memory.mjs"
|
|
4
4
|
import { teamConfig, gitAuthor } from "./make-agent.mjs"
|
|
@@ -64,7 +64,7 @@ export async function distillCommand(args, exitSoon) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const opts = {
|
|
67
|
-
projectDir: config.memory.projectDir ? join(process.cwd(), config.memory.projectDir) : null,
|
|
67
|
+
projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
|
|
68
68
|
team,
|
|
69
69
|
author: gitAuthor(),
|
|
70
70
|
}
|
package/src/cli/make-agent.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process"
|
|
2
|
-
import { join } from "node:path"
|
|
2
|
+
import { isAbsolute, join } from "node:path"
|
|
3
3
|
import { createAgent } from "../agent.mjs"
|
|
4
4
|
import { loadConfig, configDir } from "../config.mjs"
|
|
5
5
|
import { createMemory, memoryTools, syncDir, codeSearchTool, docSearchTool } from "../memory.mjs"
|
|
@@ -10,8 +10,18 @@ import { discoverRules } from "../rules.mjs"
|
|
|
10
10
|
// R10 L2(MULTI-INSTANCE-COLLAB §2a.4 D-L2b):peer_instances 只读工具——挂感知模块导出
|
|
11
11
|
import { peerInstancesTool } from "../peer-instances.mjs"
|
|
12
12
|
|
|
13
|
+
/** 第 27 批 §12.3⑤(R-A1.1):装配期工具剔除——按 `name` 过滤的纯函数(机验锚)。
|
|
14
|
+
* 恒等语义:空列表 / 零命中 → 原数组原样返回(零意外剔除——T15)。
|
|
15
|
+
* ACP 通道经 `assembleAgent({ excludeTools })` 传入(acp.mjs `ACP_EXCLUDED_TOOLS`)。 */
|
|
16
|
+
export function applyToolExclusions(tools, excludeTools = []) {
|
|
17
|
+
const excluded = new Set(excludeTools ?? [])
|
|
18
|
+
if (excluded.size === 0) return tools
|
|
19
|
+
const kept = tools.filter((t) => !excluded.has(t?.name))
|
|
20
|
+
return kept.length === tools.length ? tools : kept
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
/** Assemble an agent with memory, MCP tools, and code/doc indices attached (sync all layers, then return) */
|
|
14
|
-
export async function assembleAgent() {
|
|
24
|
+
export async function assembleAgent({ excludeTools = [] } = {}) {
|
|
15
25
|
const config = loadConfig()
|
|
16
26
|
const provider = config.provider
|
|
17
27
|
const providers = config.providersList
|
|
@@ -20,7 +30,7 @@ export async function assembleAgent() {
|
|
|
20
30
|
const { injectProxy } = await import("../proxy.mjs")
|
|
21
31
|
injectProxy(providers, config)
|
|
22
32
|
// config.provider 是 loadConfig 里的独立拷贝,同步注入结果
|
|
23
|
-
provider.proxyUri = providers.find((p) => p.name ===
|
|
33
|
+
if (provider?.name) provider.proxyUri = providers.find((p) => p.name === provider.name)?.proxyUri
|
|
24
34
|
|
|
25
35
|
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
26
36
|
// Vector retrieval: enabled if embedding is configured (lazy vector generation, computed on first search)
|
|
@@ -41,7 +51,7 @@ export async function assembleAgent() {
|
|
|
41
51
|
memory.codeOrigin = cwd
|
|
42
52
|
// Project layer: sync .thincoder/memory/ dir to index on startup (sync if present, skip otherwise)
|
|
43
53
|
if (config.memory.projectDir) {
|
|
44
|
-
memory.projectOrigin = join(cwd, config.memory.projectDir)
|
|
54
|
+
memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir)
|
|
45
55
|
await syncDir(memory, { layer: "project", dir: memory.projectOrigin })
|
|
46
56
|
}
|
|
47
57
|
// Team layer (optional): auto-clone on first use; startup only indexes local dir, remote pull via explicit thincoder sync
|
|
@@ -101,18 +111,24 @@ export async function assembleAgent() {
|
|
|
101
111
|
|
|
102
112
|
const agent = createAgent({
|
|
103
113
|
provider,
|
|
104
|
-
tools: [...baseTools, ...mcpTools],
|
|
114
|
+
tools: applyToolExclusions([...baseTools, ...mcpTools], excludeTools),
|
|
105
115
|
config,
|
|
106
116
|
cwd,
|
|
107
117
|
memory,
|
|
108
118
|
})
|
|
109
119
|
agent.providers = providers
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
// MODEL-MERGE-SESSION:config 无 active*——会话运行时起点 = defaultModel 复合解析
|
|
121
|
+
// (provider 对象带解析后 model——API/spec 消费点零改);无效 → {} + 原因走 D-S1 标记
|
|
122
|
+
agent.activeProvider = provider.name ?? ""
|
|
123
|
+
agent.activeModel = provider.model ?? null
|
|
112
124
|
agent._mcpWarnings = mcpWarnings
|
|
113
125
|
// SESSION.md §8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
|
|
114
126
|
// 标记由调用侧消费(TUI 弹重选 / headless 报错)。空 provider 由 TUI 路径在 startTUI 前清空。
|
|
115
127
|
validateProvider(agent)
|
|
128
|
+
// defaultModel 无效/未设的具体原因覆盖通用判据文案(providers 存在时更有指导性)
|
|
129
|
+
if (agent._providerInvalid && config.providerInvalidReason) {
|
|
130
|
+
agent._providerInvalidReason = config.providerInvalidReason
|
|
131
|
+
}
|
|
116
132
|
return agent
|
|
117
133
|
}
|
|
118
134
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
1
|
+
import { isAbsolute, join } from "node:path"
|
|
2
2
|
import { loadConfig } from "../config.mjs"
|
|
3
3
|
import { teamConfig } from "./make-agent.mjs"
|
|
4
4
|
import { put, search, list } from "../memory/core.mjs"
|
|
@@ -67,7 +67,7 @@ export async function memoryCommand(memory, args, opts = {}) {
|
|
|
67
67
|
function cliDirs() {
|
|
68
68
|
const config = loadConfig()
|
|
69
69
|
const dirs = { project: null, team: null }
|
|
70
|
-
if (config.memory?.projectDir) dirs.project = join(process.cwd(), config.memory.projectDir)
|
|
70
|
+
if (config.memory?.projectDir) dirs.project = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
|
|
71
71
|
const team = teamConfig(config)
|
|
72
72
|
if (team) dirs.team = team.dir
|
|
73
73
|
return dirs
|
package/src/cli/setup-wizard.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline"
|
|
2
2
|
import { configPath, writeConfigAtomic, PROVIDER_PRESETS } from "../config.mjs"
|
|
3
|
+
import { probeChannelModels } from "../tui/model-catalog.mjs"
|
|
3
4
|
|
|
4
5
|
/** First-time setup (TTY chat / distill): ask a few questions to configure a provider, save to disk, return runtime provider. Cancel returns null. */
|
|
5
6
|
export async function setupWizard() {
|
|
@@ -28,7 +29,7 @@ export async function setupWizard() {
|
|
|
28
29
|
presets.forEach(([n, p], i) => console.error(` ${i + 1}. ${n.padEnd(10)} ${p.desc}`))
|
|
29
30
|
console.error(` ${presets.length + 1}. Custom endpoint`)
|
|
30
31
|
const choice = Number((await ask(`Pick [1-${presets.length + 1}]: `)).trim())
|
|
31
|
-
let name, baseURL, model
|
|
32
|
+
let name, baseURL, model, presetFields = null
|
|
32
33
|
if (choice === presets.length + 1) {
|
|
33
34
|
name = (await ask("Name (e.g. my-openai): ")).trim()
|
|
34
35
|
baseURL = (await ask("baseURL (e.g. https://api.openai.com/v1): ")).trim().replace(/\/+$/, "")
|
|
@@ -38,9 +39,11 @@ export async function setupWizard() {
|
|
|
38
39
|
return null
|
|
39
40
|
}
|
|
40
41
|
} else if (choice >= 1 && choice <= presets.length) {
|
|
42
|
+
// 预设全量字段随行(format/thinking/maxTokens…)——探针分派与落盘同源(M9 需要 format)
|
|
43
|
+
presetFields = presets[choice - 1][1]
|
|
41
44
|
name = presets[choice - 1][0]
|
|
42
|
-
baseURL =
|
|
43
|
-
model =
|
|
45
|
+
baseURL = presetFields.baseURL
|
|
46
|
+
model = presetFields.model
|
|
44
47
|
} else {
|
|
45
48
|
console.error("Invalid choice — cancelled")
|
|
46
49
|
return null
|
|
@@ -51,25 +54,42 @@ export async function setupWizard() {
|
|
|
51
54
|
return null
|
|
52
55
|
}
|
|
53
56
|
const embedKey = (await ask("Optional: embedding API key (SiliconFlow, for vector search; press Enter to skip): ")).trim()
|
|
57
|
+
// M9 配置阶段准入(加渠道 = 配置写入面):探一次 `/models`——探通/探不通都不阻断保存,
|
|
58
|
+
// 仅明示结果(失败文案 = M8 消息本体;无绕过指引)。
|
|
59
|
+
// 探针输入带预设扩展字段(format 决定 M1 分派——claude/gemini 预设必须走自己的拉取分支)。
|
|
60
|
+
const probe = await probeChannelModels(presetFields ? { ...presetFields, name, apiKey } : { name, baseURL, apiKey })
|
|
61
|
+
console.error(probe.ok
|
|
62
|
+
? `✓ ${name}: /models 可用(${probe.list.length} 个模型可候选)`
|
|
63
|
+
: `⚠ ${probe.message}`)
|
|
54
64
|
// D-F5b:磁盘新鲜读 → mutate → mtime 门控写(writeConfigAtomic 收口);冲突 = 放弃
|
|
55
65
|
// + 提示重试(首配场景另有实例同时写盘——极低概率;不自动合并——决策点① A)
|
|
66
|
+
// MODEL-SELECTION v2:渠道默认模型 = 单值 `model` + defaultModel 顶层复合;
|
|
67
|
+
// 预设扩展字段(format/thinking/maxTokens…)随行落盘——与 TUI wizard 近似同构(探针/聊天字段同源),
|
|
68
|
+
// 但 `thinking` 复制语义不同:本文件用 `!== undefined` → kimi/kimi-code 预设的 `thinking: null`
|
|
69
|
+
// **被落盘**;TUI 路径用真值过滤(model-picker.mjs / wizard.mjs)不落该键。
|
|
70
|
+
// `thinking: null`(NF1 = 显式 off)落盘差异已登记待口径统一(父侧)。
|
|
56
71
|
const r = writeConfigAtomic(configPath, (raw) => {
|
|
57
72
|
const providers = raw.providers?.length ? raw.providers : []
|
|
58
73
|
const existing = providers.find((p) => p.name === name)
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
const rec = { name, baseURL, model, apiKey }
|
|
75
|
+
for (const k of ["format", "thinking", "reasoningEffort", "maxTokens", "chatPath"]) {
|
|
76
|
+
if (presetFields?.[k] !== undefined) rec[k] = presetFields[k]
|
|
77
|
+
}
|
|
78
|
+
if (existing) Object.assign(existing, rec) // 渠道老字段(models 候选清单)由 config-migrate 在下次 load 统一清理
|
|
79
|
+
else providers.push(rec)
|
|
61
80
|
raw.providers = providers
|
|
62
|
-
raw.
|
|
63
|
-
delete raw.
|
|
81
|
+
raw.defaultModel = `${name}:${model}`
|
|
82
|
+
delete raw.activeProvider
|
|
83
|
+
delete raw.activeModel
|
|
64
84
|
if (embedKey) raw.embedding = { ...(raw.embedding ?? {}), apiKey: embedKey }
|
|
65
85
|
})
|
|
66
86
|
if (!r.ok) {
|
|
67
87
|
console.error("config changed on disk concurrently — retry")
|
|
68
88
|
return null
|
|
69
89
|
}
|
|
70
|
-
console.error(`Configured: ${name} / ${model} (saved to ${configPath})`)
|
|
90
|
+
console.error(`Configured: ${name} / ${model} (defaultModel = ${name}:${model} — saved to ${configPath})`)
|
|
71
91
|
console.error(embedKey ? "Vector search enabled\n" : "(No embedding key configured: memory search will use text-only FTS. Add embedding.apiKey to config.json to enable vector search later.)\n")
|
|
72
|
-
return { name, baseURL, model, apiKey }
|
|
92
|
+
return { name, baseURL, model, apiKey, defaultModel: `${name}:${model}` }
|
|
73
93
|
} finally {
|
|
74
94
|
rl.close()
|
|
75
95
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* completions.mjs — `thincoder completion <shell>` shell 补全脚本发射(bash/zsh/fish)。
|
|
3
|
+
* bin/thincoder.mjs case "completion" 直写段拆分(2026-09-08——500 行硬限触碰执行:
|
|
4
|
+
* 段逐字迁出,脚本字节与既有输出一致)。
|
|
5
|
+
*/
|
|
6
|
+
export function printCompletion(shell) {
|
|
7
|
+
if (shell === "bash") {
|
|
8
|
+
process.stdout.write(`_thincoder() {
|
|
9
|
+
local cur prev words cword
|
|
10
|
+
_init_completion 2>/dev/null || { COMPREPLY=(); return; }
|
|
11
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
12
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
13
|
+
case "\${COMP_WORDS[1]}" in
|
|
14
|
+
chat) COMPREPLY=( \\$(compgen -W "--auto" -- "\\$cur") ) ;;
|
|
15
|
+
memory)
|
|
16
|
+
case "\\$prev" in
|
|
17
|
+
memory) COMPREPLY=( \\$(compgen -W "list search put remove" -- "\\$cur") ) ;;
|
|
18
|
+
list) COMPREPLY=( \\$(compgen -W "--type=rule --type=knowledge --type=decision --type=pattern" -- "\\$cur") ) ;;
|
|
19
|
+
put) COMPREPLY=( \\$(compgen -W "--type= --title= --content= --tags=" -- "\\$cur") ) ;;
|
|
20
|
+
esac ;;
|
|
21
|
+
distill) COMPREPLY=( \\$(compgen -W "--yes --layer=" -- "\\$cur") ) ;;
|
|
22
|
+
completion) COMPREPLY=( \\$(compgen -W "bash zsh fish" -- "\\$cur") ) ;;
|
|
23
|
+
*)
|
|
24
|
+
COMPREPLY=( \\$(compgen -W "chat acp memory sync reindex distill upgrade completion session -v --version -h --help" -- "\\$cur") ) ;;
|
|
25
|
+
esac
|
|
26
|
+
}
|
|
27
|
+
complete -F _thincoder thincoder
|
|
28
|
+
`)
|
|
29
|
+
} else if (shell === "zsh") {
|
|
30
|
+
process.stdout.write(`#compdef thincoder
|
|
31
|
+
|
|
32
|
+
_thincoder() {
|
|
33
|
+
local context state state_descr line
|
|
34
|
+
typeset -A opt_args
|
|
35
|
+
_arguments -C \\
|
|
36
|
+
'1: :->cmd' \\
|
|
37
|
+
'*:: :->args'
|
|
38
|
+
|
|
39
|
+
case "\\$state" in
|
|
40
|
+
cmd)
|
|
41
|
+
_values 'command' \\
|
|
42
|
+
'chat[One-shot agent run with tools]' \\
|
|
43
|
+
'acp[Agent Client Protocol server for IDEs]' \\
|
|
44
|
+
'memory[Manage long-term memory]' \\
|
|
45
|
+
'sync[Sync team memory repo]' \\
|
|
46
|
+
'reindex[Rebuild local index from markdown]' \\
|
|
47
|
+
'distill[Extract knowledge from session transcript]' \\
|
|
48
|
+
'upgrade[Update to latest version from npm]' \\
|
|
49
|
+
'completion[Generate shell completion script]' \\
|
|
50
|
+
'session[Session dir GC: session gc --dry-run|--confirm]'
|
|
51
|
+
;;
|
|
52
|
+
args)
|
|
53
|
+
case "\\$words[1]" in
|
|
54
|
+
chat) _arguments '--auto[Auto-approve all tool calls]' ;;
|
|
55
|
+
memory)
|
|
56
|
+
case "\\$words[2]" in
|
|
57
|
+
list) _arguments '--type=[Filter by type]' ;;
|
|
58
|
+
put) _arguments '--type=[Entry type]' '--title=[Title]' '--content=[Content]' '--tags=[Space-separated tags]' ;;
|
|
59
|
+
esac ;;
|
|
60
|
+
distill) _arguments '--yes[Skip confirmation]' '--layer=[Layer filter]' ;;
|
|
61
|
+
completion) _values 'shell' 'bash' 'zsh' 'fish' ;;
|
|
62
|
+
esac ;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
_thincoder
|
|
66
|
+
`)
|
|
67
|
+
} else if (shell === "fish") {
|
|
68
|
+
process.stdout.write(`# thincoder completions for fish shell
|
|
69
|
+
complete -c thincoder -f
|
|
70
|
+
|
|
71
|
+
# Subcommands
|
|
72
|
+
complete -c thincoder -a chat -d 'One-shot agent run with tools'
|
|
73
|
+
complete -c thincoder -a memory -d 'Manage long-term memory'
|
|
74
|
+
complete -c thincoder -a sync -d 'Sync team memory repo'
|
|
75
|
+
complete -c thincoder -a reindex -d 'Rebuild local index from markdown'
|
|
76
|
+
complete -c thincoder -a distill -d 'Extract knowledge from session'
|
|
77
|
+
complete -c thincoder -a upgrade -d 'Update to latest version'
|
|
78
|
+
complete -c thincoder -a completion -d 'Shell completion'
|
|
79
|
+
complete -c thincoder -a session -d 'Session dir GC (session gc --dry-run|--confirm)'
|
|
80
|
+
complete -c thincoder -a acp -d 'Agent Client Protocol server for IDEs'
|
|
81
|
+
|
|
82
|
+
# Flags
|
|
83
|
+
complete -c thincoder -s v -l version -d 'Print version'
|
|
84
|
+
complete -c thincoder -s h -l help -d 'Print help'
|
|
85
|
+
|
|
86
|
+
# chat flags
|
|
87
|
+
complete -c thincoder -n '__fish_seen_subcommand_from chat' -l auto -d 'Auto-approve tool calls'
|
|
88
|
+
|
|
89
|
+
# memory subcommands
|
|
90
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a list -d 'List entries'
|
|
91
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a search -d 'Search memory'
|
|
92
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a put -d 'Add entry'
|
|
93
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a remove -d 'Remove entry'
|
|
94
|
+
|
|
95
|
+
# memory list flags
|
|
96
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from list' -l type -d 'Filter by type' -xa 'rule knowledge decision pattern'
|
|
97
|
+
|
|
98
|
+
# memory put flags
|
|
99
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l type -d 'Entry type'
|
|
100
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l title -d 'Title'
|
|
101
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l content -d 'Content'
|
|
102
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l tags -d 'Space-separated tags'
|
|
103
|
+
|
|
104
|
+
# distill flags
|
|
105
|
+
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l yes -d 'Skip confirmation'
|
|
106
|
+
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l layer -d 'Layer filter'
|
|
107
|
+
|
|
108
|
+
# completion shells
|
|
109
|
+
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a bash -d 'Bash completions'
|
|
110
|
+
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a zsh -d 'Zsh completions'
|
|
111
|
+
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a fish -d 'Fish completions'
|
|
112
|
+
`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-migrate.mjs — model-schema legacy-shape migration core (MODEL-SELECTION v2, §16.2 M7).
|
|
3
|
+
* Split out of config.mjs (500-line hard limit). Pure function, zero imports — the
|
|
4
|
+
* write-back orchestration (writeConfigAtomic / persistRaw, failure tolerance) stays in
|
|
5
|
+
* the caller (CLI: config.mjs loadConfig; VSC: config-io.mjs loadRaw — same rule, 双端各自
|
|
6
|
+
* 独立实现——不做同步依赖).
|
|
7
|
+
*
|
|
8
|
+
* 目标形态 C:每渠道恰好一个默认模型(`providers[].model` 单值)+ 顶层 `defaultModel` 复合。
|
|
9
|
+
* 老形态迁移(v2):
|
|
10
|
+
* - A:`providers[].model` + `activeProvider/activeModel` → `p.model` **保留**(不再搬入 models);
|
|
11
|
+
* `activeProvider/activeModel` → `defaultModel` 复合(`activeModel` 优先;AP 不存在/AM 空 → 首渠道回退)
|
|
12
|
+
* - B:`providers[].models[]` → `p.model = defaultModel 属本渠道的模型段 ?? 现有 p.model(非空字符串)
|
|
13
|
+
* ?? models[] 首个非空字符串`,然后 `delete p.models`
|
|
14
|
+
* - 混合/垃圾:非数组 `p.models` / 非字符串(空串)`p.model` → 删除(清理)
|
|
15
|
+
* - 顺序:先构造/读取有效 `defaultModel` 值(含老 active* 转换),再按它给各渠道播种 `p.model`
|
|
16
|
+
* - 幂等:无老字段(无 `models` / 无 active* / 无垃圾值)→ 返回 false 不动;slot 旧字段
|
|
17
|
+
* (会话文件内)不迁移——读侧容忍
|
|
18
|
+
* - 空结果合法:渠道无模型来源 → `p.model` 不设(模型选择经 `/models` 拉取候选——M8/M9)
|
|
19
|
+
*/
|
|
20
|
+
export function migrateLegacyModelFields(raw) {
|
|
21
|
+
if (!raw || typeof raw !== "object") return false
|
|
22
|
+
const providers = Array.isArray(raw.providers) ? raw.providers : []
|
|
23
|
+
let changed = false
|
|
24
|
+
|
|
25
|
+
// ── ① 有效 defaultModel 值(含老 active* 转换)──
|
|
26
|
+
if (raw.activeProvider !== undefined || raw.activeModel !== undefined) {
|
|
27
|
+
const rawAP = typeof raw.activeProvider === "string" && raw.activeProvider.trim() ? raw.activeProvider : ""
|
|
28
|
+
const rawAM = typeof raw.activeModel === "string" && raw.activeModel.trim() ? raw.activeModel : ""
|
|
29
|
+
const ap = rawAP ? providers.find((p) => p?.name === rawAP) : null
|
|
30
|
+
if (ap && rawAM) {
|
|
31
|
+
if (raw.defaultModel == null) { raw.defaultModel = `${rawAP}:${rawAM}`; changed = true }
|
|
32
|
+
} else {
|
|
33
|
+
// AP 已不存在 / AM 无法归属 → 首渠道回退(该渠道有模型来源才可用——迁移产物必可用)
|
|
34
|
+
const first = providers.find((p) => seedModel(p) != null)
|
|
35
|
+
if (first && raw.defaultModel == null) { raw.defaultModel = `${first.name}:${seedModel(first)}`; changed = true }
|
|
36
|
+
}
|
|
37
|
+
delete raw.activeProvider
|
|
38
|
+
delete raw.activeModel
|
|
39
|
+
changed = true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── ② 渠道播种单值 `p.model` + 清 models ──
|
|
43
|
+
const dm = typeof raw.defaultModel === "string" && raw.defaultModel.trim() ? raw.defaultModel : null
|
|
44
|
+
const dmSep = dm ? dm.indexOf(":") : -1
|
|
45
|
+
const dmProvider = dmSep > 0 ? dm.slice(0, dmSep) : null
|
|
46
|
+
const dmModel = dmSep > 0 && dmSep < dm.length - 1 ? dm.slice(dmSep + 1) : null
|
|
47
|
+
for (const p of providers) {
|
|
48
|
+
if (!p || typeof p !== "object") continue
|
|
49
|
+
if (Array.isArray(p.models)) {
|
|
50
|
+
const next = (p.name === dmProvider && dmModel)
|
|
51
|
+
? dmModel
|
|
52
|
+
: (typeof p.model === "string" && p.model.trim() ? p.model : seedModel(p))
|
|
53
|
+
if (next == null) delete p.model
|
|
54
|
+
else p.model = next
|
|
55
|
+
delete p.models
|
|
56
|
+
changed = true
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
if (p.models !== undefined) { delete p.models; changed = true } // 非数组 models —— 垃圾清理
|
|
60
|
+
if (p.model !== undefined && !(typeof p.model === "string" && p.model.trim())) { delete p.model; changed = true }
|
|
61
|
+
}
|
|
62
|
+
return changed
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 渠道级模型来源读数(迁移用):现有 `p.model`(非空字符串优先)或 `models[]` 首个非空字符串。 */
|
|
66
|
+
function seedModel(p) {
|
|
67
|
+
if (typeof p?.model === "string" && p.model.trim()) return p.model
|
|
68
|
+
const models = Array.isArray(p?.models) ? p.models : []
|
|
69
|
+
return models.find((m) => typeof m === "string" && m.trim()) ?? null
|
|
70
|
+
}
|