@luchenglong/cli-bridge 0.2.4 → 0.2.6
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 +73 -0
- package/docs/HANDOVER.md +201 -51
- package/dynamic/agy.host.js +1 -1
- package/dynamic/codebuddy.host.js +1332 -925
- package/dynamic/codebuddy.host.template.mjs +1 -0
- package/dynamic/mimo.host.js +314 -548
- package/dynamic/mimo.host.template.mjs +43 -30
- package/dynamic/opencode.host.js +32 -22
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +11 -14
- package/mcp/cli-bridge-mcp.mjs +1 -1
- package/mcp/providers/codebuddy.mjs +427 -398
- package/mcp/providers/mimo.mjs +37 -68
- package/mcp/providers/opencode.mjs +28 -1
- package/package.json +3 -1
- package/preset/codebuddy-first/cli-bridge.mjs +12 -0
- package/preset/mimo-first/cli-bridge.mjs +32 -50
- package/providers/codebuddy-catalog.mjs +605 -0
- package/providers/codebuddy-models-tool.mjs +106 -0
- package/providers/codebuddy.mjs +636 -926
- package/providers/mimo.mjs +271 -517
- package/providers/opencode.mjs +31 -21
- package/scripts/build.mjs +262 -235
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
// providers/codebuddy-catalog.mjs — codebuddy 模型目录/积分/描述文件解析(纯逻辑)。
|
|
2
|
+
//
|
|
3
|
+
// 从 providers/codebuddy.mjs 拆出:主文件原 864 行远超 600 行红线(规则 §26)。
|
|
4
|
+
// 只放「模型目录相关」纯逻辑:静态表、描述文件候选路径、模型 id 抽取、积分倍率、
|
|
5
|
+
// 账号清单收割、目录渲染。不含 CLI 调用、状态机、凭据链。
|
|
6
|
+
//
|
|
7
|
+
// 沙箱纪律(与 providers/*.mjs 同):本文件会被 scripts/build.mjs 经模板顶部的
|
|
8
|
+
// 第三个占位标记内联进同一个 dynamic 沙箱脚本作用域 —— 所以:
|
|
9
|
+
// 1) 不得 import 任何模块(import 行会被 stripModuleSyntax 剥掉,函数体留下但
|
|
10
|
+
// 依赖丢失 → 运行期 ReferenceError);node 内建走 cbCatalog* 守卫;
|
|
11
|
+
// 2) 顶层标识符不得与 bridge-kit / codebuddy.mjs 剩余部分撞名(同一作用域重复
|
|
12
|
+
// 声明 = 构建期 Identifier already declared);
|
|
13
|
+
// 3) 不得出现 import.meta 字面量;
|
|
14
|
+
// 4) ⚠️ 注释里绝不写出那个占位标记的字面量:build 做全局文本替换,注释中出现
|
|
15
|
+
// 同样会被替换成正文代码,把注释从中间劈开 → 生成物语法崩。
|
|
16
|
+
|
|
17
|
+
// ── 宿主能力注入(env / fs / path / sep)───────────────────────────────────
|
|
18
|
+
// 本模块函数搬自主文件的闭包,拿不到那边的 env / fsRead / pathJoin / pathSep。
|
|
19
|
+
// 由 codebuddy.mjs 在 defineProvider 里调 cbCatalogSetHost() 注入;未注入时退到
|
|
20
|
+
// getBuiltinModule 守卫(沙箱里没有 → 返回 null / 默认分隔符,绝不抛)。
|
|
21
|
+
// 名字一律带 cbCatalog_ 前缀:内联后与主文件的 fsRead/pathJoin 同处一个作用域。
|
|
22
|
+
let cbCatalogEnv = {}
|
|
23
|
+
let cbCatalogFs = null
|
|
24
|
+
let cbCatalogPathSep = "/"
|
|
25
|
+
|
|
26
|
+
export function cbCatalogSetHost(envObj, pathMod, fsMod) {
|
|
27
|
+
if (envObj && typeof envObj === "object") cbCatalogEnv = envObj
|
|
28
|
+
if (fsMod && typeof fsMod.readFileSync === "function") cbCatalogFs = fsMod
|
|
29
|
+
if (pathMod && typeof pathMod.sep === "string") cbCatalogPathSep = pathMod.sep
|
|
30
|
+
else if (/[A-Za-z]:[\\/]/.test(String((cbCatalogEnv && (cbCatalogEnv.USERPROFILE || cbCatalogEnv.APPDATA)) || ""))) cbCatalogPathSep = "\\"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function cbCatalogRead(p, enc) {
|
|
34
|
+
if (!p) return null
|
|
35
|
+
try {
|
|
36
|
+
if (cbCatalogFs && typeof cbCatalogFs.readFileSync === "function") return cbCatalogFs.readFileSync(p, enc || "utf8")
|
|
37
|
+
const proc = globalThis.process
|
|
38
|
+
const nf = (proc && typeof proc.getBuiltinModule === "function") ? proc.getBuiltinModule("node:fs") : null
|
|
39
|
+
if (!nf || typeof nf.readFileSync !== "function") return null
|
|
40
|
+
return nf.readFileSync(p, enc || "utf8")
|
|
41
|
+
} catch (e) { return null }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function cbCatalogPathJoin(...parts) {
|
|
45
|
+
try {
|
|
46
|
+
const proc = globalThis.process
|
|
47
|
+
const np = (proc && typeof proc.getBuiltinModule === "function") ? proc.getBuiltinModule("node:path") : null
|
|
48
|
+
if (np && typeof np.join === "function") return np.join(...parts)
|
|
49
|
+
} catch (e) { }
|
|
50
|
+
return parts.join("/")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── 以下常量与函数原样搬自 providers/codebuddy.mjs(行为 zero-change)────────
|
|
54
|
+
|
|
55
|
+
export const BACKEND_MODEL_IDS = {
|
|
56
|
+
'codebuddy': ['hy4-preview', 'hy3', 'hy3-x', 'deepseek-v4.1-flash', 'glm-5.3', 'glm-5.3-flash', 'glm-5.2', 'glm-5.1', 'glm-5v-turbo', 'minimax-m3', 'minimax-m2.7', 'kimi-k3-1', 'kimi-k2.8-preview', 'kimi-k2.7', 'kimi-k2.6', 'deepseek-v4-pro'],
|
|
57
|
+
'codebuddy-intl': ['claude-sonnet-5', 'claude-sonnet-5-1m', 'claude-opus-5', 'claude-opus-4.8', 'claude-opus-4.8-1m', 'gemini-3.1-pro', 'gemini-3.5-flash', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna', 'gpt-5.5', 'gpt-5.4', 'glm-5.3-ioa', 'glm-5.3-flash-ioa', 'glm-5.2-ioa', 'kimi-k3-ioa', 'kimi-k2.8-preview', 'kimi-k2.6-ioa', 'minimax-m3-ioa', 'minimax-m2.7-ioa', 'deepseek-v4.1-flash', 'deepseek-v4-pro-ioa'],
|
|
58
|
+
// WorkBuddyAI 自制目录(product.json 顶层 37 项,按其 credits 排序:免费的在前)
|
|
59
|
+
'codebuddy-en': ['hy3', 'gpt-5.1-codex-mini', 'gemini-3.1-flash-lite', 'gemini-2.5-flash', 'minimax-m3', 'gemini-3.0-flash', 'fast-model', 'deepseek-v3-2-volc', 'balanced-model', 'kimi-k2.5', 'kimi-k2.6', 'default-model-lite', 'glm-5.3', 'glm-5.2', 'glm-5.0', 'gpt-5.1-codex', 'gemini-2.5-pro', 'gemini-3.5-flash', 'gpt-5.3-codex', 'gemini-3.1-pro', 'gpt-5.6-terra', 'kimi-k3', 'gpt-5.4', 'primary-model', 'default-model', 'deep-model', 'gpt-5.5', 'gpt-5.6-sol', 'gpt-5.6-luna'],
|
|
60
|
+
// WorkBuddy 国内桌面自制目录(product.json:applicationName=WorkBuddy)
|
|
61
|
+
'workbuddy': ['auto', 'hy4-preview', 'hy3', 'hy3-x', 'deepseek-v4.1-flash', 'glm-5.3', 'glm-5.3-flash', 'glm-5.2', 'glm-5.1', 'glm-5v-turbo', 'minimax-m3', 'minimax-m2.7', 'kimi-k3-1', 'kimi-k2.8-preview', 'kimi-k2.7', 'kimi-k2.6', 'deepseek-v4-pro']
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 各后端真正生效的 product 描述文件位置(旧 core v1.6.1 verbatim)。
|
|
65
|
+
export const PRODUCT_DESCRIPTOR_ENV = {
|
|
66
|
+
'codebuddy': { env: 'CODEBUDDY_MODELS_FILE', rel: 'product.ioa.json', fallbackRel: 'product.json', accountDir: null },
|
|
67
|
+
'codebuddy-intl': { env: 'CODEBUDDY_INTL_MODELS_FILE', rel: 'product.ioa.json', fallbackRel: 'product.json', accountDir: null },
|
|
68
|
+
'codebuddy-en': { env: 'CODEBUDDY_EN_MODELS_FILE', rel: 'product.ioa.json', fallbackRel: 'product.json', accountDir: '.workbuddy-ai' },
|
|
69
|
+
'workbuddy': { env: 'WORKBUDDY_MODELS_FILE', rel: 'product.ioa.json', fallbackRel: 'product.json', accountDir: '.workbuddy' }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 倍率跨命名匹配(账号清单 id → 描述文件 id,旧 core verbatim)。
|
|
73
|
+
export const CREDIT_ID_ALIASES = {
|
|
74
|
+
'kimi-k3-1': ['kimi-k3', 'kimi-k3-ioa'],
|
|
75
|
+
'hy3-x': ['hy3', 'hy3-ioa'],
|
|
76
|
+
'default': ['codewise-default-model-v2'],
|
|
77
|
+
'auto': ['hy3', 'hy3-ioa']
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const MODEL_DESCRIPTION = 'Optional model id. When unspecified, the user-preferred default model (plugin settings) is used if set, else the CLI default. Two layers decide: the PRODUCT FACE decides whether the id exists in its catalogue, then the ACCOUNT decides whether it is licensed. The free tier (hy3, deepseek-v4.1-flash at x0.00 credits; hy4-preview at x0.29) belongs to the npm CLI faces codebuddy/codebuddy-intl: hy4-preview, hy3, hy3-x, deepseek-v4.1-flash, glm-5.3, glm-5.3-flash, glm-5.2, glm-5.1, glm-5v-turbo, minimax-m3, minimax-m2.7, kimi-k3-1, kimi-k2.8-preview, kimi-k2.7, kimi-k2.6, deepseek-v4-pro. codebuddy-en (WorkBuddyAI) carries NO hy4-preview and NO deepseek-v4.1-flash; its only free model is hy3. International ids (claude-sonnet-5, gemini-3.1-pro, ...) are account-gated. Availability is per ACCOUNT: the CLI rejects an id outside the account catalogue with "400 service info not found" and one inside it but unlicensed with "400 only available for authorized users" (many catalogue flagships such as claude-opus-4.8, gpt-5.4 and gpt-6-astra are in this second group for a domestic account). Run the CLI with a deliberately invalid model id to print "Currently supported models for your account:" and get the authoritative list; the plugin caches that result and the settings panel lists exactly those ids with their credit multiplier (x0.00 = free), read live from the CLI descriptor so it stays in sync with CLI upgrades. Prefer the cheapest model that can do the job, and prefer a free (x0.00) one for routine work. Pass a model only when the task clearly benefits from a specific one.'
|
|
81
|
+
|
|
82
|
+
export const BACKEND_ENUM = ['codebuddy', 'codebuddy-intl', 'codebuddy-ioa', 'codebuddy-international', 'codebuddy-en', 'workbuddy-en', 'workbuddy-ai', 'workbuddy']
|
|
83
|
+
|
|
84
|
+
export function accountConfigCandidates(backend, io) {
|
|
85
|
+
const e = (io && io.env) || cbCatalogEnv
|
|
86
|
+
const spec = PRODUCT_DESCRIPTOR_ENV[backend]
|
|
87
|
+
if (!spec || !spec.accountDir) return []
|
|
88
|
+
const home = e.USERPROFILE || e.HOME
|
|
89
|
+
if (!home) return []
|
|
90
|
+
const base = cbCatalogPathJoin(home, spec.accountDir.split('/').join(cbCatalogPathSep))
|
|
91
|
+
return [
|
|
92
|
+
cbCatalogPathJoin(base, 'cache', 'acc-product-config-v3.json'),
|
|
93
|
+
cbCatalogPathJoin(base, 'local_storage', 'entry_d43e96994f944cfb77961c2ea7d04605.info'),
|
|
94
|
+
cbCatalogPathJoin(base, 'local_storage', 'wb_entry_d43e96994f944cfb77961c2ea7d04605.info')
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function productDescriptorCandidates(backend, io) {
|
|
99
|
+
const e = (io && io.env) || cbCatalogEnv
|
|
100
|
+
const spec = PRODUCT_DESCRIPTOR_ENV[backend]
|
|
101
|
+
if (!spec) return []
|
|
102
|
+
const out = []
|
|
103
|
+
for (const p of accountConfigCandidates(backend, io)) out.push(p)
|
|
104
|
+
if (e[spec.env]) out.push(e[spec.env])
|
|
105
|
+
const rels = spec.fallbackRel ? [spec.rel, spec.fallbackRel] : [spec.rel]
|
|
106
|
+
const push = (root) => { if (root) for (const r of rels) out.push(cbCatalogPathJoin(root, ...r.split('/'))) }
|
|
107
|
+
if (backend === 'codebuddy' || backend === 'codebuddy-intl') {
|
|
108
|
+
push(e.APPDATA ? cbCatalogPathJoin(e.APPDATA, 'npm', 'node_modules', '@tencent-ai', 'codebuddy-code') : null)
|
|
109
|
+
} else if (backend === 'codebuddy-en') {
|
|
110
|
+
push('C:\\Program Files\\WorkBuddyAI\\resources\\app.asar.unpacked\\cli')
|
|
111
|
+
} else if (backend === 'workbuddy') {
|
|
112
|
+
push('C:\\Program Files\\WorkBuddy\\resources\\app.asar.unpacked\\cli')
|
|
113
|
+
}
|
|
114
|
+
return out
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function normalizeProductConfig(raw) {
|
|
118
|
+
try {
|
|
119
|
+
const obj = typeof raw === 'string' ? JSON.parse(raw) : raw
|
|
120
|
+
if (!obj) return null
|
|
121
|
+
if (Array.isArray(obj)) {
|
|
122
|
+
// 数组形态:从后往前找第一个带 data 的条目(末尾最新)。
|
|
123
|
+
for (let i = obj.length - 1; i >= 0; i--) {
|
|
124
|
+
const e = obj[i]
|
|
125
|
+
if (e && typeof e === 'object' && e.data && typeof e.data === 'object') return e.data
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
if (typeof obj !== 'object') return null
|
|
130
|
+
if (obj.data && typeof obj.data === 'object' && !Array.isArray(obj.models) && !Array.isArray(obj.agents)) return obj.data
|
|
131
|
+
return obj
|
|
132
|
+
} catch (e) { return null }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function extractModelIds(productJson) {
|
|
136
|
+
try {
|
|
137
|
+
const obj = normalizeProductConfig(productJson)
|
|
138
|
+
if (!obj || typeof obj !== 'object') return null
|
|
139
|
+
const pick = (arr) => {
|
|
140
|
+
if (!Array.isArray(arr) || !arr.length) return null
|
|
141
|
+
const ids = arr.map((m) => (typeof m === 'string' ? m : m && m.id)).filter((s) => typeof s === 'string' && s.trim())
|
|
142
|
+
return ids.length ? [...new Set(ids)] : null
|
|
143
|
+
}
|
|
144
|
+
const agents = Array.isArray(obj.agents) ? obj.agents : []
|
|
145
|
+
const cli = agents.find((a) => a && a.name === 'cli')
|
|
146
|
+
const fromCli = cli ? pick(cli.models) : null
|
|
147
|
+
const fromTop = pick(obj.models)
|
|
148
|
+
if (fromCli && fromTop) return fromTop.length > fromCli.length ? fromTop : fromCli
|
|
149
|
+
return fromCli || fromTop || null
|
|
150
|
+
} catch (e) { return null }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function parseCreditValue(raw) {
|
|
154
|
+
if (typeof raw !== 'string') return null
|
|
155
|
+
const s = raw.trim()
|
|
156
|
+
let m = s.match(/^[xX×]\s*([0-9]+(?:\.[0-9]+)?)/)
|
|
157
|
+
if (!m) m = s.match(/^([0-9]+(?:\.[0-9]+)?)\s*[xX×]/)
|
|
158
|
+
if (!m) return null
|
|
159
|
+
const n = Number(m[1])
|
|
160
|
+
return Number.isFinite(n) ? n : null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function formatCreditLabel(credits) {
|
|
164
|
+
if (typeof credits !== 'number' || !Number.isFinite(credits)) return ''
|
|
165
|
+
if (credits === 0) return '免费'
|
|
166
|
+
return `x${credits.toFixed(2)}`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function extractModelCredits(productJson) {
|
|
170
|
+
try {
|
|
171
|
+
const obj = normalizeProductConfig(productJson)
|
|
172
|
+
if (!obj || typeof obj !== 'object' || !Array.isArray(obj.models)) return null
|
|
173
|
+
const map = new Map()
|
|
174
|
+
for (const m of obj.models) {
|
|
175
|
+
if (!m || typeof m !== 'object') continue
|
|
176
|
+
const id = typeof m.id === 'string' ? m.id.trim() : ''
|
|
177
|
+
if (!id) continue
|
|
178
|
+
const n = parseCreditValue(m.credits)
|
|
179
|
+
if (n !== null) map.set(id, n)
|
|
180
|
+
}
|
|
181
|
+
return map.size ? map : null
|
|
182
|
+
} catch (e) { return null }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function lookupModelCredits(creditsMap, id) {
|
|
186
|
+
if (!creditsMap || typeof id !== 'string') return null
|
|
187
|
+
const norm = id.trim()
|
|
188
|
+
const cands = [norm]
|
|
189
|
+
if (norm.endsWith('-ioa')) cands.push(norm.slice(0, -4))
|
|
190
|
+
else cands.push(norm + '-ioa')
|
|
191
|
+
if (CREDIT_ID_ALIASES[norm]) cands.push(...CREDIT_ID_ALIASES[norm])
|
|
192
|
+
for (const c of cands) {
|
|
193
|
+
if (creditsMap.has(c)) {
|
|
194
|
+
const v = creditsMap.get(c)
|
|
195
|
+
if (typeof v === 'number') return v
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function backendModelCatalog(backend, io) {
|
|
202
|
+
const known = BACKEND_MODEL_IDS[backend] || []
|
|
203
|
+
const read = (io && io.readFileSync) || cbCatalogRead
|
|
204
|
+
let fromFile = null
|
|
205
|
+
let fromFileRich = null
|
|
206
|
+
// 旧 v1.6.1 语义:第一份有内容的 + 第一份带促销的,优先后者。
|
|
207
|
+
for (const p of productDescriptorCandidates(backend, io)) {
|
|
208
|
+
let txt = null
|
|
209
|
+
try { txt = read ? read(p, 'utf8') : null } catch (e) { continue }
|
|
210
|
+
if (txt == null) continue
|
|
211
|
+
const ids = extractModelIds(txt)
|
|
212
|
+
if (ids && ids.length) {
|
|
213
|
+
if (!fromFile) fromFile = ids
|
|
214
|
+
const obj = normalizeProductConfig(txt)
|
|
215
|
+
if (!fromFileRich && obj && Array.isArray(obj.modelPromotions)) fromFileRich = ids
|
|
216
|
+
}
|
|
217
|
+
if (fromFileRich) break
|
|
218
|
+
}
|
|
219
|
+
if (!fromFile) return known
|
|
220
|
+
return fromFileRich || fromFile
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 服务端回吐的账号清单缓存(进程内,旧 core verbatim)。
|
|
224
|
+
const ACCOUNT_CATALOG_CACHE = new Map()
|
|
225
|
+
export function rememberAccountCatalog(backend, ids) {
|
|
226
|
+
if (Array.isArray(ids) && ids.length) ACCOUNT_CATALOG_CACHE.set(backend, ids.slice())
|
|
227
|
+
}
|
|
228
|
+
export function getAccountCatalog(backend) {
|
|
229
|
+
const v = ACCOUNT_CATALOG_CACHE.get(backend)
|
|
230
|
+
return v && v.length ? v.slice() : null
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── 实时模型清单(CLI 是一手权威来源,对齐 workbuddy-auth 的「权威白名单」思路)──
|
|
234
|
+
//
|
|
235
|
+
// 为什么不再以静态表/磁盘描述文件为主:实测本机 codebuddy CLI --
|
|
236
|
+
// • 账号实时清单(用无效模型触发 400 报文)= 19 项
|
|
237
|
+
// • CLI `--help` 声明的 roster = 19 项,与账号清单完全一致
|
|
238
|
+
// • 我们原先读的 product.ioa.json 静态表 = 16 项,且 2 个 id 已过时
|
|
239
|
+
// (minimax-m3 → minimax-m3-pay、kimi-k3-1 → kimi-k3-2),另缺 4 个新模型。
|
|
240
|
+
// 静态表是「随包冻结」的,必然落后于 CLI,故只保留作倍率元数据与最后兜底。
|
|
241
|
+
//
|
|
242
|
+
// 两级实时来源(都不静默冒充权威数据,各带 source 标签):
|
|
243
|
+
// A. account-live —— 起 CLI 用无效模型探测「该账号当前支持」的清单(需该面已登录,
|
|
244
|
+
// 一次网络往返;对照 workbuddy-auth 的 agents[cli].models 白名单语义)。
|
|
245
|
+
// B. cli-help —— 解析 `--help` 的 "Currently supported: (...)"(零网络、随 CLI
|
|
246
|
+
// 版本自动更新;产品面级 roster)。
|
|
247
|
+
// 静态表仅作 credit 元数据与 descriptor-static 兜底标签,绝不冒充实时。
|
|
248
|
+
|
|
249
|
+
/** 解析 CLI `--help` 里声明的模型 roster。空/畸形输入返回 []。
|
|
250
|
+
* ⚠️ 名册很长时 yargs 会在任意列**折行**(实测把 `minimax-m3-pay` 从中间断成
|
|
251
|
+
* `minimax-` + `m3-pay`),若按行/按逗号直接切会丢项或产生残片(同一命令两次运行
|
|
252
|
+
* 给出 18/19 两种结果)。故先去掉括号内所有空白再按逗号切:逗号是唯一分隔符,
|
|
253
|
+
* 折行插入的空白一律视为噪声。 */
|
|
254
|
+
export function parseHelpModels(helpText) {
|
|
255
|
+
const m = /Currently supported:\s*\(([^)]+)\)/.exec(String(helpText || ''))
|
|
256
|
+
if (!m) return []
|
|
257
|
+
const out = []
|
|
258
|
+
for (const raw of m[1].replace(/\s+/g, '').split(',')) {
|
|
259
|
+
const id = raw.trim()
|
|
260
|
+
if (id && /^[A-Za-z0-9_.\-]+$/.test(id) && out.indexOf(id) < 0) out.push(id)
|
|
261
|
+
}
|
|
262
|
+
return out
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** 解析 codebuddy CLI 的可执行候选(env > npm 全局 shim > PATH 名)。 */
|
|
266
|
+
export function cbCatalogCliCandidates(env) {
|
|
267
|
+
const e = env || cbCatalogEnv || {}
|
|
268
|
+
const out = []
|
|
269
|
+
const push = (p) => { if (p && out.indexOf(p) < 0) out.push(p) }
|
|
270
|
+
if (e.CODEBUDDY_BIN) push(String(e.CODEBUDDY_BIN))
|
|
271
|
+
const joinP = (a, b) => cbCatalogPathJoin(a, b)
|
|
272
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy.cmd'))
|
|
273
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy'))
|
|
274
|
+
push('codebuddy')
|
|
275
|
+
return out
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const CLI_MODEL_CACHE = new Map() // backend → { at, mode, ids }
|
|
279
|
+
const CLI_MODEL_TTL_MS = 5 * 60 * 1000
|
|
280
|
+
|
|
281
|
+
// Windows .cmd/.bat 需要经 shell(Node 安全限制),但 `shell:true` + args 数组会触发
|
|
282
|
+
// DEP0190(参数不转义)。这里统一做法:把命令与参数拼成【一个】字符串交给 shell,
|
|
283
|
+
// 参数各自按 Windows 规则加引号 —— 既避开 DEP0190,又保证含空格参数不被拆开。
|
|
284
|
+
function quoteWinArg(a) {
|
|
285
|
+
const s = String(a)
|
|
286
|
+
if (s === '') return '""'
|
|
287
|
+
if (!/[\s"&|<>^]/.test(s)) return s
|
|
288
|
+
return '"' + s.replace(/"/g, '\\"') + '"'
|
|
289
|
+
}
|
|
290
|
+
function cliCommandLine(exe, args) {
|
|
291
|
+
return [exe].concat(args || []).map(quoteWinArg).join(' ')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** 把候选拆成 { cmd, prefixArgs }:.mjs/.js/.cjs 目标经 node 包装(测试夹具与自定义
|
|
295
|
+
* 入口都靠这条),其余原样。 */
|
|
296
|
+
function splitExeSpec(exe) {
|
|
297
|
+
if (/\.(mjs|cjs|js)$/i.test(String(exe))) {
|
|
298
|
+
const proc = globalThis.process
|
|
299
|
+
return { cmd: (proc && proc.execPath) ? proc.execPath : 'node', prefixArgs: [String(exe)] }
|
|
300
|
+
}
|
|
301
|
+
return { cmd: String(exe), prefixArgs: [] }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function cliSpawnSync(exe, args, opts) {
|
|
305
|
+
const proc = globalThis.process
|
|
306
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
307
|
+
if (!child || typeof child.spawnSync !== 'function') return null
|
|
308
|
+
const base = Object.assign({
|
|
309
|
+
encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe']
|
|
310
|
+
}, opts || {})
|
|
311
|
+
const spec = splitExeSpec(exe)
|
|
312
|
+
const argv = spec.prefixArgs.concat(args || [])
|
|
313
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
314
|
+
if (isCmd) base.shell = true
|
|
315
|
+
try {
|
|
316
|
+
// shell 形态不带 args 数组(否则 DEP0190);非 shell 形态正常传数组。
|
|
317
|
+
return isCmd
|
|
318
|
+
? child.spawnSync(cliCommandLine(spec.cmd, argv), base)
|
|
319
|
+
: child.spawnSync(spec.cmd, argv, base)
|
|
320
|
+
} catch (e) { return null }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 同步取「CLI 声明的 roster」(零网络,随 CLI 版本走)。
|
|
325
|
+
* @returns {{ok:boolean, ids:string[], mode:string, error?:string}}
|
|
326
|
+
*/
|
|
327
|
+
export function fetchHelpModelsSync(env, opts) {
|
|
328
|
+
const o = opts || {}
|
|
329
|
+
const cands = cbCatalogCliCandidates(env)
|
|
330
|
+
let lastErr = null
|
|
331
|
+
for (const exe of cands) {
|
|
332
|
+
const r = cliSpawnSync(exe, ['--help'], { timeout: Math.max(1000, Math.min(o.timeoutMs || 12000, 30000)) })
|
|
333
|
+
if (!r || r.error) { lastErr = 'spawn failed: ' + String((r && r.error && r.error.message) || 'unavailable'); continue }
|
|
334
|
+
const text = String(r.stdout || '') + String(r.stderr || '')
|
|
335
|
+
const ids = parseHelpModels(text)
|
|
336
|
+
if (ids.length) return { ok: true, ids, mode: 'cli-help', exe }
|
|
337
|
+
lastErr = 'no roster in --help output'
|
|
338
|
+
}
|
|
339
|
+
return { ok: false, ids: [], mode: 'cli-help', error: lastErr || 'codebuddy CLI not found (set CODEBUDDY_BIN)' }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* 异步探测「该账号当前支持」的清单:用无效模型触发 CLI 的 400 报文。
|
|
344
|
+
* 对齐 workbuddy-auth 的权威白名单语义(服务器告诉我们这个账号能用什么)。
|
|
345
|
+
* @returns {Promise<{ok:boolean, ids:string[], mode:string, error?:string}>}
|
|
346
|
+
*/
|
|
347
|
+
export function probeAccountModels(env, opts) {
|
|
348
|
+
const o = opts || {}
|
|
349
|
+
const cands = cbCatalogCliCandidates(env)
|
|
350
|
+
const proc = globalThis.process
|
|
351
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
352
|
+
if (!child || typeof child.spawn !== 'function') {
|
|
353
|
+
return Promise.resolve({ ok: false, ids: [], mode: 'account-live', error: 'no spawn capability in this host form' })
|
|
354
|
+
}
|
|
355
|
+
const extraEnv = o.envExtra || {}
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
let idx = 0
|
|
358
|
+
// 记录每个候选的真实结局:区分「压根没找到 CLI」与「CLI 跑了但没吐账号名录」
|
|
359
|
+
// (后者通常是该面未登录 / 端点拒了)—— 报错文案必须能指导下一步,不能一律
|
|
360
|
+
// 说 "CLI not found"(本轮就踩到:codebuddy-en 无 token 时被误报成找不到 CLI)。
|
|
361
|
+
const reasons = []
|
|
362
|
+
const attempt = () => {
|
|
363
|
+
if (idx >= cands.length) {
|
|
364
|
+
resolve({
|
|
365
|
+
ok: false, ids: [], mode: 'account-live',
|
|
366
|
+
error: reasons.length ? reasons.join('; ') : 'no CLI candidate to probe',
|
|
367
|
+
notFound: reasons.every((r) => /not found|ENOENT|spawn/i.test(r))
|
|
368
|
+
})
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
const exe = cands[idx++]
|
|
372
|
+
let c
|
|
373
|
+
try {
|
|
374
|
+
// .cmd/.bat 需经 shell(Windows 安全限制);.mjs/.js 经 node 包装。
|
|
375
|
+
// 与 cliSpawnSync 同一规则:shell 形态把命令拼成单字符串(避免 DEP0190)。
|
|
376
|
+
const spec = splitExeSpec(exe)
|
|
377
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
378
|
+
const probeArgs = spec.prefixArgs.concat(['-p', 'hi', '--model', '__clb_probe_invalid__', '--output-format', 'text'])
|
|
379
|
+
const opts = {
|
|
380
|
+
stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
381
|
+
env: Object.assign({}, proc.env, extraEnv)
|
|
382
|
+
}
|
|
383
|
+
c = isCmd
|
|
384
|
+
? child.spawn(cliCommandLine(spec.cmd, probeArgs), Object.assign({ shell: true }, opts))
|
|
385
|
+
: child.spawn(spec.cmd, probeArgs, opts)
|
|
386
|
+
} catch (e) { reasons.push(exe + ': spawn threw ' + String(e && e.message || e)); attempt(); return }
|
|
387
|
+
let out = ''
|
|
388
|
+
const timer = setTimeout(() => { try { c.kill() } catch (e) { } }, Math.max(5000, Math.min(o.timeoutMs || 30000, 90000)))
|
|
389
|
+
const onData = (d) => { out += d; if (out.length > 400000) out = out.slice(-200000) }
|
|
390
|
+
c.stdout.on('data', onData)
|
|
391
|
+
c.stderr.on('data', onData)
|
|
392
|
+
c.on('error', (e) => {
|
|
393
|
+
clearTimeout(timer)
|
|
394
|
+
reasons.push(exe + ': ' + String((e && e.code) || (e && e.message) || 'spawn error'))
|
|
395
|
+
attempt()
|
|
396
|
+
})
|
|
397
|
+
c.on('close', (code) => {
|
|
398
|
+
clearTimeout(timer)
|
|
399
|
+
const ids = parseAccountModels(out)
|
|
400
|
+
if (ids && ids.length) { resolve({ ok: true, ids, mode: 'account-live', exe }); return }
|
|
401
|
+
const head = String(out).replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
402
|
+
reasons.push(exe + ': exit ' + code + ', no account roster' + (head ? ' — ' + head : ''))
|
|
403
|
+
attempt()
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
attempt()
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** 缓存读写(面板每次渲染都会取,避免反复起进程)。 */
|
|
411
|
+
export function cachedCliModels(backend, load, nowMs) {
|
|
412
|
+
const at = Number(nowMs) || Date.now()
|
|
413
|
+
const hit = CLI_MODEL_CACHE.get(backend)
|
|
414
|
+
if (hit && at - hit.at < CLI_MODEL_TTL_MS) return hit
|
|
415
|
+
const v = typeof load === 'function' ? load() : load
|
|
416
|
+
if (v && v.ok && v.ids && v.ids.length) CLI_MODEL_CACHE.set(backend, { at, mode: v.mode, ids: v.ids.slice() })
|
|
417
|
+
return v
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** 读统一设置文件里 codebuddy 分区的 preferredBackend(meta 需要它才知道列哪个面)。 */
|
|
421
|
+
export function currentPreferredBackend() {
|
|
422
|
+
try {
|
|
423
|
+
const e = cbCatalogEnv || {}
|
|
424
|
+
const p = cbCatalogRead(e.CLI_BRIDGE_SETTINGS_FILE || cbCatalogPathJoin(
|
|
425
|
+
e.DSH_HOME || (e.APPDATA ? cbCatalogPathJoin(e.APPDATA, 'DSH Desktop', 'dsh-home') : ''), 'cli-bridge-settings.json'))
|
|
426
|
+
if (!p) return null
|
|
427
|
+
const all = JSON.parse(p)
|
|
428
|
+
const v = all && all.codebuddy && all.codebuddy.preferredBackend
|
|
429
|
+
return typeof v === 'string' && v ? v : null
|
|
430
|
+
} catch (err) { return null }
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// CLI 报错里解析「该账号当前支持的模型」(旧 core parseAccountModels verbatim)。
|
|
434
|
+
export function parseAccountModels(text) {
|
|
435
|
+
const s = String(text || '')
|
|
436
|
+
const anchor = s.indexOf('Currently supported models for your account')
|
|
437
|
+
if (anchor < 0) return null
|
|
438
|
+
const tail = s.slice(anchor)
|
|
439
|
+
const ids = []
|
|
440
|
+
for (const line of tail.split(/\r?\n/)) {
|
|
441
|
+
const m = /^\s*[-*]\s+(\S+)\s*$/.exec(line)
|
|
442
|
+
if (!m) {
|
|
443
|
+
if (ids.length) break
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
if (!ids.includes(m[1])) ids.push(m[1])
|
|
447
|
+
}
|
|
448
|
+
return ids.length ? ids : null
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 带倍率/授权的详细清单(无促销角标,旧 v1.6.1 的 0.1.0 子集;排序规则 verbatim:
|
|
452
|
+
// 已授权在前 → 有倍率升序 → 无倍率殿后)。
|
|
453
|
+
export function modelCatalogDetailed(backend, io) {
|
|
454
|
+
const o = io || {}
|
|
455
|
+
const fromFile = backendModelCatalog(backend, o)
|
|
456
|
+
const acct = (Array.isArray(o.accountModels) && o.accountModels.length) ? o.accountModels : getAccountCatalog(backend)
|
|
457
|
+
const ids = []
|
|
458
|
+
const authorized = new Set()
|
|
459
|
+
if (acct) for (const id of acct) { if (!ids.includes(id)) ids.push(id); authorized.add(id) }
|
|
460
|
+
for (const id of fromFile) if (!ids.includes(id)) ids.push(id)
|
|
461
|
+
const read = o.readFileSync || cbCatalogRead
|
|
462
|
+
let creditsMap = null
|
|
463
|
+
for (const p of productDescriptorCandidates(backend, io)) {
|
|
464
|
+
let txt = null
|
|
465
|
+
try { txt = read ? read(p, 'utf8') : null } catch (e) { continue }
|
|
466
|
+
if (txt == null) continue
|
|
467
|
+
const map = extractModelCredits(txt)
|
|
468
|
+
if (map) { creditsMap = map; break }
|
|
469
|
+
}
|
|
470
|
+
const items = ids.map((id) => {
|
|
471
|
+
const base = lookupModelCredits(creditsMap, id)
|
|
472
|
+
const credits = base
|
|
473
|
+
const hasCredits = typeof credits === 'number'
|
|
474
|
+
const suffix = formatCreditLabel(credits)
|
|
475
|
+
const marks = []
|
|
476
|
+
if (suffix) marks.push(suffix)
|
|
477
|
+
if (acct && !authorized.has(id)) marks.push('未授权')
|
|
478
|
+
return {
|
|
479
|
+
id,
|
|
480
|
+
credits,
|
|
481
|
+
baseCredits: base,
|
|
482
|
+
free: hasCredits && credits === 0,
|
|
483
|
+
hasCredits,
|
|
484
|
+
authorized: acct ? authorized.has(id) : null,
|
|
485
|
+
promotion: null,
|
|
486
|
+
label: marks.length ? `${id} · ${marks.join(' · ')}` : id
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
const rank = (x) => (x.authorized === false ? 1 : 0)
|
|
490
|
+
const withC = items.filter((x) => x.hasCredits).sort((a, b) => rank(a) - rank(b) || a.credits - b.credits)
|
|
491
|
+
const withoutC = items.filter((x) => !x.hasCredits).sort((a, b) => rank(a) - rank(b))
|
|
492
|
+
return [...withC, ...withoutC]
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function formatModelCatalogText(backend, io) {
|
|
496
|
+
return modelCatalogDetailed(backend, io).map((x) => x.label).join(', ')
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
export const BACKEND_AUTH_IDS = {
|
|
501
|
+
'codebuddy': 'Tencent-Cloud.coding-copilot',
|
|
502
|
+
'codebuddy-intl': 'Tencent-Cloud.coding-copilot',
|
|
503
|
+
'codebuddy-en': 'workbuddy-desktop-ai',
|
|
504
|
+
'workbuddy': 'workbuddy-desktop'
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export const CREDENTIAL_KEYS = ['WORKBUDDY_TOKEN', 'WORKBUDDY_AI_TOKEN', 'CODEBUDDY_EN_TOKEN']
|
|
508
|
+
|
|
509
|
+
// ── 凭据读取(搬自 codebuddy.mjs;fs/path 走 cbCatalog* 守卫,env 经 io 或注入)
|
|
510
|
+
export function readBackendAuthDomain(backend, io) {
|
|
511
|
+
const o = io || {}
|
|
512
|
+
const id = BACKEND_AUTH_IDS[backend]
|
|
513
|
+
if (!id) return null
|
|
514
|
+
try {
|
|
515
|
+
const read = o.readFileSync || cbCatalogRead
|
|
516
|
+
if (typeof read !== 'function') return null
|
|
517
|
+
const e = o.env || env
|
|
518
|
+
let dir = o.dir
|
|
519
|
+
if (!dir) {
|
|
520
|
+
dir = e.CODEBUDDY_AUTH_DIR ||
|
|
521
|
+
cbCatalogPathJoin(e.LOCALAPPDATA || 'C:\\Users\\lcl\\AppData\\Local', 'CodeBuddyExtension', 'Data', 'Public', 'auth')
|
|
522
|
+
}
|
|
523
|
+
const sep = String(dir).indexOf('\\') >= 0 ? '\\' : '/'
|
|
524
|
+
const txt = read(dir + sep + id + '.info', 'utf8')
|
|
525
|
+
if (txt == null) return null
|
|
526
|
+
const raw = JSON.parse(txt)
|
|
527
|
+
const d = raw && raw.auth && raw.auth.domain
|
|
528
|
+
return typeof d === 'string' && d.trim() ? d.trim() : null
|
|
529
|
+
} catch (e) { return null }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// DSH 凭据库第三通道(旧 core readDshWorkbuddyToken verbatim 语义;CODEBUDDY_
|
|
533
|
+
// CREDENTIALS_DIR 覆盖测试夹具;fs 经 deps 注入)。
|
|
534
|
+
export function readDshWorkbuddyToken(io) {
|
|
535
|
+
const e = (io && io.env) || cbCatalogEnv
|
|
536
|
+
const read = (io && io.readFileSync) || cbCatalogRead
|
|
537
|
+
const overrideDir = e.CODEBUDDY_CREDENTIALS_DIR || null
|
|
538
|
+
const home = overrideDir || e.DSH_HOME || null
|
|
539
|
+
if (!home) return null
|
|
540
|
+
const files = [home + '\\.credentials.yaml', home + '\\.env', home + '/.credentials.yaml', home + '/.env']
|
|
541
|
+
for (const f of files) {
|
|
542
|
+
const text = read(f)
|
|
543
|
+
if (!text) continue
|
|
544
|
+
for (const key of CREDENTIAL_KEYS) {
|
|
545
|
+
const m = text.match(new RegExp('^[ \\t]*' + key + '[ \\t]*[:=][ \\t]*(.+)$', 'm'))
|
|
546
|
+
if (!m) continue
|
|
547
|
+
const v = m[1].trim().replace(/\s+#.*$/, '').replace(/^['"]|['"]$/g, '').trim()
|
|
548
|
+
// 只接受看起来是真实 token 的值(避免把 $VAR 引用或占位符当凭据)。
|
|
549
|
+
if (v.length >= 40 && !v.startsWith('$') && !/^<.*>$/.test(v)) return v
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return null
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// codebuddy-en 凭据链(旧 core verbatim):设置面板 > CODEBUDDY_AUTH_TOKEN >
|
|
556
|
+
// DSH 凭据库。dynamic 沙箱无 fs → 第三通道自然缺席(旧 dynamic 本也有限)。
|
|
557
|
+
export function resolveEnToken(settingToken, io) {
|
|
558
|
+
const e = (io && io.env) || cbCatalogEnv
|
|
559
|
+
const s = String(settingToken || '').trim()
|
|
560
|
+
if (s) return { token: s, source: 'setting' }
|
|
561
|
+
const envTok = e.CODEBUDDY_AUTH_TOKEN || ''
|
|
562
|
+
if (String(envTok).trim()) return { token: String(envTok).trim(), source: 'env' }
|
|
563
|
+
const fromStore = readDshWorkbuddyToken(io)
|
|
564
|
+
if (fromStore) return { token: fromStore, source: 'dsh-store' }
|
|
565
|
+
return { token: null, source: null }
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ── 三工具 schema(旧 preset/codebuddy-first verbatim;契约 §8 红线)────────────
|
|
569
|
+
// 纯数据,无依赖 —— 放这里是为了让主描述符留在 600 行红线内(规则 §26)。
|
|
570
|
+
export const RUN_PARAMS = {
|
|
571
|
+
type: 'object',
|
|
572
|
+
additionalProperties: false,
|
|
573
|
+
required: ['prompt'],
|
|
574
|
+
properties: {
|
|
575
|
+
prompt: { type: 'string', description: 'The full task/instruction for codebuddy. Be complete and self-contained.' },
|
|
576
|
+
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to dispatch to. codebuddy (default; domestic CodeBuddy npm CLI, product endpoint www.codebuddy.ai) for coding work; codebuddy-intl = CodeBuddy INTERNATIONAL face of the same npm CLI (product.ioa.json catalogue: claude-sonnet-5 / claude-opus-5 / gemini-3.1-pro / gpt-6-astra / hy3-ioa ...; aliases codebuddy-ioa / codebuddy-international) when the account is an international CodeBuddy account; codebuddy-en = WorkBuddy international edition (the WorkBuddyAI desktop bundled CLI at C:\\Program Files\\WorkBuddyAI, product endpoint www.workbuddy.ai; needs codebuddyEnToken unless DSH already holds a workbuddy key) — aliases workbuddy-en / workbuddy-ai also accepted; workbuddy = WorkBuddy domestic desktop CLI (product endpoint copilot.tencent.com, zero config), the office-scenario face: documents/slides/spreadsheets, knowledge-base lookups, image/video generation, WeChat/WeCom replies. New calls without a backend follow the user-preferred default backend (plugin settings). Continuing a session routes back to its owning backend automatically.' },
|
|
577
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'auto follows DSH plan state; plan = no writes; accept-edits = allow edits. Default auto.' },
|
|
578
|
+
model: { type: 'string', description: MODEL_DESCRIPTION },
|
|
579
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
580
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns (1-500, default unlimited).' },
|
|
581
|
+
cwd: { type: 'string', description: 'Working directory for codebuddy. Defaults to the DSH workspace root.' },
|
|
582
|
+
addDirs: { type: 'array', items: { type: 'string' }, description: 'Extra directories to add to codebuddy workspace.' },
|
|
583
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
584
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
export const CONTINUE_PARAMS = {
|
|
588
|
+
type: 'object',
|
|
589
|
+
additionalProperties: false,
|
|
590
|
+
required: ['prompt'],
|
|
591
|
+
properties: {
|
|
592
|
+
prompt: { type: 'string', description: 'Follow-up instruction for the ongoing codebuddy conversation.' },
|
|
593
|
+
sessionId: { type: 'string', description: 'codebuddy session id to resume (from a prior codebuddy_run result).' },
|
|
594
|
+
latest: { type: 'boolean', description: 'Continue the most recent codebuddy conversation instead of a specific id.' },
|
|
595
|
+
backend: { type: 'string', enum: BACKEND_ENUM, description: 'Which CLI face to resume on. codebuddy-en = WorkBuddy international (WorkBuddyAI desktop CLI; aliases workbuddy-en / workbuddy-ai accepted), workbuddy = WorkBuddy domestic desktop CLI. When omitted, the backend that owns the sessionId is used automatically; brand-new conversations follow the user-preferred default backend (plugin settings).' },
|
|
596
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'Execution mode; default auto.' },
|
|
597
|
+
model: { type: 'string', description: 'Optional model id (two layers: product face carries it, account licenses it; the credit multiplier of each id comes from the CLI descriptor — see codebuddy_run).' },
|
|
598
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
599
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns.' },
|
|
600
|
+
cwd: { type: 'string', description: "Working directory for codebuddy; when resuming, defaults to the resumed session's project directory." },
|
|
601
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
602
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
export const STATUS_PARAMS = { type: 'object', additionalProperties: false, required: [], properties: { cwd: { type: 'string', description: 'Optional: filter the snapshot to a single project (working directory).' } } }
|