@luchenglong/cli-bridge 0.2.5 → 0.2.7
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 +58 -0
- package/dynamic/codebuddy.host.js +466 -143
- package/dynamic/mimo.host.js +514 -616
- package/dynamic/opencode.host.js +584 -628
- package/mcp/cli-bridge-mcp.mjs +2 -1
- package/package.json +3 -1
- package/providers/codebuddy-catalog.mjs +232 -65
- package/providers/codebuddy-consts.mjs +72 -0
- package/providers/codebuddy-models-tool.mjs +106 -0
- package/providers/codebuddy-schemas.mjs +46 -0
- package/providers/codebuddy.mjs +61 -124
- package/providers/mimo-pure.mjs +111 -0
- package/providers/mimo.mjs +517 -618
- package/providers/opencode-exe.mjs +63 -0
- package/providers/opencode.mjs +587 -629
- package/scripts/build.mjs +262 -253
|
@@ -568,88 +568,19 @@ function createRunner(o) {
|
|
|
568
568
|
return { coreExecute, runSync, askFallback }
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
// providers/codebuddy.mjs — codebuddy provider descriptor
|
|
571
|
+
// providers/codebuddy.mjs — codebuddy provider descriptor。
|
|
572
572
|
//
|
|
573
|
-
// 移植基线:codebuddy-first-bridge v1.6.x
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
573
|
+
// 移植基线:codebuddy-first-bridge v1.6.x 的纯逻辑与 preset/codebuddy-first 内联语义,
|
|
574
|
+
// 逐字保持(契约 §8:工具入参语义与 schema 不许漂移)。provider 无关骨架在 bridge-kit。
|
|
575
|
+
// 纯逻辑片:codebuddy-catalog.mjs(模型目录/凭据/探测)、codebuddy-schemas.mjs(三 schema)、
|
|
576
|
+
// codebuddy-models-tool.mjs(codebuddy_models 工具)。build 会把 codebuddy-*.mjs 全部
|
|
577
|
+
// 内联进沙箱同一作用域。
|
|
578
578
|
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
// 单列,运行面(buildArgv/CLI 选择)不受影响。
|
|
583
|
-
//
|
|
584
|
-
// 红线(PROVIDER-CONTRACT.md §8):
|
|
585
|
-
// - 本文件会被 build 内联进 dynamic 沙箱(new Function):绝不出现元环境字面量;
|
|
586
|
-
// process/env/fs 只经 deps 注入或 typeof 守卫读取;宿主能力全部走 attach(hostOpts)。
|
|
587
|
-
// - 只允许 import '../core/bridge-kit.mjs' 一行。
|
|
588
|
-
// - isLimited 用本文件自定义的中文限流表(旧 core verbatim),不用 kit 通用表。
|
|
579
|
+
// 红线:本文件会被内联进 dynamic 沙箱 —— 绝不出现元环境字面量;process/env/fs 只经
|
|
580
|
+
// deps 注入或 typeof 守卫读取;宿主能力全部走 attach(hostOpts)。isLimited 用本文件
|
|
581
|
+
// 自定义的中文限流表(旧 core verbatim),不用 kit 通用表。
|
|
589
582
|
// ── 提示词策略段(旧 preset POLICY_TEXT verbatim;systemPrompt order 5)────────
|
|
590
|
-
const POLICY_TEXT = [
|
|
591
|
-
'codebuddy-first execution policy (local codebuddy CLI bridge, with fallback + live indicator + live status tool).',
|
|
592
|
-
'',
|
|
593
|
-
'A local sub-agent CLI named "codebuddy" (Tencent CodeBuddy Code) is available through the codebuddy_run and codebuddy_continue tools. DSH fully controls codebuddy: every invocation runs non-interactively with permissions auto-approved (--permission-mode bypassPermissions), so codebuddy never prompts and DSH decides all of its work. A header status light shows when codebuddy is working — one light per project (working directory), so parallel runs in different projects are tracked separately; call codebuddy_status any time to see what codebuddy is doing right now (per project: current tool/step, recent trail, and cumulative usage).',
|
|
594
|
-
'',
|
|
595
|
-
'Priority rule for EVERY mode (normal, plan, accept-edits, subagent/workflow/ralph delegation, and goal rounds):',
|
|
596
|
-
'- For any real work — implementing features, editing/refactoring code, debugging, running builds, or multi-file investigation — prefer dispatching the task to codebuddy_run FIRST with a complete, self-contained prompt.',
|
|
597
|
-
'- Use native read/write/shell tools mainly for quick read-only lookups and for the final build/test verification of what codebuddy produced.',
|
|
598
|
-
'- In plan mode, call codebuddy_run with mode=auto (or mode=plan); in accept-edits/normal mode codebuddy_run applies edits directly.',
|
|
599
|
-
'- When you delegate to a DSH subagent or workflow, instruct that delegate to also prefer codebuddy_run.',
|
|
600
|
-
'- For long-running tasks, call codebuddy_run with background=true and collect the result with job_output; use codebuddy_status to watch progress.',
|
|
601
|
-
'',
|
|
602
|
-
'Fallback protocol: when codebuddy is rate-limited or the network is down, codebuddy_run/codebuddy_continue automatically pop a confirmation dialog asking the user whether to use the DSH local API config. If the returned result has fallback=true (status FALLBACK_TO_DSH), the user chose to fall back: complete the task with native DSH tools / the local model and DO NOT call codebuddy again for this task. If ok=false without fallback, report the codebuddy error. Never loop codebuddy calls; never ask codebuddy to call back into DSH.',
|
|
603
|
-
'',
|
|
604
|
-
'Model selection: codebuddy_run takes an optional model. When unspecified, the CLI default applies unless the user set a preferred default model in the plugin settings (then that is injected automatically per call). Which models ACTUALLY WORK depends on the ACCOUNT, and the authoritative live list comes from the server: running the CLI with a nonexistent model returns 400 followed by "Currently supported models for your account:" and one "- <id>" per line (core exposes parseAccountModels for this; the plugin caches the result automatically and the settings panel shows exactly those ids, with the credit multiplier of each). Do NOT trust the bundled product descriptor as a menu of usable models: it lists ids with an "-ioa" suffix (glm-5.3-ioa) and expensive flagships (claude-opus-4.8, gpt-5.4, gpt-6-astra) that are present in the catalogue but NOT licensed for this account, and picking one fails with 400 "only available for authorized users". The four backends are also not one pool: "codebuddy" and "codebuddy-intl" are the npm CLI and offer its catalogue - including the free tier (hy3 and deepseek-v4.1-flash cost x0.00 credits; hy4-preview costs x0.29 but is frequently free on promotional accounts); "codebuddy-en" (WorkBuddyAI) has its OWN catalogue which does NOT contain hy4-preview or deepseek-v4.1-flash - it offers hy3 (free), gpt-5.1-codex-mini, gemini-3.1-flash-lite, gemini-2.5-flash, minimax-m3, gemini-3.0-flash, deepseek-v3-2-volc, kimi-k2.5/2.6, glm-5.0/5.2/5.3, gpt-5.3-codex, gpt-5.4/5.5, gpt-5.6-sol/terra/luna and the role aliases (fast/balanced/primary/deep-model); "workbuddy" (domestic desktop) has a further catalogue (auto, hy4-preview, hy3, deepseek-v4.1-flash, glm-5.x, kimi-k2.6/2.7/2.8-preview/k3-1, minimax-m3/m2.7, deepseek-v4-pro). Within a face availability is still gated per ACCOUNT: an id outside the account catalogue fails with "400 model [...] service info not found", and one inside it but unlicensed fails with "400 model [...] is only available for authorized users". International ids (claude-sonnet-5, claude-opus-5, gemini-3.1-pro, ...) are per-account gated and return the latter for a domestic account. COST: every id carries a credit multiplier in the credits field of its descriptor (x0.00 means free, x3.31 means 3.31x the base rate); the settings panel shows each candidate with its multiplier and reads it live from the CLI descriptor, so it stays in sync when the CLI is upgraded. Measured free (x0.00) models for this account: hy3 and deepseek-v4.1-flash (both npm faces) and hy3 for WorkBuddyAI; hy4-preview is x0.29 on npm faces. Prefer the cheapest model that can do the job and use a free one for routine work; do not silently upgrade to an expensive id when the task is simple. To get the exact live list for a CLI, run it with a deliberately invalid model id - the reply prints "Currently supported models for your account:" followed by one "- <id>" per line (core exposes parseAccountModels for this). Pass a model only when the task clearly benefits from a specific one; the default is usually right. Optional effort: minimal/low/medium/high/xhigh/max. Optional maxTurns caps agentic turns (default unlimited).',
|
|
605
|
-
'',
|
|
606
|
-
'Backends: codebuddy_run/codebuddy_continue take an optional backend parameter choosing which CLI face of the same engine (Tencent CodeBuddy Code) runs the task. "codebuddy" is the domestic CodeBuddy (npm CLI @tencent-ai/codebuddy-code, product endpoint www.codebuddy.ai) — default for coding work. "codebuddy-intl" is the INTERNATIONAL face of that SAME npm CLI (product.ioa.json catalogue: claude-sonnet-5, claude-opus-5, gemini-3.1-pro, gpt-6-astra, hy3-ioa …); use it when the account is an international CodeBuddy account. Its aliases "codebuddy-ioa" and "codebuddy-international" are accepted and normalized to codebuddy-intl. "codebuddy-en" is the WorkBuddy INTERNATIONAL edition — the CLI bundled with the WorkBuddyAI desktop app (C:\\Program Files\\WorkBuddyAI, product endpoint www.workbuddy.ai); the aliases "workbuddy-en" and "workbuddy-ai" are also accepted and normalized to codebuddy-en. "workbuddy" is the CLI bundled with the domestic WorkBuddy desktop app (product endpoint copilot.tencent.com, zero config) — the office-scenario face: documents, slides, spreadsheets, knowledge-base lookups, image/video generation, WeChat/WeCom replies. When the user asks for office/document/IM work, dispatch with backend="workbuddy"; for international accounts use "codebuddy-intl" (CodeBuddy 国际版) or "codebuddy-en" (WorkBuddy 国际版 / WorkBuddyAI). Sessions are kept per backend (login domains are exclusive), and continuing a session automatically routes back to the backend that owns it (explicit backend wins). A user-preferred default backend (plugin settings) applies when a call is new (no session) and no explicit backend is given.'
|
|
607
|
-
].join('\n')
|
|
608
|
-
|
|
609
|
-
// ── 限流/网络/认证失败判定(旧 core verbatim)。仅匹配 stderr + status;数字码
|
|
610
|
-
// 加词边界(避免 "1500" 命中 500、"4013" 命中 401)。中文码(流量/受限/金额/
|
|
611
|
-
// 余额/额度/认证/连接/断开 等)为 codebuddy 面专属,必须逐字保留。
|
|
612
|
-
// 命名纪律(mimo 同款):本文件顶层名绝不与 core/bridge-kit.mjs 顶层导出同名 ——
|
|
613
|
-
// dynamic 形态把 kit 与 provider 内联进同一脚本作用域,同名 const 直接炸语法
|
|
614
583
|
// (LIMIT_RE 在 kit L33 已有,故此处带 CODEBUDDY_ 前缀)。
|
|
615
|
-
const CODEBUDDY_LIMIT_RE = /rate.?limit|ratelimit|\b429\b|too many|quota|insufficient|credit|balance|exhausted|exceed|\bnetwork\b|offline|ENETUNREACH|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|\btimeouts?\b|timed out|unavailable|\b50[023]\b|\b40[13]\b|unauthorized|invalid api|api key|proxy|socket|tls|ssl|\bdns\b|网络|超时|限流|流量|受限|配额|金额|余额|额度|认证|连接|断开/i
|
|
616
|
-
|
|
617
|
-
// ── 后端注册表(旧 core verbatim:端点按登录域自动对齐)───────────────────────
|
|
618
|
-
const BACKENDS = ['codebuddy', 'codebuddy-intl', 'codebuddy-en', 'workbuddy']
|
|
619
|
-
|
|
620
|
-
const BACKEND_ENDPOINTS = {
|
|
621
|
-
'codebuddy': 'https://www.codebuddy.ai/v2',
|
|
622
|
-
'codebuddy-intl': 'https://www.codebuddy.ai/v2',
|
|
623
|
-
'codebuddy-en': 'https://www.workbuddy.ai/v2',
|
|
624
|
-
'workbuddy': 'https://copilot.tencent.com/v2'
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
const AUTH_DOMAIN_ENDPOINTS = {
|
|
628
|
-
'www.codebuddy.cn': 'https://www.codebuddy.cn/v2',
|
|
629
|
-
'www.codebuddy.ai': 'https://www.codebuddy.ai/v2',
|
|
630
|
-
'www.workbuddy.ai': 'https://www.workbuddy.ai/v2',
|
|
631
|
-
'copilot.tencent.com': 'https://copilot.tencent.com/v2'
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const BACKEND_LABELS = {
|
|
635
|
-
'codebuddy': 'CodeBuddy 国内版(npm CLI)',
|
|
636
|
-
'codebuddy-intl': 'CodeBuddy 国际版(npm CLI · 国际面)',
|
|
637
|
-
'codebuddy-en': 'WorkBuddy 国际版(WorkBuddyAI 桌面 CLI)',
|
|
638
|
-
'workbuddy': 'WorkBuddy 国内版(桌面 CLI)'
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
const BACKEND_ALIASES = {
|
|
642
|
-
'workbuddy-en': 'codebuddy-en',
|
|
643
|
-
'workbuddy-ai': 'codebuddy-en',
|
|
644
|
-
'codebuddy-ioa': 'codebuddy-intl',
|
|
645
|
-
'codebuddy-international': 'codebuddy-intl',
|
|
646
|
-
'codebuddy-oversea': 'codebuddy-intl'
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const DEFAULT_BACKEND = 'codebuddy'
|
|
650
|
-
|
|
651
|
-
const SETTINGS_KEYS = ['preferredBackend', 'defaultModel', 'codebuddyEnToken', 'endpointOverride']
|
|
652
|
-
|
|
653
584
|
// codebuddy-en 凭据的第三条通道:DSH 凭据库里可能已有的 workbuddy key(旧 core verbatim)。
|
|
654
585
|
|
|
655
586
|
// 离线/读不到描述文件时的回退静态表(真机按账号实测快照,2026-09)。
|
|
@@ -659,45 +590,7 @@ const SETTINGS_KEYS = ['preferredBackend', 'defaultModel', 'codebuddyEnToken', '
|
|
|
659
590
|
// • dynamic 沙箱形态:scripts/build.mjs 把该文件内联进同一作用域(模板顶部的
|
|
660
591
|
// 第三个占位标记),import 行被 stripModuleSyntax 剥掉,同名标识符天然可见。
|
|
661
592
|
// ⚠️ 本注释里绝不写出那个标记的字面量:build 做全局文本替换,注释中出现同样
|
|
662
|
-
// 会被替换成正文代码,把注释劈开 → 生成物语法崩。
|
|
663
|
-
// ── 三工具 schema(旧 preset/codebuddy-first verbatim;契约 §8)────────────────
|
|
664
|
-
const RUN_PARAMS = {
|
|
665
|
-
type: 'object',
|
|
666
|
-
additionalProperties: false,
|
|
667
|
-
required: ['prompt'],
|
|
668
|
-
properties: {
|
|
669
|
-
prompt: { type: 'string', description: 'The full task/instruction for codebuddy. Be complete and self-contained.' },
|
|
670
|
-
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.' },
|
|
671
|
-
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'auto follows DSH plan state; plan = no writes; accept-edits = allow edits. Default auto.' },
|
|
672
|
-
model: { type: 'string', description: MODEL_DESCRIPTION },
|
|
673
|
-
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
674
|
-
maxTurns: { type: 'integer', description: 'Optional max agentic turns (1-500, default unlimited).' },
|
|
675
|
-
cwd: { type: 'string', description: 'Working directory for codebuddy. Defaults to the DSH workspace root.' },
|
|
676
|
-
addDirs: { type: 'array', items: { type: 'string' }, description: 'Extra directories to add to codebuddy workspace.' },
|
|
677
|
-
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
678
|
-
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
const CONTINUE_PARAMS = {
|
|
682
|
-
type: 'object',
|
|
683
|
-
additionalProperties: false,
|
|
684
|
-
required: ['prompt'],
|
|
685
|
-
properties: {
|
|
686
|
-
prompt: { type: 'string', description: 'Follow-up instruction for the ongoing codebuddy conversation.' },
|
|
687
|
-
sessionId: { type: 'string', description: 'codebuddy session id to resume (from a prior codebuddy_run result).' },
|
|
688
|
-
latest: { type: 'boolean', description: 'Continue the most recent codebuddy conversation instead of a specific id.' },
|
|
689
|
-
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).' },
|
|
690
|
-
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'Execution mode; default auto.' },
|
|
691
|
-
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).' },
|
|
692
|
-
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
693
|
-
maxTurns: { type: 'integer', description: 'Optional max agentic turns.' },
|
|
694
|
-
cwd: { type: 'string', description: "Working directory for codebuddy; when resuming, defaults to the resumed session's project directory." },
|
|
695
|
-
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
696
|
-
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
const STATUS_PARAMS = { type: 'object', additionalProperties: false, required: [], properties: { cwd: { type: 'string', description: 'Optional: filter the snapshot to a single project (working directory).' } } }
|
|
700
|
-
|
|
593
|
+
// 会被替换成正文代码,把注释劈开 → 生成物语法崩。
|
|
701
594
|
function defineProvider (deps = {}) {
|
|
702
595
|
const env = deps.env || {}
|
|
703
596
|
let host = null // attach 后保存 hostOpts(preset/dynamic/MCP 形态宿主能力)
|
|
@@ -1084,12 +977,23 @@ function defineProvider (deps = {}) {
|
|
|
1084
977
|
}
|
|
1085
978
|
},
|
|
1086
979
|
|
|
1087
|
-
// codebuddy_models
|
|
980
|
+
// codebuddy_models:实时列出本机 codebuddy 可用模型(账号探测优先,回落 CLI 名册)。
|
|
1088
981
|
// 只在宿主有 spawn/fs 能力时注册(preset 注入 deps.process / MCP 注入 deps.nodeChild;
|
|
1089
982
|
// dynamic 沙箱形态省略)。**失败必须 ok:false + 可操作错误**,绝不静默返回空列表或
|
|
1090
983
|
// 伪造模型(工作手册红线 §7;对照 workbuddy-auth 的 catalog-store 静默降级 —— 不照抄)。
|
|
1091
984
|
extraTools: (deps.process || deps.nodeChild || deps.nodeFs) ? [codebuddyModelsTool({
|
|
1092
|
-
env, deps, BACKEND_ENUM, normalizeBackend, resolveBackend, modelCatalogDetailed, getAccountCatalog
|
|
985
|
+
env, deps, BACKEND_ENUM, normalizeBackend, resolveBackend, modelCatalogDetailed, getAccountCatalog,
|
|
986
|
+
// faceEnv:让账号探测打到**请求的那个产品面**(与 buildArgv 完全同一套端点/凭据
|
|
987
|
+
// 注入规则)。缺了它,探测会走 CLI 自身默认登录面 —— 标着 codebuddy-en 实际却
|
|
988
|
+
// 问的是 codebuddy 面,得出的清单看着对其实是另一个面的(本轮修的就是这类偏差)。
|
|
989
|
+
faceEnv: (backend) => {
|
|
990
|
+
try {
|
|
991
|
+
const settings = (deps.settings && typeof deps.settings === 'object') ? deps.settings : {}
|
|
992
|
+
const authDomain = readBackendAuthDomain(backend, { env })
|
|
993
|
+
const tok = resolveEnToken(backend === 'codebuddy-en' ? settings.codebuddyEnToken : null, { env })
|
|
994
|
+
return endpointEnv(backend, settings.endpointOverride, authDomain, tok.token) || {}
|
|
995
|
+
} catch (e) { return {} }
|
|
996
|
+
}
|
|
1093
997
|
})] : [],
|
|
1094
998
|
|
|
1095
999
|
// 设置分区(契约 §3):keystone 统一文件 {codebuddy:{...}};旧扁平文件的字段
|
|
@@ -1100,10 +1004,37 @@ function defineProvider (deps = {}) {
|
|
|
1100
1004
|
defaults: () => normalizeSettings(null),
|
|
1101
1005
|
normalize: (raw) => normalizeSettings(raw),
|
|
1102
1006
|
meta() {
|
|
1103
|
-
// defaultModel 下拉 =
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1106
|
-
|
|
1007
|
+
// defaultModel 下拉 = 账号级实时清单(服务端裁决)。走过的弯路留档:
|
|
1008
|
+
// ① 随包冻结的 product.ioa.json → 16 项、2 个 id 已改名(错)
|
|
1009
|
+
// ② CLI `--help` 的 "Currently supported:" → 18 项,但那是 CLI 构建期烙进去的,
|
|
1010
|
+
// 实测同样滞后(含已废弃的 minimax-m3/kimi-k3-1,缺 glm-5.3-flashx 等)
|
|
1011
|
+
// ③ 现在:用无效模型探测让服务端回「该账号当前支持」的 19 项 —— 唯一权威源。
|
|
1012
|
+
// 面板同步回调故用 spawnSync 探测并缓存 5 分钟;失败则下拉留空 + 明确提示,
|
|
1013
|
+
// 绝不展示已知错误的 roster。面跟随 preferredBackend(旧实现硬编码 codebuddy 面)。
|
|
1014
|
+
const face = (currentPreferredBackend() && normalizeBackend(currentPreferredBackend())) || DEFAULT_BACKEND
|
|
1015
|
+
const faceEnv = (() => {
|
|
1016
|
+
try {
|
|
1017
|
+
const s = deps.settings || {}
|
|
1018
|
+
const authDomain = readBackendAuthDomain(face, { env })
|
|
1019
|
+
const tok = resolveEnToken(face === 'codebuddy-en' ? s.codebuddyEnToken : null, { env })
|
|
1020
|
+
return endpointEnv(face, s.endpointOverride, authDomain, tok.token) || {}
|
|
1021
|
+
} catch (e) { return {} }
|
|
1022
|
+
})()
|
|
1023
|
+
const live = cachedCliModels(face, () => probeAccountModelsSync(env, { envExtra: faceEnv, timeoutMs: 12000 }))
|
|
1024
|
+
let options = []
|
|
1025
|
+
let note = ''
|
|
1026
|
+
if (live && live.ok && live.ids.length) {
|
|
1027
|
+
const detail = modelCatalogDetailed(face, { env, accountModels: live.ids })
|
|
1028
|
+
const byId = new Map(detail.map((x) => [x.id, x]))
|
|
1029
|
+
options = live.ids.map((id) => {
|
|
1030
|
+
const d = byId.get(id)
|
|
1031
|
+
return { value: id, label: (d && d.label) || id }
|
|
1032
|
+
})
|
|
1033
|
+
note = '账号实时清单(' + live.ids.length + ' 项,服务端裁决);当前面:' + face + '。日常任务优先免费(x0.00)。'
|
|
1034
|
+
} else {
|
|
1035
|
+
note = '未能读取该账号的模型清单(' + String((live && live.error) || 'unknown') +
|
|
1036
|
+
')。请确认该产品面已登录(在 codebuddy CLI 里完成登录),或手动输入模型 id。'
|
|
1037
|
+
}
|
|
1107
1038
|
return {
|
|
1108
1039
|
titleSuffix: 'CodeBuddy',
|
|
1109
1040
|
fields: [
|
|
@@ -1115,7 +1046,7 @@ function defineProvider (deps = {}) {
|
|
|
1115
1046
|
{
|
|
1116
1047
|
key: 'defaultModel', label: '默认模型', type: 'select', allowCustom: true, placeholder: '(CLI 默认)',
|
|
1117
1048
|
options,
|
|
1118
|
-
note
|
|
1049
|
+
note
|
|
1119
1050
|
},
|
|
1120
1051
|
{ key: 'codebuddyEnToken', label: 'WorkBuddy 国际版 Token', type: 'secret', note: '仅 backend="codebuddy-en" 需要。国际版登录 token 被 protector key 封装不落盘,只能手工提供;留空则自动尝试 DSH 凭据库的 WORKBUDDY_TOKEN。' },
|
|
1121
1052
|
{ key: 'endpointOverride', label: '端点覆盖', type: 'text', placeholder: 'https://…/v2', note: '一般留空:端点按登录域自动对齐(www.codebuddy.cn 等)。仅在需要强制指定 API 端点时填写,且必须自带 /v2。' }
|
|
@@ -1199,6 +1130,7 @@ function defineProvider (deps = {}) {
|
|
|
1199
1130
|
return descriptor
|
|
1200
1131
|
}
|
|
1201
1132
|
|
|
1133
|
+
// ── inlined from providers/codebuddy-catalog.mjs ──
|
|
1202
1134
|
// providers/codebuddy-catalog.mjs — codebuddy 模型目录/积分/描述文件解析(纯逻辑)。
|
|
1203
1135
|
//
|
|
1204
1136
|
// 从 providers/codebuddy.mjs 拆出:主文件原 864 行远超 600 行红线(规则 §26)。
|
|
@@ -1431,6 +1363,238 @@ const BACKEND_ENUM = ['codebuddy', 'codebuddy-intl', 'codebuddy-ioa', 'codebuddy
|
|
|
1431
1363
|
return v && v.length ? v.slice() : null
|
|
1432
1364
|
}
|
|
1433
1365
|
|
|
1366
|
+
// ── 实时模型清单(CLI 是一手权威来源,对齐 workbuddy-auth 的「权威白名单」思路)──
|
|
1367
|
+
//
|
|
1368
|
+
// 为什么不再以静态表/磁盘描述文件为主:实测本机 codebuddy CLI --
|
|
1369
|
+
// • 账号实时清单(用无效模型触发 400 报文)= 19 项
|
|
1370
|
+
// • CLI `--help` 声明的 roster = 19 项,与账号清单完全一致
|
|
1371
|
+
// • 我们原先读的 product.ioa.json 静态表 = 16 项,且 2 个 id 已过时
|
|
1372
|
+
// (minimax-m3 → minimax-m3-pay、kimi-k3-1 → kimi-k3-2),另缺 4 个新模型。
|
|
1373
|
+
// 静态表是「随包冻结」的,必然落后于 CLI,故只保留作倍率元数据与最后兜底。
|
|
1374
|
+
//
|
|
1375
|
+
// 两级实时来源(都不静默冒充权威数据,各带 source 标签):
|
|
1376
|
+
// A. account-live —— 起 CLI 用无效模型探测「该账号当前支持」的清单(需该面已登录,
|
|
1377
|
+
// 一次网络往返;对照 workbuddy-auth 的 agents[cli].models 白名单语义)。
|
|
1378
|
+
// B. cli-help —— 解析 `--help` 的 "Currently supported: (...)"(零网络、随 CLI
|
|
1379
|
+
// 版本自动更新;产品面级 roster)。
|
|
1380
|
+
// 静态表仅作 credit 元数据与 descriptor-static 兜底标签,绝不冒充实时。
|
|
1381
|
+
|
|
1382
|
+
/** 解析 CLI `--help` 里声明的模型 roster。空/畸形输入返回 []。
|
|
1383
|
+
* ⚠️ 名册很长时 yargs 会在任意列**折行**(实测把 `minimax-m3-pay` 从中间断成
|
|
1384
|
+
* `minimax-` + `m3-pay`),若按行/按逗号直接切会丢项或产生残片(同一命令两次运行
|
|
1385
|
+
* 给出 18/19 两种结果)。故先去掉括号内所有空白再按逗号切:逗号是唯一分隔符,
|
|
1386
|
+
* 折行插入的空白一律视为噪声。 */
|
|
1387
|
+
function parseHelpModels(helpText) {
|
|
1388
|
+
const m = /Currently supported:\s*\(([^)]+)\)/.exec(String(helpText || ''))
|
|
1389
|
+
if (!m) return []
|
|
1390
|
+
const out = []
|
|
1391
|
+
for (const raw of m[1].replace(/\s+/g, '').split(',')) {
|
|
1392
|
+
const id = raw.trim()
|
|
1393
|
+
if (id && /^[A-Za-z0-9_.\-]+$/.test(id) && out.indexOf(id) < 0) out.push(id)
|
|
1394
|
+
}
|
|
1395
|
+
return out
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/** 解析 codebuddy CLI 的可执行候选(env > npm 全局 shim > PATH 名)。 */
|
|
1399
|
+
function cbCatalogCliCandidates(env) {
|
|
1400
|
+
const e = env || cbCatalogEnv || {}
|
|
1401
|
+
const out = []
|
|
1402
|
+
const push = (p) => { if (p && out.indexOf(p) < 0) out.push(p) }
|
|
1403
|
+
if (e.CODEBUDDY_BIN) push(String(e.CODEBUDDY_BIN))
|
|
1404
|
+
const joinP = (a, b) => cbCatalogPathJoin(a, b)
|
|
1405
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy.cmd'))
|
|
1406
|
+
if (e.APPDATA) push(joinP(joinP(e.APPDATA, 'npm'), 'codebuddy'))
|
|
1407
|
+
push('codebuddy')
|
|
1408
|
+
return out
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
const CLI_MODEL_CACHE = new Map() // backend → { at, mode, ids }
|
|
1412
|
+
const CLI_MODEL_TTL_MS = 5 * 60 * 1000
|
|
1413
|
+
|
|
1414
|
+
// Windows .cmd/.bat 需要经 shell(Node 安全限制),但 `shell:true` + args 数组会触发
|
|
1415
|
+
// DEP0190(参数不转义)。这里统一做法:把命令与参数拼成【一个】字符串交给 shell,
|
|
1416
|
+
// 参数各自按 Windows 规则加引号 —— 既避开 DEP0190,又保证含空格参数不被拆开。
|
|
1417
|
+
function quoteWinArg(a) {
|
|
1418
|
+
const s = String(a)
|
|
1419
|
+
if (s === '') return '""'
|
|
1420
|
+
if (!/[\s"&|<>^]/.test(s)) return s
|
|
1421
|
+
return '"' + s.replace(/"/g, '\\"') + '"'
|
|
1422
|
+
}
|
|
1423
|
+
function cliCommandLine(exe, args) {
|
|
1424
|
+
return [exe].concat(args || []).map(quoteWinArg).join(' ')
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/** 把候选拆成 { cmd, prefixArgs }:.mjs/.js/.cjs 目标经 node 包装(测试夹具与自定义
|
|
1428
|
+
* 入口都靠这条),其余原样。 */
|
|
1429
|
+
function splitExeSpec(exe) {
|
|
1430
|
+
if (/\.(mjs|cjs|js)$/i.test(String(exe))) {
|
|
1431
|
+
const proc = globalThis.process
|
|
1432
|
+
return { cmd: (proc && proc.execPath) ? proc.execPath : 'node', prefixArgs: [String(exe)] }
|
|
1433
|
+
}
|
|
1434
|
+
return { cmd: String(exe), prefixArgs: [] }
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function cliSpawnSync(exe, args, opts) {
|
|
1438
|
+
const proc = globalThis.process
|
|
1439
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
1440
|
+
if (!child || typeof child.spawnSync !== 'function') return null
|
|
1441
|
+
const base = Object.assign({
|
|
1442
|
+
encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'pipe']
|
|
1443
|
+
}, opts || {})
|
|
1444
|
+
const spec = splitExeSpec(exe)
|
|
1445
|
+
const argv = spec.prefixArgs.concat(args || [])
|
|
1446
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
1447
|
+
if (isCmd) base.shell = true
|
|
1448
|
+
try {
|
|
1449
|
+
// shell 形态不带 args 数组(否则 DEP0190);非 shell 形态正常传数组。
|
|
1450
|
+
return isCmd
|
|
1451
|
+
? child.spawnSync(cliCommandLine(spec.cmd, argv), base)
|
|
1452
|
+
: child.spawnSync(spec.cmd, argv, base)
|
|
1453
|
+
} catch (e) { return null }
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* 同步取「CLI 声明的 roster」(零网络,随 CLI 版本走)。
|
|
1458
|
+
* @returns {{ok:boolean, ids:string[], mode:string, error?:string}}
|
|
1459
|
+
*/
|
|
1460
|
+
function fetchHelpModelsSync(env, opts) {
|
|
1461
|
+
const o = opts || {}
|
|
1462
|
+
const cands = cbCatalogCliCandidates(env)
|
|
1463
|
+
let lastErr = null
|
|
1464
|
+
for (const exe of cands) {
|
|
1465
|
+
const r = cliSpawnSync(exe, ['--help'], { timeout: Math.max(1000, Math.min(o.timeoutMs || 12000, 30000)) })
|
|
1466
|
+
if (!r || r.error) { lastErr = 'spawn failed: ' + String((r && r.error && r.error.message) || 'unavailable'); continue }
|
|
1467
|
+
const text = String(r.stdout || '') + String(r.stderr || '')
|
|
1468
|
+
const ids = parseHelpModels(text)
|
|
1469
|
+
if (ids.length) return { ok: true, ids, mode: 'cli-help', exe }
|
|
1470
|
+
lastErr = 'no roster in --help output'
|
|
1471
|
+
}
|
|
1472
|
+
return { ok: false, ids: [], mode: 'cli-help', error: lastErr || 'codebuddy CLI not found (set CODEBUDDY_BIN)' }
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* 同步版的账号探测(供设置面板用 —— 面板必须同步返回)。
|
|
1477
|
+
* 走 spawnSync + 超时;结果进 5 分钟缓存,避免每次渲染都打一次网络。
|
|
1478
|
+
*
|
|
1479
|
+
* ⚠️ 为什么面板不能用 `--help` 名册顶替:实测本机 CLI 2.167.0 的 help 文本**本身
|
|
1480
|
+
* 就是过时的** —— 它列 18 项且含已不存在的 `minimax-m3` / `kimi-k3-1`,缺真实的
|
|
1481
|
+
* `glm-5.3-flashx` / `minimax-m3-pay` / `kimi-k3-2`;而账号探测给出 19 项且全部有效。
|
|
1482
|
+
* 也就是说「CLI 声明」≠「服务端实际可用」,拿它当兜底会直接把错误清单展示给用户。
|
|
1483
|
+
* 故这里宁可空列表 + 明确提示,也不展示已知错误的 roster。
|
|
1484
|
+
* @returns {{ok:boolean, ids:string[], mode:string, error?:string}}
|
|
1485
|
+
*/
|
|
1486
|
+
function probeAccountModelsSync(env, opts) {
|
|
1487
|
+
const o = opts || {}
|
|
1488
|
+
const cands = cbCatalogCliCandidates(env)
|
|
1489
|
+
const extraEnv = o.envExtra || {}
|
|
1490
|
+
const reasons = []
|
|
1491
|
+
const timeout = Math.max(3000, Math.min(o.timeoutMs || 12000, 30000))
|
|
1492
|
+
for (const exe of cands) {
|
|
1493
|
+
const r = cliSpawnSync(exe, ['-p', 'hi', '--model', '__clb_probe_invalid__', '--output-format', 'text'], {
|
|
1494
|
+
timeout,
|
|
1495
|
+
env: Object.assign({}, (globalThis.process && globalThis.process.env) || {}, extraEnv)
|
|
1496
|
+
})
|
|
1497
|
+
if (!r || r.error) { reasons.push(exe + ': ' + String((r && r.error && (r.error.code || r.error.message)) || 'spawn failed')); continue }
|
|
1498
|
+
const out = String(r.stdout || '') + String(r.stderr || '')
|
|
1499
|
+
const ids = parseAccountModels(out)
|
|
1500
|
+
if (ids && ids.length) return { ok: true, ids, mode: 'account-live', exe }
|
|
1501
|
+
const head = String(out).replace(/\s+/g, ' ').trim().slice(0, 140)
|
|
1502
|
+
reasons.push(exe + ': exit ' + r.status + ', no account roster' + (head ? ' — ' + head : ''))
|
|
1503
|
+
}
|
|
1504
|
+
return { ok: false, ids: [], mode: 'account-live', error: reasons.join('; ') || 'no CLI candidate to probe' }
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* 异步探测「该账号当前支持」的清单:用无效模型触发 CLI 的 400 报文。
|
|
1509
|
+
* 对齐 workbuddy-auth 的权威白名单语义(服务器告诉我们这个账号能用什么)。
|
|
1510
|
+
* @returns {Promise<{ok:boolean, ids:string[], mode:string, error?:string}>}
|
|
1511
|
+
*/
|
|
1512
|
+
function probeAccountModels(env, opts) {
|
|
1513
|
+
const o = opts || {}
|
|
1514
|
+
const cands = cbCatalogCliCandidates(env)
|
|
1515
|
+
const proc = globalThis.process
|
|
1516
|
+
const child = (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule('node:child_process') : null
|
|
1517
|
+
if (!child || typeof child.spawn !== 'function') {
|
|
1518
|
+
return Promise.resolve({ ok: false, ids: [], mode: 'account-live', error: 'no spawn capability in this host form' })
|
|
1519
|
+
}
|
|
1520
|
+
const extraEnv = o.envExtra || {}
|
|
1521
|
+
return new Promise((resolve) => {
|
|
1522
|
+
let idx = 0
|
|
1523
|
+
// 记录每个候选的真实结局:区分「压根没找到 CLI」与「CLI 跑了但没吐账号名录」
|
|
1524
|
+
// (后者通常是该面未登录 / 端点拒了)—— 报错文案必须能指导下一步,不能一律
|
|
1525
|
+
// 说 "CLI not found"(本轮就踩到:codebuddy-en 无 token 时被误报成找不到 CLI)。
|
|
1526
|
+
const reasons = []
|
|
1527
|
+
const attempt = () => {
|
|
1528
|
+
if (idx >= cands.length) {
|
|
1529
|
+
resolve({
|
|
1530
|
+
ok: false, ids: [], mode: 'account-live',
|
|
1531
|
+
error: reasons.length ? reasons.join('; ') : 'no CLI candidate to probe',
|
|
1532
|
+
notFound: reasons.every((r) => /not found|ENOENT|spawn/i.test(r))
|
|
1533
|
+
})
|
|
1534
|
+
return
|
|
1535
|
+
}
|
|
1536
|
+
const exe = cands[idx++]
|
|
1537
|
+
let c
|
|
1538
|
+
try {
|
|
1539
|
+
// .cmd/.bat 需经 shell(Windows 安全限制);.mjs/.js 经 node 包装。
|
|
1540
|
+
// 与 cliSpawnSync 同一规则:shell 形态把命令拼成单字符串(避免 DEP0190)。
|
|
1541
|
+
const spec = splitExeSpec(exe)
|
|
1542
|
+
const isCmd = /\.(cmd|bat)$/i.test(spec.cmd)
|
|
1543
|
+
const probeArgs = spec.prefixArgs.concat(['-p', 'hi', '--model', '__clb_probe_invalid__', '--output-format', 'text'])
|
|
1544
|
+
const opts = {
|
|
1545
|
+
stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
1546
|
+
env: Object.assign({}, proc.env, extraEnv)
|
|
1547
|
+
}
|
|
1548
|
+
c = isCmd
|
|
1549
|
+
? child.spawn(cliCommandLine(spec.cmd, probeArgs), Object.assign({ shell: true }, opts))
|
|
1550
|
+
: child.spawn(spec.cmd, probeArgs, opts)
|
|
1551
|
+
} catch (e) { reasons.push(exe + ': spawn threw ' + String(e && e.message || e)); attempt(); return }
|
|
1552
|
+
let out = ''
|
|
1553
|
+
const timer = setTimeout(() => { try { c.kill() } catch (e) { } }, Math.max(5000, Math.min(o.timeoutMs || 30000, 90000)))
|
|
1554
|
+
const onData = (d) => { out += d; if (out.length > 400000) out = out.slice(-200000) }
|
|
1555
|
+
c.stdout.on('data', onData)
|
|
1556
|
+
c.stderr.on('data', onData)
|
|
1557
|
+
c.on('error', (e) => {
|
|
1558
|
+
clearTimeout(timer)
|
|
1559
|
+
reasons.push(exe + ': ' + String((e && e.code) || (e && e.message) || 'spawn error'))
|
|
1560
|
+
attempt()
|
|
1561
|
+
})
|
|
1562
|
+
c.on('close', (code) => {
|
|
1563
|
+
clearTimeout(timer)
|
|
1564
|
+
const ids = parseAccountModels(out)
|
|
1565
|
+
if (ids && ids.length) { resolve({ ok: true, ids, mode: 'account-live', exe }); return }
|
|
1566
|
+
const head = String(out).replace(/\s+/g, ' ').trim().slice(0, 160)
|
|
1567
|
+
reasons.push(exe + ': exit ' + code + ', no account roster' + (head ? ' — ' + head : ''))
|
|
1568
|
+
attempt()
|
|
1569
|
+
})
|
|
1570
|
+
}
|
|
1571
|
+
attempt()
|
|
1572
|
+
})
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/** 缓存读写(面板每次渲染都会取,避免反复起进程)。 */
|
|
1576
|
+
function cachedCliModels(backend, load, nowMs) {
|
|
1577
|
+
const at = Number(nowMs) || Date.now()
|
|
1578
|
+
const hit = CLI_MODEL_CACHE.get(backend)
|
|
1579
|
+
if (hit && at - hit.at < CLI_MODEL_TTL_MS) return hit
|
|
1580
|
+
const v = typeof load === 'function' ? load() : load
|
|
1581
|
+
if (v && v.ok && v.ids && v.ids.length) CLI_MODEL_CACHE.set(backend, { at, mode: v.mode, ids: v.ids.slice() })
|
|
1582
|
+
return v
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/** 读统一设置文件里 codebuddy 分区的 preferredBackend(meta 需要它才知道列哪个面)。 */
|
|
1586
|
+
function currentPreferredBackend() {
|
|
1587
|
+
try {
|
|
1588
|
+
const e = cbCatalogEnv || {}
|
|
1589
|
+
const p = cbCatalogRead(e.CLI_BRIDGE_SETTINGS_FILE || cbCatalogPathJoin(
|
|
1590
|
+
e.DSH_HOME || (e.APPDATA ? cbCatalogPathJoin(e.APPDATA, 'DSH Desktop', 'dsh-home') : ''), 'cli-bridge-settings.json'))
|
|
1591
|
+
if (!p) return null
|
|
1592
|
+
const all = JSON.parse(p)
|
|
1593
|
+
const v = all && all.codebuddy && all.codebuddy.preferredBackend
|
|
1594
|
+
return typeof v === 'string' && v ? v : null
|
|
1595
|
+
} catch (err) { return null }
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1434
1598
|
// CLI 报错里解析「该账号当前支持的模型」(旧 core parseAccountModels verbatim)。
|
|
1435
1599
|
function parseAccountModels(text) {
|
|
1436
1600
|
const s = String(text || '')
|
|
@@ -1566,6 +1730,88 @@ const CREDENTIAL_KEYS = ['WORKBUDDY_TOKEN', 'WORKBUDDY_AI_TOKEN', 'CODEBUDDY_EN_
|
|
|
1566
1730
|
return { token: null, source: null }
|
|
1567
1731
|
}
|
|
1568
1732
|
|
|
1733
|
+
|
|
1734
|
+
// ── inlined from providers/codebuddy-consts.mjs ──
|
|
1735
|
+
// providers/codebuddy-consts.mjs — codebuddy 的提示词策略与后端注册表常量(纯数据)。
|
|
1736
|
+
//
|
|
1737
|
+
// 从 providers/codebuddy.mjs 拆出,让主描述符留在 600 行红线内(规则 §26)。
|
|
1738
|
+
// build 会把 providers/codebuddy-*.mjs 全部内联进 dynamic 沙箱同一作用域。
|
|
1739
|
+
// 命名纪律:顶层名绝不与 core/bridge-kit.mjs 同名(同作用域重复声明 = 语法错)。
|
|
1740
|
+
// ⚠️ 注释里绝不写出口令标记字面量(build 全局替换会把注释劈开)。
|
|
1741
|
+
|
|
1742
|
+
// ── 提示词策略段(旧 preset POLICY_TEXT verbatim;systemPrompt order 5)────────
|
|
1743
|
+
const POLICY_TEXT = [
|
|
1744
|
+
'codebuddy-first execution policy (local codebuddy CLI bridge, with fallback + live indicator + live status tool).',
|
|
1745
|
+
'',
|
|
1746
|
+
'A local sub-agent CLI named "codebuddy" (Tencent CodeBuddy Code) is available through the codebuddy_run and codebuddy_continue tools. DSH fully controls codebuddy: every invocation runs non-interactively with permissions auto-approved (--permission-mode bypassPermissions), so codebuddy never prompts and DSH decides all of its work. A header status light shows when codebuddy is working — one light per project (working directory), so parallel runs in different projects are tracked separately; call codebuddy_status any time to see what codebuddy is doing right now (per project: current tool/step, recent trail, and cumulative usage).',
|
|
1747
|
+
'',
|
|
1748
|
+
'Priority rule for EVERY mode (normal, plan, accept-edits, subagent/workflow/ralph delegation, and goal rounds):',
|
|
1749
|
+
'- For any real work — implementing features, editing/refactoring code, debugging, running builds, or multi-file investigation — prefer dispatching the task to codebuddy_run FIRST with a complete, self-contained prompt.',
|
|
1750
|
+
'- Use native read/write/shell tools mainly for quick read-only lookups and for the final build/test verification of what codebuddy produced.',
|
|
1751
|
+
'- In plan mode, call codebuddy_run with mode=auto (or mode=plan); in accept-edits/normal mode codebuddy_run applies edits directly.',
|
|
1752
|
+
'- When you delegate to a DSH subagent or workflow, instruct that delegate to also prefer codebuddy_run.',
|
|
1753
|
+
'- For long-running tasks, call codebuddy_run with background=true and collect the result with job_output; use codebuddy_status to watch progress.',
|
|
1754
|
+
'',
|
|
1755
|
+
'Fallback protocol: when codebuddy is rate-limited or the network is down, codebuddy_run/codebuddy_continue automatically pop a confirmation dialog asking the user whether to use the DSH local API config. If the returned result has fallback=true (status FALLBACK_TO_DSH), the user chose to fall back: complete the task with native DSH tools / the local model and DO NOT call codebuddy again for this task. If ok=false without fallback, report the codebuddy error. Never loop codebuddy calls; never ask codebuddy to call back into DSH.',
|
|
1756
|
+
'',
|
|
1757
|
+
'Model selection: codebuddy_run takes an optional model. When unspecified, the CLI default applies unless the user set a preferred default model in the plugin settings (then that is injected automatically per call). Which models ACTUALLY WORK depends on the ACCOUNT, and the authoritative live list comes from the server: running the CLI with a nonexistent model returns 400 followed by "Currently supported models for your account:" and one "- <id>" per line (core exposes parseAccountModels for this; the plugin caches the result automatically and the settings panel shows exactly those ids, with the credit multiplier of each). Do NOT trust the bundled product descriptor as a menu of usable models: it lists ids with an "-ioa" suffix (glm-5.3-ioa) and expensive flagships (claude-opus-4.8, gpt-5.4, gpt-6-astra) that are present in the catalogue but NOT licensed for this account, and picking one fails with 400 "only available for authorized users". The four backends are also not one pool: "codebuddy" and "codebuddy-intl" are the npm CLI and offer its catalogue - including the free tier (hy3 and deepseek-v4.1-flash cost x0.00 credits; hy4-preview costs x0.29 but is frequently free on promotional accounts); "codebuddy-en" (WorkBuddyAI) has its OWN catalogue which does NOT contain hy4-preview or deepseek-v4.1-flash - it offers hy3 (free), gpt-5.1-codex-mini, gemini-3.1-flash-lite, gemini-2.5-flash, minimax-m3, gemini-3.0-flash, deepseek-v3-2-volc, kimi-k2.5/2.6, glm-5.0/5.2/5.3, gpt-5.3-codex, gpt-5.4/5.5, gpt-5.6-sol/terra/luna and the role aliases (fast/balanced/primary/deep-model); "workbuddy" (domestic desktop) has a further catalogue (auto, hy4-preview, hy3, deepseek-v4.1-flash, glm-5.x, kimi-k2.6/2.7/2.8-preview/k3-1, minimax-m3/m2.7, deepseek-v4-pro). Within a face availability is still gated per ACCOUNT: an id outside the account catalogue fails with "400 model [...] service info not found", and one inside it but unlicensed fails with "400 model [...] is only available for authorized users". International ids (claude-sonnet-5, claude-opus-5, gemini-3.1-pro, ...) are per-account gated and return the latter for a domestic account. COST: every id carries a credit multiplier in the credits field of its descriptor (x0.00 means free, x3.31 means 3.31x the base rate); the settings panel shows each candidate with its multiplier and reads it live from the CLI descriptor, so it stays in sync when the CLI is upgraded. Measured free (x0.00) models for this account: hy3 and deepseek-v4.1-flash (both npm faces) and hy3 for WorkBuddyAI; hy4-preview is x0.29 on npm faces. Prefer the cheapest model that can do the job and use a free one for routine work; do not silently upgrade to an expensive id when the task is simple. To get the exact live list for a CLI, run it with a deliberately invalid model id - the reply prints "Currently supported models for your account:" followed by one "- <id>" per line (core exposes parseAccountModels for this). Pass a model only when the task clearly benefits from a specific one; the default is usually right. Optional effort: minimal/low/medium/high/xhigh/max. Optional maxTurns caps agentic turns (default unlimited).',
|
|
1758
|
+
'',
|
|
1759
|
+
'Backends: codebuddy_run/codebuddy_continue take an optional backend parameter choosing which CLI face of the same engine (Tencent CodeBuddy Code) runs the task. "codebuddy" is the domestic CodeBuddy (npm CLI @tencent-ai/codebuddy-code, product endpoint www.codebuddy.ai) — default for coding work. "codebuddy-intl" is the INTERNATIONAL face of that SAME npm CLI (product.ioa.json catalogue: claude-sonnet-5, claude-opus-5, gemini-3.1-pro, gpt-6-astra, hy3-ioa …); use it when the account is an international CodeBuddy account. Its aliases "codebuddy-ioa" and "codebuddy-international" are accepted and normalized to codebuddy-intl. "codebuddy-en" is the WorkBuddy INTERNATIONAL edition — the CLI bundled with the WorkBuddyAI desktop app (C:\\Program Files\\WorkBuddyAI, product endpoint www.workbuddy.ai); the aliases "workbuddy-en" and "workbuddy-ai" are also accepted and normalized to codebuddy-en. "workbuddy" is the CLI bundled with the domestic WorkBuddy desktop app (product endpoint copilot.tencent.com, zero config) — the office-scenario face: documents, slides, spreadsheets, knowledge-base lookups, image/video generation, WeChat/WeCom replies. When the user asks for office/document/IM work, dispatch with backend="workbuddy"; for international accounts use "codebuddy-intl" (CodeBuddy 国际版) or "codebuddy-en" (WorkBuddy 国际版 / WorkBuddyAI). Sessions are kept per backend (login domains are exclusive), and continuing a session automatically routes back to the backend that owns it (explicit backend wins). A user-preferred default backend (plugin settings) applies when a call is new (no session) and no explicit backend is given.'
|
|
1760
|
+
].join('\n')
|
|
1761
|
+
|
|
1762
|
+
// ── 限流/网络/认证失败判定(旧 core verbatim)。仅匹配 stderr + status;数字码
|
|
1763
|
+
// 加词边界(避免 "1500" 命中 500、"4013" 命中 401)。中文码(流量/受限/金额/
|
|
1764
|
+
// 余额/额度/认证/连接/断开 等)为 codebuddy 面专属,必须逐字保留。
|
|
1765
|
+
// 命名纪律(mimo 同款):本文件顶层名绝不与 core/bridge-kit.mjs 顶层导出同名 ——
|
|
1766
|
+
// dynamic 形态把 kit 与 provider 内联进同一脚本作用域,同名 const 直接炸语法
|
|
1767
|
+
|
|
1768
|
+
// ── 后端注册表(旧 core verbatim:端点按登录域自动对齐)───────────────────────
|
|
1769
|
+
const CODEBUDDY_LIMIT_RE = /rate.?limit|ratelimit|\b429\b|too many|quota|insufficient|credit|balance|exhausted|exceed|\bnetwork\b|offline|ENETUNREACH|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|\btimeouts?\b|timed out|unavailable|\b50[023]\b|\b40[13]\b|unauthorized|invalid api|api key|proxy|socket|tls|ssl|\bdns\b|网络|超时|限流|流量|受限|配额|金额|余额|额度|认证|连接|断开/i
|
|
1770
|
+
|
|
1771
|
+
// ── 后端注册表(旧 core verbatim:端点按登录域自动对齐)───────────────────────
|
|
1772
|
+
const BACKENDS = ['codebuddy', 'codebuddy-intl', 'codebuddy-en', 'workbuddy']
|
|
1773
|
+
|
|
1774
|
+
const BACKEND_ENDPOINTS = {
|
|
1775
|
+
'codebuddy': 'https://www.codebuddy.ai/v2',
|
|
1776
|
+
'codebuddy-intl': 'https://www.codebuddy.ai/v2',
|
|
1777
|
+
'codebuddy-en': 'https://www.workbuddy.ai/v2',
|
|
1778
|
+
'workbuddy': 'https://copilot.tencent.com/v2'
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
const AUTH_DOMAIN_ENDPOINTS = {
|
|
1782
|
+
'www.codebuddy.cn': 'https://www.codebuddy.cn/v2',
|
|
1783
|
+
'www.codebuddy.ai': 'https://www.codebuddy.ai/v2',
|
|
1784
|
+
'www.workbuddy.ai': 'https://www.workbuddy.ai/v2',
|
|
1785
|
+
'copilot.tencent.com': 'https://copilot.tencent.com/v2'
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
const BACKEND_LABELS = {
|
|
1789
|
+
'codebuddy': 'CodeBuddy 国内版(npm CLI)',
|
|
1790
|
+
'codebuddy-intl': 'CodeBuddy 国际版(npm CLI · 国际面)',
|
|
1791
|
+
'codebuddy-en': 'WorkBuddy 国际版(WorkBuddyAI 桌面 CLI)',
|
|
1792
|
+
'workbuddy': 'WorkBuddy 国内版(桌面 CLI)'
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
const BACKEND_ALIASES = {
|
|
1796
|
+
'workbuddy-en': 'codebuddy-en',
|
|
1797
|
+
'workbuddy-ai': 'codebuddy-en',
|
|
1798
|
+
'codebuddy-ioa': 'codebuddy-intl',
|
|
1799
|
+
'codebuddy-international': 'codebuddy-intl',
|
|
1800
|
+
'codebuddy-oversea': 'codebuddy-intl'
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
const DEFAULT_BACKEND = 'codebuddy'
|
|
1804
|
+
|
|
1805
|
+
const SETTINGS_KEYS = ['preferredBackend', 'defaultModel', 'codebuddyEnToken', 'endpointOverride']
|
|
1806
|
+
|
|
1807
|
+
|
|
1808
|
+
// ── inlined from providers/codebuddy-models-tool.mjs ──
|
|
1809
|
+
// providers/codebuddy-models-tool.mjs — codebuddy_models 工具定义(纯逻辑)。
|
|
1810
|
+
//
|
|
1811
|
+
// 从 codebuddy-catalog.mjs 再拆出:两个文件都要留在 600 行红线内(规则 §26)。
|
|
1812
|
+
// scripts/build.mjs 会把 providers/codebuddy-*.mjs 全部内联进 dynamic 沙箱的同一个
|
|
1813
|
+
// 作用域,所以这里仍可引用 catalog 的函数(沙箱内天然可见);ESM 形态走本文件的 import。
|
|
1814
|
+
// ⚠️ 注释里绝不写出口令标记字面量(build 全局替换会劈开注释 → 生成物语法崩)。
|
|
1569
1815
|
// ── codebuddy_models 工具定义(搬自主文件;纯逻辑,依赖经 ctx 注入)────────────
|
|
1570
1816
|
// ctx = { env, deps, resolveBackend, modelCatalogDetailed, getAccountCatalog }
|
|
1571
1817
|
function codebuddyModelsTool(ctx) {
|
|
@@ -1574,7 +1820,7 @@ function codebuddyModelsTool(ctx) {
|
|
|
1574
1820
|
const env = c.env || {}
|
|
1575
1821
|
return {
|
|
1576
1822
|
name: 'codebuddy_models',
|
|
1577
|
-
description: 'List the models
|
|
1823
|
+
description: 'List the models the local codebuddy CLI can use for a given backend, read LIVE from the CLI. Primary source: an account probe (runs the CLI with a deliberately invalid model id, which makes the server print "Currently supported models for your account") — this is the authoritative licensed list for THIS account. If that face is not logged in, it falls back to the roster the installed CLI declares in --help and says so in the source field. Credit multipliers (x0.00 = free) come from the product descriptor as metadata. Returns ok:false with an actionable error when no live roster can be read — it never presents a frozen static table as if it were live.',
|
|
1578
1824
|
inputSchema: {
|
|
1579
1825
|
type: 'object', additionalProperties: false, required: [],
|
|
1580
1826
|
properties: {
|
|
@@ -1596,41 +1842,118 @@ function codebuddyModelsTool(ctx) {
|
|
|
1596
1842
|
lines.push(' ' + m.id + (m.creditLabel ? ' ' + m.creditLabel : '') + (flags.length ? ' [' + flags.join(',') + ']' : ''))
|
|
1597
1843
|
}
|
|
1598
1844
|
if (list.length > 40) lines.push(' … and ' + (list.length - 40) + ' more')
|
|
1599
|
-
|
|
1845
|
+
// 只有一种来源:账号实时(服务端裁决)。不再有 cli-help 兜底分支,
|
|
1846
|
+
// 因为 CLI 的 --help 名册实测会滞后服务端并含已废弃 id。
|
|
1847
|
+
lines.push('source: account-live (authoritative for this account)')
|
|
1848
|
+
if (v.note) lines.push('note: ' + v.note)
|
|
1600
1849
|
return [{ type: 'text', text: lines.join('\n') }]
|
|
1601
1850
|
},
|
|
1602
1851
|
async logic(args) {
|
|
1603
1852
|
const a = args || {}
|
|
1604
|
-
const preferred = (deps.settings && deps.settings.preferredBackend) || null
|
|
1853
|
+
const preferred = currentPreferredBackend() || (deps.settings && deps.settings.preferredBackend) || null
|
|
1605
1854
|
const backend = c.normalizeBackend(a.backend) || c.resolveBackend({}, null, preferred)
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1855
|
+
|
|
1856
|
+
// ① 账号实时(最权威,对照 workbuddy-auth 的 agents[cli] 白名单语义)。
|
|
1857
|
+
// 该面未登录/无 token 时探测会失败 —— 失败就**明确降级到 CLI 名册并标注**,
|
|
1858
|
+
// 绝不把静态表冒充成账号可用清单。
|
|
1859
|
+
const extraEnv = (c.faceEnv && c.faceEnv(backend)) || {}
|
|
1860
|
+
const probe = await probeAccountModels(env, { envExtra: extraEnv, timeoutMs: a.timeoutMs })
|
|
1861
|
+
|
|
1862
|
+
// 唯一权威源 = 账号探测(服务端裁决)。**不再回落 CLI `--help` 名册**:
|
|
1863
|
+
// 实测 CLI 2.167.0 的 help 文本本身就过时(18 项、含已废弃的 minimax-m3 /
|
|
1864
|
+
// kimi-k3-1、缺 3 个真实模型),拿它兜底等于把错误清单当结果交付。
|
|
1865
|
+
// 探测失败就明确失败,并附上「CLI 声明了 N 项」作为诊断线索(只作提示,不展示
|
|
1866
|
+
// 那些 id),符合红线:开发期禁止静默降级。
|
|
1867
|
+
if (!probe.ok || !probe.ids || !probe.ids.length) {
|
|
1868
|
+
let hint = ''
|
|
1869
|
+
try {
|
|
1870
|
+
const help = fetchHelpModelsSync(env, { timeoutMs: a.timeoutMs })
|
|
1871
|
+
if (help.ok && help.ids.length) {
|
|
1872
|
+
hint = ' The installed CLI declares ' + help.ids.length + ' models via --help, but that roster is a static ' +
|
|
1873
|
+
'build-time list and has been observed to lag the server (it can contain retired ids). It is NOT shown here ' +
|
|
1874
|
+
'because it is not authoritative for this account.'
|
|
1875
|
+
}
|
|
1876
|
+
} catch (e) { }
|
|
1613
1877
|
return {
|
|
1614
1878
|
ok: false, backend,
|
|
1615
|
-
error: 'no model
|
|
1879
|
+
error: 'no account model roster for backend "' + backend + '": ' + String(probe.error || 'probe failed') +
|
|
1880
|
+
'. Make sure this product face is logged in (run the codebuddy CLI and complete login), or set CODEBUDDY_BIN ' +
|
|
1881
|
+
'to the right executable, then retry.' + hint
|
|
1616
1882
|
}
|
|
1617
1883
|
}
|
|
1618
|
-
const
|
|
1619
|
-
const
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
}))
|
|
1884
|
+
const ids = probe.ids
|
|
1885
|
+
const source = 'account-live'
|
|
1886
|
+
|
|
1887
|
+
// ③ 倍率/授权元数据(来自描述文件;仅作标注,不参与「是否可用」判定)。
|
|
1888
|
+
let detail = []
|
|
1889
|
+
try { detail = c.modelCatalogDetailed(backend, { env, accountModels: ids }) || [] } catch (e) { detail = [] }
|
|
1890
|
+
const byId = new Map(detail.map((x) => [x.id, x]))
|
|
1891
|
+
let list = ids.map((id) => {
|
|
1892
|
+
const d = byId.get(id)
|
|
1893
|
+
return {
|
|
1894
|
+
id,
|
|
1895
|
+
authorized: d ? !!d.authorized : (source === 'account-live'),
|
|
1896
|
+
credits: d && typeof d.credits === 'number' ? d.credits : null,
|
|
1897
|
+
creditLabel: (d && (d.creditLabel || d.label)) || '',
|
|
1898
|
+
label: (d && d.label) || id
|
|
1899
|
+
}
|
|
1900
|
+
})
|
|
1624
1901
|
if (a.authorizedOnly) list = list.filter((m) => m.authorized)
|
|
1625
1902
|
if (a.freeOnly) list = list.filter((m) => m.credits === 0)
|
|
1626
1903
|
return {
|
|
1627
1904
|
ok: true, backend, models: list, count: list.length,
|
|
1628
|
-
source:
|
|
1629
|
-
|
|
1905
|
+
source, note: (typeof note !== 'undefined' ? note : undefined),
|
|
1906
|
+
probedAt: Date.now()
|
|
1630
1907
|
}
|
|
1631
1908
|
}
|
|
1632
1909
|
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
// ── inlined from providers/codebuddy-schemas.mjs ──
|
|
1914
|
+
// providers/codebuddy-schemas.mjs — 三工具 JSON Schema(纯数据,零依赖)。
|
|
1915
|
+
//
|
|
1916
|
+
// 旧 preset/codebuddy-first verbatim(契约 §8 红线):字段名/枚举/描述一字不改。
|
|
1917
|
+
// 放这里是为了让 catalog 与主描述符都留在 600 行红线内(规则 §26)。
|
|
1918
|
+
// ⚠️ 注释里绝不写出口令标记字面量(build 全局替换会把注释劈开)。
|
|
1919
|
+
// ── 三工具 schema(旧 preset/codebuddy-first verbatim;契约 §8 红线)────────────
|
|
1920
|
+
// 纯数据,无依赖 —— 放这里是为了让主描述符留在 600 行红线内(规则 §26)。
|
|
1921
|
+
const RUN_PARAMS = {
|
|
1922
|
+
type: 'object',
|
|
1923
|
+
additionalProperties: false,
|
|
1924
|
+
required: ['prompt'],
|
|
1925
|
+
properties: {
|
|
1926
|
+
prompt: { type: 'string', description: 'The full task/instruction for codebuddy. Be complete and self-contained.' },
|
|
1927
|
+
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.' },
|
|
1928
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'auto follows DSH plan state; plan = no writes; accept-edits = allow edits. Default auto.' },
|
|
1929
|
+
model: { type: 'string', description: MODEL_DESCRIPTION },
|
|
1930
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
1931
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns (1-500, default unlimited).' },
|
|
1932
|
+
cwd: { type: 'string', description: 'Working directory for codebuddy. Defaults to the DSH workspace root.' },
|
|
1933
|
+
addDirs: { type: 'array', items: { type: 'string' }, description: 'Extra directories to add to codebuddy workspace.' },
|
|
1934
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
1935
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
const CONTINUE_PARAMS = {
|
|
1939
|
+
type: 'object',
|
|
1940
|
+
additionalProperties: false,
|
|
1941
|
+
required: ['prompt'],
|
|
1942
|
+
properties: {
|
|
1943
|
+
prompt: { type: 'string', description: 'Follow-up instruction for the ongoing codebuddy conversation.' },
|
|
1944
|
+
sessionId: { type: 'string', description: 'codebuddy session id to resume (from a prior codebuddy_run result).' },
|
|
1945
|
+
latest: { type: 'boolean', description: 'Continue the most recent codebuddy conversation instead of a specific id.' },
|
|
1946
|
+
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).' },
|
|
1947
|
+
mode: { type: 'string', enum: ['auto', 'plan', 'accept-edits'], description: 'Execution mode; default auto.' },
|
|
1948
|
+
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).' },
|
|
1949
|
+
effort: { type: 'string', enum: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], description: 'Optional reasoning effort.' },
|
|
1950
|
+
maxTurns: { type: 'integer', description: 'Optional max agentic turns.' },
|
|
1951
|
+
cwd: { type: 'string', description: "Working directory for codebuddy; when resuming, defaults to the resumed session's project directory." },
|
|
1952
|
+
timeoutSec: { type: 'integer', description: 'Run timeout seconds (10-3600, default 300); a DSH-side hang guard force-terminates at timeout+60s.' },
|
|
1953
|
+
background: { type: 'boolean', description: 'Run as a background job and return a jobId immediately.' }
|
|
1633
1954
|
}
|
|
1955
|
+
}
|
|
1956
|
+
const STATUS_PARAMS = { type: 'object', additionalProperties: false, required: [], properties: { cwd: { type: 'string', description: 'Optional: filter the snapshot to a single project (working directory).' } } }
|
|
1634
1957
|
|
|
1635
1958
|
// core/scheduling.mjs — CLI 调度(启用集合 + 调用优先级)单源纯逻辑。
|
|
1636
1959
|
//
|