@luchenglong/cli-bridge 0.0.0-stage → 0.2.3
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 +191 -0
- package/LICENSE +21 -0
- package/MCP-POLICY.md +53 -0
- package/PROVIDER-CONTRACT.md +235 -0
- package/README.en.md +98 -0
- package/README.md +151 -2
- package/assets/indicator-states.svg +62 -0
- package/bin/agy-quota.mjs +223 -0
- package/core/bridge-kit.mjs +549 -0
- package/core/scheduling.mjs +149 -0
- package/docs/ARCHITECTURE.md +117 -0
- package/docs/FALLBACK-AND-INDICATOR.md +93 -0
- package/docs/HANDOVER.md +226 -0
- package/docs/INSTALL.md +118 -0
- package/docs/MCP-POLICY.zh.md +49 -0
- package/docs/MIGRATION.md +43 -0
- package/docs/RELEASE-SOP.md +80 -0
- package/docs/history/agy/CHANGELOG.md +326 -0
- package/docs/history/codebuddy/CHANGELOG.md +668 -0
- package/docs/history/mimo/CHANGELOG.md +346 -0
- package/dynamic/agy.host.js +1237 -0
- package/dynamic/agy.host.template.mjs +140 -0
- package/dynamic/codebuddy.host.js +1787 -0
- package/dynamic/codebuddy.host.template.mjs +162 -0
- package/dynamic/mimo.host.js +1732 -0
- package/dynamic/mimo.host.template.mjs +169 -0
- package/dynamic/opencode.host.js +1443 -0
- package/dynamic/opencode.host.template.mjs +125 -0
- package/home-plugin/cli-bridge-indicator/cordis.patch.yml +31 -0
- package/home-plugin/cli-bridge-indicator/lib/client.js +606 -0
- package/home-plugin/cli-bridge-indicator/lib/index.mjs +420 -0
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +137 -0
- package/home-plugin/cli-bridge-indicator/lib/settings.mjs +300 -0
- package/mcp/agy-mcp-server.mjs +6 -0
- package/mcp/cli-bridge-mcp.mjs +185 -0
- package/mcp/codebuddy-mcp-server.mjs +6 -0
- package/mcp/mimo-mcp-server.mjs +6 -0
- package/mcp/providers/agy.mjs +254 -0
- package/mcp/providers/codebuddy.mjs +397 -0
- package/mcp/providers/mimo.mjs +290 -0
- package/mcp/providers/opencode.mjs +236 -0
- package/package.json +89 -4
- package/preset/agy-first/agent.cordis.yml +141 -0
- package/preset/agy-first/cli-bridge.mjs +169 -0
- package/preset/agy-first/plugin-list.mjs +26 -0
- package/preset/agy-first/preset.yml +3 -0
- package/preset/codebuddy-first/agent.cordis.yml +141 -0
- package/preset/codebuddy-first/cli-bridge.mjs +188 -0
- package/preset/codebuddy-first/plugin-list.mjs +20 -0
- package/preset/codebuddy-first/preset.yml +3 -0
- package/preset/mimo-first/agent.cordis.yml +143 -0
- package/preset/mimo-first/cli-bridge.mjs +236 -0
- package/preset/mimo-first/plugin-list.mjs +39 -0
- package/preset/mimo-first/preset.yml +3 -0
- package/preset/opencode-first/agent.cordis.yml +141 -0
- package/preset/opencode-first/cli-bridge.mjs +147 -0
- package/preset/opencode-first/plugin-list.mjs +26 -0
- package/preset/opencode-first/preset.yml +3 -0
- package/preset/standard-base.mjs +102 -0
- package/providers/agy.mjs +398 -0
- package/providers/codebuddy.mjs +926 -0
- package/providers/mimo.mjs +864 -0
- package/providers/opencode.mjs +619 -0
- package/providers/registry.mjs +39 -0
- package/scripts/build.mjs +235 -0
- package/scripts/deploy.mjs +61 -0
- package/scripts/verify.mjs +148 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// core/scheduling.mjs — CLI 调度(启用集合 + 调用优先级)单源纯逻辑。
|
|
2
|
+
//
|
|
3
|
+
// 语义(用户可见的两件事):
|
|
4
|
+
// • enabled = 勾选启用哪些 CLI(数量随之而定);未启用的一面不注册工具、不进提示词。
|
|
5
|
+
// • priority = 智能体调用 CLI 的建议顺序,数组顺序即优先级(首位最高)。
|
|
6
|
+
//
|
|
7
|
+
// 落盘位置:统一设置文件 {DSH_HOME}/cli-bridge-settings.json 的顶层 `_scheduling` 区。
|
|
8
|
+
// 下划线前缀 = 非 provider 分区(registry 的 PROVIDER_IDS 永远不含它,逐分区语义不受影响),
|
|
9
|
+
// 因此新代码写出的文件被旧包读取时整区被忽略 = 纯增量,向后兼容。
|
|
10
|
+
//
|
|
11
|
+
// 缺省语义(文件缺失/无该区):全部 provider 启用、按注册表顺序 —— 与调度特性上线前
|
|
12
|
+
// 的行为逐字一致(读不到配置永远不等于关掉所有桥)。
|
|
13
|
+
//
|
|
14
|
+
// 沙箱纪律:与 bridge-kit 同源约束 —— 不得 import;Node 内建一律经
|
|
15
|
+
// globalThis.process.getBuiltinModule + typeof 守卫(动态宿主沙箱可能没有 fs,
|
|
16
|
+
// 那种环境读不到文件 → 走缺省全开,绝不炸 apply)。
|
|
17
|
+
|
|
18
|
+
/** 设置文件里调度区的键名(下划线前缀避开 provider id 命名空间)。 */
|
|
19
|
+
export const SCHEDULING_SECTION = '_scheduling'
|
|
20
|
+
|
|
21
|
+
/** 人类可读的默认注册顺序(调用建议顺序);enabled 缺省 = 全部启用。 */
|
|
22
|
+
export function defaultPriority(ids) {
|
|
23
|
+
return (Array.isArray(ids) ? ids : []).slice()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 清洗任意来源的调度对象。
|
|
28
|
+
* @param {unknown} raw 文件/POST 里的原始值(可能是任何东西,全部容错)
|
|
29
|
+
* @param {string[]} ids 全量 provider id(顺序 = 缺省优先级)
|
|
30
|
+
* @param {(id: string) => string} [labelOf] 展示名(仅用于诊断文本,不参与判定)
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeScheduling(raw, ids, labelOf) {
|
|
33
|
+
const all = Array.isArray(ids) ? ids.slice() : []
|
|
34
|
+
const label = typeof labelOf === 'function' ? labelOf : ((id) => String(id))
|
|
35
|
+
const enabled = all.slice()
|
|
36
|
+
let priority = all.slice()
|
|
37
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
38
|
+
if (Array.isArray(raw.enabled)) {
|
|
39
|
+
const seen = Object.create(null)
|
|
40
|
+
const picked = []
|
|
41
|
+
for (const x of raw.enabled) {
|
|
42
|
+
const id = String(x == null ? '' : x)
|
|
43
|
+
if (!id || all.indexOf(id) < 0 || seen[id]) continue
|
|
44
|
+
seen[id] = true; picked.push(id)
|
|
45
|
+
}
|
|
46
|
+
// 空数组 = 用户真的想把全部关掉(而不是「没配」),保留该语义;
|
|
47
|
+
// 非空但过滤后为空 = 写的全是未知 id(坏值)→ 回落全开(fail-open,
|
|
48
|
+
// 绝不让一次手滑/陈旧配置把所有桥关掉)。
|
|
49
|
+
if (raw.enabled.length === 0 || picked.length > 0) {
|
|
50
|
+
enabled.length = 0
|
|
51
|
+
Array.prototype.push.apply(enabled, picked)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(raw.priority)) {
|
|
55
|
+
const seen2 = Object.create(null)
|
|
56
|
+
const ordered = []
|
|
57
|
+
for (const x of raw.priority) {
|
|
58
|
+
const id = String(x == null ? '' : x)
|
|
59
|
+
if (!id || all.indexOf(id) < 0 || seen2[id]) continue
|
|
60
|
+
seen2[id] = true; ordered.push(id)
|
|
61
|
+
}
|
|
62
|
+
for (const id of all) if (!seen2[id]) ordered.push(id) // 漏写的排在末尾
|
|
63
|
+
if (ordered.length) priority = ordered
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const order = priority.filter((id) => enabled.indexOf(id) >= 0)
|
|
67
|
+
for (const id of enabled) if (order.indexOf(id) < 0) order.push(id)
|
|
68
|
+
return { enabled, priority, order }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 展示文本:「CB → MIMO」或「全部启用 · CB → AGY → MIMO」。 */
|
|
72
|
+
export function describeScheduling(s, labelOf) {
|
|
73
|
+
const st = s || {}
|
|
74
|
+
const label = typeof labelOf === 'function' ? labelOf : ((id) => String(id))
|
|
75
|
+
const order = Array.isArray(st.order) ? st.order : []
|
|
76
|
+
const names = order.map(label).join(' → ')
|
|
77
|
+
return names ? names : '(全部禁用)'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── 读盘(四形态共用;无 fs 时安全返回 null)────────────────────────────────────
|
|
81
|
+
// 名字带 sched 前缀:本文件会与 bridge-kit / provider 描述符【文本内联进同一个动态
|
|
82
|
+
// 沙箱脚本作用域】(providers/codebuddy.mjs 已有 builtinModule),撞名即隐患。
|
|
83
|
+
function schedBuiltinModule(name) {
|
|
84
|
+
try {
|
|
85
|
+
const proc = globalThis.process
|
|
86
|
+
return (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule(name) : null
|
|
87
|
+
} catch (e) { return null }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function schedulingSettingsPath(env) {
|
|
91
|
+
const e = env || (globalThis.process && globalThis.process.env) || {}
|
|
92
|
+
try {
|
|
93
|
+
if (e.CLI_BRIDGE_SETTINGS_FILE) return String(e.CLI_BRIDGE_SETTINGS_FILE)
|
|
94
|
+
const nodePath = schedBuiltinModule('node:path')
|
|
95
|
+
if (nodePath && e.DSH_HOME) return nodePath.join(String(e.DSH_HOME), 'cli-bridge-settings.json')
|
|
96
|
+
} catch (e2) { }
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 生效调度:文件 _scheduling > 缺省全开。
|
|
102
|
+
* env/argv CLI_BRIDGE_PROVIDER(单面钉住,旧薄封装兼容语义)存在时视为最高优先 ——
|
|
103
|
+
* 它代表「这个 MCP 进程只为这一家而启动」(部署者的显式意志,比面板勾选更具体、
|
|
104
|
+
* 更靠近进程生命周期),调度不再削减它。
|
|
105
|
+
* 宿主形态(preset / dynamic 桥接插件)应传 ignorePin:钉住只是 MCP 进程的启动参数,
|
|
106
|
+
* 不该让全局 env 反过来把用户会话里的其他桥关掉。
|
|
107
|
+
* @param {string[]} ids 全量 provider id
|
|
108
|
+
* @param {{ env?: Record<string,string>, readFileSync?: Function, path?: string|null, ignorePin?: boolean }} [io]
|
|
109
|
+
*/
|
|
110
|
+
export function resolveScheduling(ids, io) {
|
|
111
|
+
const o = io || {}
|
|
112
|
+
const env = o.env || (globalThis.process && globalThis.process.env) || {}
|
|
113
|
+
let raw = null
|
|
114
|
+
try {
|
|
115
|
+
const p = ('path' in o) ? o.path : schedulingSettingsPath(env)
|
|
116
|
+
const nodeFs = o.readFileSync ? { readFileSync: o.readFileSync } : schedBuiltinModule('node:fs')
|
|
117
|
+
if (p && nodeFs && typeof nodeFs.readFileSync === 'function') {
|
|
118
|
+
raw = JSON.parse(nodeFs.readFileSync(p, 'utf8'))
|
|
119
|
+
}
|
|
120
|
+
} catch (e) { raw = null }
|
|
121
|
+
const file = normalizeScheduling(raw && typeof raw === 'object' ? raw[SCHEDULING_SECTION] : null, ids)
|
|
122
|
+
const pinned = o.ignorePin ? '' : String(env.CLI_BRIDGE_PROVIDER || '').trim()
|
|
123
|
+
if (pinned && ids.indexOf(pinned) >= 0) {
|
|
124
|
+
return Object.assign(file, {
|
|
125
|
+
enabled: [pinned],
|
|
126
|
+
order: [pinned],
|
|
127
|
+
pinned: pinned,
|
|
128
|
+
source: raw ? 'env:CLI_BRIDGE_PROVIDER+file' : 'env:CLI_BRIDGE_PROVIDER'
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
return Object.assign(file, { pinned: '', source: raw ? 'file' : 'default' })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 桥接面门禁(preset / dynamic 的 apply() 首行用):本 provider 是否被调度启用。
|
|
136
|
+
* fail-open —— 任何异常(无 fs、文件损坏、沙箱限制)都返回 true:读不到配置永远
|
|
137
|
+
* 不等于关掉桥(否则一次文件损坏就把三条 CLI 全灭了)。ignorePin 固定 true:
|
|
138
|
+
* 钉住语义属于 MCP 独立进程,宿主形态的全局 env 不该反向影响用户会话。
|
|
139
|
+
*/
|
|
140
|
+
export function isBridgeEnabled(selfId, ids, io) {
|
|
141
|
+
try {
|
|
142
|
+
const list = Array.isArray(ids) ? ids : []
|
|
143
|
+
// 全量清单本身不可得(空/不含自己)= 无门禁语义,fail-open。
|
|
144
|
+
if (!list.length || list.indexOf(String(selfId)) < 0) return true
|
|
145
|
+
const o = Object.assign({ ignorePin: true }, io || {})
|
|
146
|
+
return resolveScheduling(list, o).enabled.indexOf(String(selfId)) >= 0
|
|
147
|
+
} catch (e) { return true }
|
|
148
|
+
}
|
|
149
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 架构(CLI Bridge)
|
|
2
|
+
|
|
3
|
+
三个旧桥接包(agy / codebuddy / mimo)深度重构为一个包。目标不是「拼盘」,而是把三处
|
|
4
|
+
几乎相同的骨架收敛成一份参数化实现,每家只提供一个描述符(descriptor)。
|
|
5
|
+
|
|
6
|
+
## 1. 三形态共享一个内核
|
|
7
|
+
|
|
8
|
+
同一个 provider 描述符驱动三种运行形态:
|
|
9
|
+
|
|
10
|
+
| 形态 | 运行环境 | 宿主能力 | 状态出口 |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| preset(`preset/<dir>/cli-bridge.mjs`) | 宿主进程内真 ESM | 真 `process`/node:*/ctx.* | `ctx.emit('<id>/status'\|'<id>/mode')` |
|
|
13
|
+
| dynamic(`dynamic/<id>.host.js`) | Cordis 沙箱 `new Function` | 只有注入的 `harness.*`/ctx,**禁止 import/process 裸用** | `cliBridgeCollector.mergeSnapshot(id, snap)` |
|
|
14
|
+
| MCP(`mcp/cli-bridge-mcp.mjs`) | 独立 stdio 进程 | node:* 全量 | 原子快照文件 `{DSH_HOME}/cli-bridge-mcp-<id>.json` |
|
|
15
|
+
|
|
16
|
+
约束的根因:沙箱形态(dynamic)里,`providers/<id>.mjs` 与 `core/bridge-kit.mjs` 被
|
|
17
|
+
`scripts/build.mjs` **文本内联**(`stripModuleSyntax`)。因此这两个文件:零 import、
|
|
18
|
+
零 `import.meta` 字面量、`process/fs` 一律 deps 注入或 typeof 守卫。
|
|
19
|
+
preset/MCP 形态是真实模块,可以用 node:* 与相对 import(禁止 `@deepseek-ai/*`,
|
|
20
|
+
历史论证见本地 preset 解析根问题)。
|
|
21
|
+
|
|
22
|
+
## 2. `core/bridge-kit.mjs` —— 通用骨架(与 provider 无关)
|
|
23
|
+
|
|
24
|
+
- **createStatusEngine({provider, publish})**:按项目(cwd)分桶的活动模型 ——
|
|
25
|
+
`begin/foldEvent/end`,每项目 `current`(此刻步骤)+ `trail`(轨迹环,MAX_TRAIL=12)+
|
|
26
|
+
累计 `runs/totalTokens`;会话表(MAX_SESSIONS=256)记录 `sessionId → {cwd, backend}`
|
|
27
|
+
供续接路由(`resolveTarget`:显式 cwd > 会话归属 > 回退 cwd)。全局状态派生:
|
|
28
|
+
running>0 → running;否则 fallback(回退激活)/ lastStatus 成功失败 / idle。
|
|
29
|
+
- **createRunner(o) → coreExecute(args, exec)**:完整编排管线——
|
|
30
|
+
`preflight`(可 abort)→ `resolveExe`(抛错→unavailableStatus)→ `buildArgv` →
|
|
31
|
+
`delegates(backend)` 判定是否交给 provider.execute(非 spawn 路径,如 mimo 桌面)→
|
|
32
|
+
`jobs.start`(background)或前台循环:`HUNG_TIMEOUT` 守卫(timeout+60s 强杀)、
|
|
33
|
+
`isTransientCliError` 静默重试一次、`isLimited`(provider 可覆盖)触发 `askFallback`
|
|
34
|
+
三选一(回退/重试/取消),一切状态变化实时 publish。
|
|
35
|
+
- **纯函数**:`projectName/clampInt/shortLabel/summarizeArgs/endpointHost/LIMIT_RE`、
|
|
36
|
+
NDJSON 半行安全 `createLineStream`、展示层 `renderResult/renderStatus`、
|
|
37
|
+
MCP 载荷 `buildMcpBridgePayload/normalizeMcpBridge`(stale=90s)。
|
|
38
|
+
|
|
39
|
+
provider 侧全部差异 = 一个 descriptor(见 `PROVIDER-CONTRACT.md` §1)。
|
|
40
|
+
|
|
41
|
+
## 3. 家级指示灯(`home-plugin/cli-bridge-indicator/`)
|
|
42
|
+
|
|
43
|
+
随 DSH 启动的 bundle 插件(`dsh.bundle.patch` 一行插入;`dsh.client.platform:"web"`)。
|
|
44
|
+
内部分层,全部可单测(`createIndicatorState` 是纯状态机,注入 `listPresetSessions/
|
|
45
|
+
readMcpFile/isSuccessStatus/now` 即可离线测):
|
|
46
|
+
|
|
47
|
+
- **index.mjs**:每 provider 一个桶 `{projects, sessionLeases, presetActiveUntil,
|
|
48
|
+
lastModeAt}`。事件源三路合并:① preset 桥接 `ctx.emit('<id>/status')`(同进程);
|
|
49
|
+
② dynamic 宿主 `cliBridgeCollector.mergeSnapshot`(家级收集器,旧包单家收集器名
|
|
50
|
+
`agyCollector/…` 保留别名);③ MCP 快照文件(每次读取实时合并,过期只清零 running,
|
|
51
|
+
绝不清掉活动细节)。合并规则:state 是派生字段——外部快照的 state 只在 running 时被
|
|
52
|
+
信任,其余本地重算(`isSuccessStatus` 来自 descriptor,mimo 的 ACCEPTED 等语义各家自定)。
|
|
53
|
+
- **租约(lease)**:preset 桥接每 30s 心跳 `ctx.emit('<id>/mode',{active,sessionId})`,
|
|
54
|
+
TTL 75s(两跳 + 5s 宽限)。`sessionId` 有值 → 会话级租约;缺省 → 全局租约(client
|
|
55
|
+
无法判定时的保守亮)。关会话/停桥接后租约过期,灯自动灭(旧 mimo 1.2.9「灯常亮」
|
|
56
|
+
事故类的对偶防线:宁可用租约到期灭,不可无来源亮)。
|
|
57
|
+
- **preset 双通道判定**:宿主侧 `agents.list() × agentPresets.composedPreset()` 枚举
|
|
58
|
+
「哪些 DSH 会话正跑在桥接预设下」是权威;client 侧 `useSessions` 投影只是兜底。
|
|
59
|
+
- **路由**:`GET /cli-bridge/status`(聚合三桶)、`GET/POST /cli-bridge/settings`、
|
|
60
|
+
`GET /agy-indicator/quota`(5 分钟缓存)。旧包路径 `/agy-indicator/status` 等全部
|
|
61
|
+
保留为单桶别名(旧前端/脚本兼容)。
|
|
62
|
+
- **preset-declarations.mjs(Form-A 自注册)**:indicator `ctx.inject(['agentPresets'])`
|
|
63
|
+
→ 对三家各 `agentPresets.register(definition)`。定义体直接 import 各
|
|
64
|
+
`preset/<dir>/plugin-list.mjs`(与 legacy YAML 生成同一事实来源)。`{__jsExpr}`
|
|
65
|
+
字面量**原样透传**给注册器(loader 原生识别;任何 JSON 序列化往返会丢函数——绝不
|
|
66
|
+
编译成函数,这是旧 gen-preset-decl 时代踩过的位置)。旧 DSH 上对不存在的服务
|
|
67
|
+
`inject` 只会 pending,无害;因此该形态跨版本一步到位。
|
|
68
|
+
- **client.js(红线 4)**:`window.__ModuleLoader__.load({ id: "@luchenglong/cli-bridge" })`
|
|
69
|
+
—— id 必须逐字等于包名,不一致 = 整个 client combo 抛错 = 白屏进安全模式(历史两次
|
|
70
|
+
事故)。`scripts/verify.mjs` + `tests/keystone/client-id.test.mjs` 双闸门。UI:标题栏
|
|
71
|
+
一盏聚合 pill + 悬停/点击分区面板 + 设置页 meta 驱动卡片(provider 声明 fields,
|
|
72
|
+
前端不认识任何具体字段名)。
|
|
73
|
+
|
|
74
|
+
## 4. 统一设置(`lib/settings.mjs`)
|
|
75
|
+
|
|
76
|
+
单一文件 `{DSH_HOME}/cli-bridge-settings.json`,按 provider 分区。provider 只声明纯逻辑
|
|
77
|
+
(keys/secretKeys/defaults/normalize/meta/diagnose,§3),落盘/掩码/清洗全在 keystone:
|
|
78
|
+
GET 永不回传明文 secret(掩码 `••••尾4`);POST 空=保持、`<key>Clear:true`=清除;
|
|
79
|
+
写用 tmp→rename 原子替换。**旧包设置文件一次性字段级迁移**(codebuddy 扁平文件 /
|
|
80
|
+
mimo `{APPDATA}/mimo-first-bridge/settings.json` 的 backend→preferredBackend 映射):
|
|
81
|
+
新分区从未写过该键才回退旧值,写过即新值说了算;永不写旧文件。
|
|
82
|
+
|
|
83
|
+
## 5. MCP 面(`mcp/cli-bridge-mcp.mjs` + `mcp/providers/<id>.mjs`)
|
|
84
|
+
|
|
85
|
+
一个 stdio JSON-RPC server(LSP 式换行分帧),initialize/tools/call 由 keystone 框架
|
|
86
|
+
实现;每家一个纯面模块(`tools(env)/call(name,args,io)/check(env)`,§4),`io` 注入
|
|
87
|
+
fs/path/homeDir/env/log,面模块不猜路径。`--provider=<id>` 过滤单家;
|
|
88
|
+
`mcp/<id>-mcp-server.mjs` 是薄封装(旧配置只换路径)。MCP 进程没有 ctx.emit,publish
|
|
89
|
+
改写快照文件;家级灯实时合并(见 §3-③)。
|
|
90
|
+
|
|
91
|
+
## 6. 构建与闸门
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
plugin-list.mjs(每家的唯一插件清单源)
|
|
95
|
+
├─ scripts/build.mjs → preset/<dir>/agent.cordis.yml + preset.yml(legacy 目录形态)
|
|
96
|
+
└─ preset-declarations.mjs 直接 import(新声明制形态,同一份源)
|
|
97
|
+
dynamic/<id>.host.template.mjs + core/bridge-kit.mjs + providers/<id>.mjs
|
|
98
|
+
└─ scripts/build.mjs(stripModuleSyntax + 双占位文本内联)→ dynamic/<id>.host.js
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
生成物提交入库(fresh clone 可用),`--check` 模式比对漂移 → CI/prepack 红。
|
|
102
|
+
三道闸门:`build --check`(漂移)、`verify.mjs`(红线/版本锁/描述符完整度)、
|
|
103
|
+
`node --test` + `mcp --check`(行为)。CI 矩阵 node 18/20/22。
|
|
104
|
+
|
|
105
|
+
## 7. 为什么这些选择(旧事故对照)
|
|
106
|
+
|
|
107
|
+
- **裸 specifier 桥接行**(`@luchenglong/cli-bridge/preset-<id>`):preset mount 的
|
|
108
|
+
baseUrl 不指向可预期的包内目录,`./preset/...` 相对行 import 永久挂起 → auditRows
|
|
109
|
+
never started(agy 1.7.1 主根因之一)。exports 子路径 + Node 解析 = 机器无关。
|
|
110
|
+
- **声明制自注册**:0.1.6+ 起 `$DSH_HOME/.agent-presets/` 目录无人再读(官方 skill 明言),
|
|
111
|
+
preset 必须以 `agentPresets.register` / bundle 声明行注册 —— Form-A 自注册跨版本可用,
|
|
112
|
+
legacy 目录形态仅作旧版兜底。
|
|
113
|
+
- **无 dsh peer 依赖**:兼容闸门只校验 `@deepseek-ai/dsh*` peers,桥接包不声明 =
|
|
114
|
+
天然通过;声明了反而武装闸门(版本漂移即 500)。
|
|
115
|
+
- **workflow 行单源**:上游把 dsh-workflow-worker-thread 改名 dsh-workflow-ptc 曾让
|
|
116
|
+
「任一 enabled 行 import 失败 → 整个 preset 拒挂」(agy 1.7.1)。现在清单由
|
|
117
|
+
plugin-list.mjs 一份源生成,行名跟 standard-base 常量走,漂移只会出现在一处修一处。
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 回退机制与状态灯(CLI Bridge)
|
|
2
|
+
|
|
3
|
+
三家桥接共用的两项能力在 `core/bridge-kit.mjs` 里收敛成了**一份参数化实现**:
|
|
4
|
+
限流/网络失败时的回退弹窗,与实时聚合状态灯。provider 只提供差异化判据
|
|
5
|
+
(`isLimited` / `failureHint` / `isSuccessStatus`),骨架不再各抄一份。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、回退机制(弹窗确认)
|
|
10
|
+
|
|
11
|
+
### 触发条件
|
|
12
|
+
|
|
13
|
+
`<id>_run` / `<id>_continue` 前台执行后,若结果 `ok === false` 且被判定为「疑似流量
|
|
14
|
+
受限 / 网络不通」,触发回退弹窗。判定优先用 provider 的 `isLimited(res)` 覆盖
|
|
15
|
+
(例如 mimo 需要认「free API service has ended / 登录 / 第三方」这类旧文案),
|
|
16
|
+
否则用 kit 的通用 `LIMIT_RE`(stderr + response + status 拼接命中任一):
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
rate limit / ratelimit / 429 / too many / quota / exceed /
|
|
20
|
+
network / offline / ENETUNREACH / ECONNREFUSED / ECONNRESET /
|
|
21
|
+
ETIMEDOUT / EAI_AGAIN / ENOTFOUND / timeout / timed out /
|
|
22
|
+
unavailable / 503 / 502 / 500 / connection / proxy / socket /
|
|
23
|
+
tls / ssl / dns / 网络 / 超时 / 限流 / 流量 / 受限 / 配额 / 连接 / 断开
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
注意 kit 的两处刻意豁免:`AUTH_REQUIRED` 不弹(登录问题不是限流,弹窗只会误导);
|
|
27
|
+
endpoint-mismatch 类 401 不弹(配置错误,重试无意义)。瞬时错误
|
|
28
|
+
(`isTransientCliError`)先静默自动重试一次,仍失败才走弹窗。
|
|
29
|
+
|
|
30
|
+
### 弹窗内容
|
|
31
|
+
|
|
32
|
+
经 DSH `userQuestions.ask()` 单选(文案 provider.label 化,三家同构):
|
|
33
|
+
|
|
34
|
+
> **AGY 受限** —— agy 调用失败(疑似流量受限/网络不通,状态=`<status>`)。
|
|
35
|
+
> 是否改用 DSH 本地 API 配置继续?
|
|
36
|
+
|
|
37
|
+
| 选项 | 行为 |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| **使用 DSH 本地 API 配置(回退)** | 返回 `{ ok:false, fallback:true, status:'FALLBACK_TO_DSH', reason:<原status> }`;模型据此改用原生工具/本地模型完成任务,不再调用该 CLI。 |
|
|
40
|
+
| **重试 <id> 一次** | 立即再执行一次(累计最多 2 次)。 |
|
|
41
|
+
| **不回退(返回错误)** | 原样返回错误结果,由模型决定后续。 |
|
|
42
|
+
|
|
43
|
+
### 防阻塞 / 防循环
|
|
44
|
+
|
|
45
|
+
- **无真人应答者不弹窗**:`ask()` 抛 `CALLER_NOT_LIVE` / `DELEGATED_CALLER` 时按
|
|
46
|
+
`'error'` 处理(子代理永不被弹窗卡死);缺 `userQuestions` 服务同理。
|
|
47
|
+
- **最多 2 次尝试**:`attempt >= 2` 即停。
|
|
48
|
+
- **后台任务不弹窗**:`background:true` 完成时真人上下文未必还在,后台失败只在结果里
|
|
49
|
+
提示「前台重跑才会被询问」。
|
|
50
|
+
- **HUNG_TIMEOUT 守卫**:`timeoutSec + 60s` 后强杀并按受限类处理(长构建要调大
|
|
51
|
+
`timeoutSec`)。
|
|
52
|
+
- **不让 CLI 回调宿主**:policy 段明文禁止,杜绝环路。
|
|
53
|
+
|
|
54
|
+
## 二、聚合状态灯(一盏 pill,三家分区)
|
|
55
|
+
|
|
56
|
+
`home-plugin/cli-bridge-indicator` 随 DSH 启动(bundle patch 一行),每 provider
|
|
57
|
+
一个桶,合并三路数据源:
|
|
58
|
+
|
|
59
|
+
1. **preset 桥接事件** —— `ctx.emit('<id>/status', { snapshot })`(同进程)+
|
|
60
|
+
`<id>/mode` 心跳租约;
|
|
61
|
+
2. **dynamic 沙箱** —— `cliBridgeCollector.mergeSnapshot(id, snap)`(旧单家收集器
|
|
62
|
+
服务名保留别名);
|
|
63
|
+
3. **MCP 子进程** —— `{DSH_HOME}/cli-bridge-mcp-<id>.json` 原子快照,读取时实时合并,
|
|
64
|
+
超过 90s 视为停更(只清零 running,不清活动细节)。旧包快照文件
|
|
65
|
+
(`codebuddy-indicator-mcp.json`、`plugins/mimo-indicator/mcp-live.json`)兼容读取。
|
|
66
|
+
|
|
67
|
+
### 租约(preset 优先态)
|
|
68
|
+
|
|
69
|
+
桥接 preset 被某会话使用时发 `<id>/mode` 心跳(30s 一跳,TTL 75s = 两跳+宽限):
|
|
70
|
+
|
|
71
|
+
- payload 带 `sessionId` → **会话级租约**:只有该会话显示「优先」高亮;
|
|
72
|
+
- 缺省 → 全局租约(client 侧无法判定时的保守亮);
|
|
73
|
+
- 会话关闭/桥接停用 → 租约自然过期,高亮自动灭。宿主侧 `listPresetSessions`
|
|
74
|
+
(agents × composedPreset)是权威枚举,client `useSessions` 投影只是兜底 ——
|
|
75
|
+
「宁可用租约到期灭,不可无来源亮」(mimo 1.2.9 灯常亮事故的对偶防线)。
|
|
76
|
+
|
|
77
|
+
### 状态语义
|
|
78
|
+
|
|
79
|
+
| 色 | state | 含义 |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| 绿 | ok / idle-ready | 就绪 / 上次成功 |
|
|
82
|
+
| 蓝 | running | 正在派发(悬停看当前步骤) |
|
|
83
|
+
| 红 | failed | 上次失败(灰非红:CLI 不可用→灰) |
|
|
84
|
+
| 灰 | unavailable | CLI 未安装 / 找不到 exe |
|
|
85
|
+
| 橙 | fallback | 用户已选择回退 DSH 本地 |
|
|
86
|
+
|
|
87
|
+
`state` 是派生字段:外部快照的 state 只在 running 时被信任,其余按 provider 的
|
|
88
|
+
`isSuccessStatus` 本地重算(引擎 globalStatus 只认 'SUCCESS',mimo 的 ACCEPTED 等
|
|
89
|
+
变体在描述符层归一 —— indicator 测试锁定此行为)。
|
|
90
|
+
|
|
91
|
+
点击 pill 展开按 provider、按项目的面板:当前步骤 + trail(12 条环)、
|
|
92
|
+
累计 runs / totalTokens、最近完成结果;`GET /cli-bridge/status` 返回同数据的 JSON
|
|
93
|
+
(旧单家路由 `/agy-indicator/status` 等保留为别名)。
|
package/docs/HANDOVER.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# HANDOVER — @luchenglong/cli-bridge 交接文档
|
|
2
|
+
|
|
3
|
+
> 面向接手这个仓库/这台机器的下一个人(或下一个 Agent)。
|
|
4
|
+
> 目标:**不读完所有历史也能安全改动、发布、排障**。历史事故与红线集中在 §5 与 §10。
|
|
5
|
+
> 最近一次更新:2026-10-15(0.2.2 首发 npm + 旧包 deprecated + 旧仓归档之后)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 这是什么
|
|
10
|
+
|
|
11
|
+
`@luchenglong/cli-bridge` 把**本机三个编码 CLI**(agy / CodeBuddy / MiMo)以同一套 provider
|
|
12
|
+
描述符接入 DSH,并有四种落地形态(preset / 动态宿主 / 家级宿主插件+指示灯 / MCP stdio server)。
|
|
13
|
+
它由三个旧包(`agy-first-bridge` / `codebuddy-first-bridge` / `mimo-first-bridge`)合并而来,
|
|
14
|
+
旧包的工具名、预设 id、策略文案**逐字保留**(兼容承诺,见 §5)。
|
|
15
|
+
|
|
16
|
+
## 2. 当前状态快照
|
|
17
|
+
|
|
18
|
+
| 项 | 值 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 版本 | **0.2.2**(三处版本锁:`package.json` / `mcp/cli-bridge-mcp.mjs` 的 `SERVER_VERSION` / `CHANGELOG.md` 顶部;`verify.mjs` 机器比对) |
|
|
21
|
+
| npm | `@luchenglong/cli-bridge@0.2.2`(`latest`;2026-10-14 首次发布,shasum `362d506e0b85dd7014bb8b3fc1100bf3a6d0d1d5`,58 files / 320.1 kB) |
|
|
22
|
+
| GitHub | `new-256/cli-bridge`,`main` @ `520194c`;tag `v0.1.0` / `v0.2.1` / `v0.2.2` |
|
|
23
|
+
| 旧包 | 三个都已在 npm 标 **deprecated**(提示迁移到统一包):`agy-first-bridge@1.7.1`、`codebuddy-first-bridge@1.6.2`、`mimo-first-bridge@1.2.9` |
|
|
24
|
+
| 旧仓 | `new-256/agy-first-bridge`、`new-256/mimo-first-bridge`:README 顶部已加迁移横幅且 **archived(只读)**;`codebuddy-first-bridge` 在 GitHub 上不存在 |
|
|
25
|
+
| 测试 | **271 pass / 0 fail**(28 个 `*.test.mjs`),四闸门 `npm run check` 全绿 |
|
|
26
|
+
| 本机运行时 | DSH Desktop,宿主 **2026-10-14 22:35 重启后加载 0.2.2**;`dsh-home/profiles/web` 以 `file:` 依赖 + pnpm 快照部署 |
|
|
27
|
+
|
|
28
|
+
**交接时点唯一未完成项**:家级补丁里四条 MCP 行刚被恢复,**需重启 DSH 才生效**(§7.3、§11)。
|
|
29
|
+
|
|
30
|
+
## 3. 五分钟上手
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
cd "C:\Users\lcl\Desktop\DSH插件开发\CLI Bridge"
|
|
34
|
+
|
|
35
|
+
npm run check # 四闸门:build --check / verify / node --test / mcp --check
|
|
36
|
+
node mcp/cli-bridge-mcp.mjs --check # MCP 自检(含 scheduling 快照)
|
|
37
|
+
node scripts/build.mjs # 改了模板/providers 后必须重跑,生成物要进同一个 commit
|
|
38
|
+
|
|
39
|
+
# 活体探针(DSH 运行中;$env:DSH_WEB_URL 是当前 GUI 地址,端口每次启动可能变)
|
|
40
|
+
Invoke-WebRequest "$env:DSH_WEB_URL/cli-bridge/status" -UseBasicParsing | Select-Object -Expand Content
|
|
41
|
+
Invoke-WebRequest "$env:DSH_WEB_URL/cli-bridge/settings" -UseBasicParsing | Select-Object -Expand Content
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
设置面板:DSH「设置 → CLI Bridge 桥接」→ 横向标签 `总设置 | AGY | CB | MIMO`。
|
|
45
|
+
「总设置」= 调度(勾选启用 + `↑↓` 优先级),其余 = 各 CLI 偏好与诊断。
|
|
46
|
+
|
|
47
|
+
## 4. 代码地图(改哪里)
|
|
48
|
+
|
|
49
|
+
| 路径 | 职责 | 谁维护 |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `providers/registry.mjs` | **唯一 id 轴** `PROVIDER_IDS`;新 provider 第一站 | keystone(Lead 单作者) |
|
|
52
|
+
| `providers/<id>.mjs` | 描述符:工具/policy/settings meta/mcp/diagnose/回退 | 每 provider 作者 |
|
|
53
|
+
| `core/bridge-kit.mjs` | 共享内核:状态引擎、runner、限流、回退弹窗、MCP payload(**零 import**) | keystone |
|
|
54
|
+
| `core/scheduling.mjs` | 调度单源(无 import → 可整段内联进沙箱) | keystone |
|
|
55
|
+
| `home-plugin/cli-bridge-indicator/lib/index.mjs` | 宿主半:状态机 + HTTP 路由 + 收集器 + 门禁 | keystone |
|
|
56
|
+
| `…/lib/settings.mjs` | 设置读写/掩码/旧文件迁移/`_scheduling` 区 | keystone |
|
|
57
|
+
| `…/lib/preset-declarations.mjs` | 声明制注册三个 preset(恒注册,顺序按优先级) | keystone |
|
|
58
|
+
| `…/lib/client.js` | 浏览器半:pill / 弹窗 / 设置标签页(**红线:load id ≡ 包名**) | keystone |
|
|
59
|
+
| `preset/<dir>/` | preset 形态(`plugin-list.mjs` 是唯一权威源 + 生成物 `*.yml`) | 生成物入库 |
|
|
60
|
+
| `dynamic/<id>.host.{js,template.mjs}` | 动态沙箱宿主(模板 → 生成物) | 生成物入库 |
|
|
61
|
+
| `mcp/cli-bridge-mcp.mjs` + `mcp/providers/<id>.mjs` | 统一 MCP server(一个 server 暴露三家) | keystone |
|
|
62
|
+
| `mcp/{agy,codebuddy,mimo}-mcp-server.mjs` | 旧包兼容入口(等价 `--provider=<id>`) | keystone |
|
|
63
|
+
| `scripts/build.mjs` / `verify.mjs` / `deploy.mjs` | 生成 / 发布红线 / 本地暂存部署 | keystone |
|
|
64
|
+
| `deploy/legacy-mcp/` | **家级 `bin\` 服务器材料的权威副本**(`bin\` 不入 git) | keystone |
|
|
65
|
+
| `tests/keystone/` | 红线行为测试(client 面、状态机、路由、门禁、legacy MCP) | keystone |
|
|
66
|
+
| `tests/{agy,codebuddy,mimo}/`、`tests/core/` | 各 provider 与内核回归 | provider 作者 |
|
|
67
|
+
|
|
68
|
+
## 5. 不可动的契约(红线)
|
|
69
|
+
|
|
70
|
+
1. **client 注册 id ≡ 包名**:`lib/client.js` 的 `window.__ModuleLoader__.load({ id: "@luchenglong/cli-bridge" })`
|
|
71
|
+
逐字不变。不等 → client combo 加载失败 → **整屏插件页崩溃**(agy v1.6.1 / codebuddy v1.1.8 事故)。
|
|
72
|
+
`verify.mjs` + `tests/keystone/client-id.test.mjs` + `client-ui-surface.test.mjs` 三重机器锁。
|
|
73
|
+
2. **对外语义 verbatim**:工具名 `<id>_run/_continue/_status`、入参 schema、preset id
|
|
74
|
+
(`cordis-agy` / `codebuddy-first` / `cordis-mimo`)、POLICY 文案(如 codebuddy `POLICY_TEXT` 6970B)。
|
|
75
|
+
要改 = 大版本 + `docs/MIGRATION.md` 补条目。
|
|
76
|
+
3. **模板占位符各恰一次**:`/*__KIT__*/`、`/*__PROVIDER__*/`、`/*__SCHED__*/`;生成物
|
|
77
|
+
(`dynamic/*.host.js`、`preset/*/agent.cordis.yml`、`preset.yml`)**必须入库**,`build --check` 漂移即红。
|
|
78
|
+
4. **单一 id 轴**:任何地方需要 id 列表都从 `providers/registry.mjs` 派生(`build.mjs`、
|
|
79
|
+
`preset-declarations`、client 的 `providerMetas()` 都已改为派生)。禁止第二处硬编码三家 id。
|
|
80
|
+
5. **零运行时依赖**,且**禁止新增 `@deepseek-ai/dsh*` peer 依赖**(`verify.mjs` + 测试双锁)。
|
|
81
|
+
6. **家级 `bin\` 三个服务器与 `deploy/legacy-mcp/` 副本逐字节一致**(`tests/keystone/legacy-mcp-gate.test.mjs`)。
|
|
82
|
+
7. **失败要响亮(开发期)**:不写静默降级/兜底;门禁只做「注册/不注册」,不改工具语义。
|
|
83
|
+
|
|
84
|
+
## 6. 调度(0.2.0+)实现要点
|
|
85
|
+
|
|
86
|
+
用户目标:**选择启用哪些 CLI(数量)+ 排定调用优先级**,且加第 N 家 CLI 时零改动。
|
|
87
|
+
|
|
88
|
+
- **落盘**:`{DSH_HOME}/cli-bridge-settings.json` 顶层 `_scheduling = { enabled, priority }`
|
|
89
|
+
(下划线前缀 = 非 provider 分区,旧代码整区忽略 → 纯增量兼容)。
|
|
90
|
+
- **语义**:`enabled` 是集合(数组顺序无意义);`priority` 的**数组顺序即优先级**;
|
|
91
|
+
派生 `order = priority ∩ enabled`。
|
|
92
|
+
- **fail-open**:文件缺失/坏值/无 fs → 全部启用(读不到 ≠ 全关);只有**显式** `enabled: []` 是全关。
|
|
93
|
+
- **钉住优先**:env `CLI_BRIDGE_PROVIDER` / `--provider=<id>`(MCP 薄封装)优先级最高;
|
|
94
|
+
宿主形态传 `ignorePin`,避免被全局 env 反噬。
|
|
95
|
+
- **四个生效面**:
|
|
96
|
+
1. `preset/<id>/cli-bridge.mjs` 与 `dynamic/*.host.js` 的 `apply()` 首行门禁(禁用面零注册);
|
|
97
|
+
`preset-declarations` 的注册顺序 = 优先级顺序;
|
|
98
|
+
2. 统一 MCP `selectedProviders()` 按 `enabled` 过滤、按 `order` 排序(**启动读一次**);
|
|
99
|
+
3. 家级 `bin\*-mcp-server.mjs` 经 `bin\cli-bridge-scheduling.mjs` 的 `filterTools()` **每请求现读**
|
|
100
|
+
(面板改完立即生效,无需重启子进程);mimo 的 dispatch 层单独加了拒调文本;
|
|
101
|
+
4. `GET /cli-bridge/status` 与 `/cli-bridge/settings` 每次轮询现读并附 `scheduling` 快照。
|
|
102
|
+
- **客户端**:`providerMetas()` 以视图 `scheduling.all ∪ providers 键` 为权威 id 轴(本地 `PROVIDERS`
|
|
103
|
+
降级为 fallback,仅补 presetId/展示 label);标签页一排点击切换;`stale` 项目(>10 分钟无活动)
|
|
104
|
+
不再渲染常亮 pill(弹窗里仍保留历史)。
|
|
105
|
+
|
|
106
|
+
## 7. 本机运行环境接线现状(机器特定)
|
|
107
|
+
|
|
108
|
+
### 7.1 安装形态
|
|
109
|
+
|
|
110
|
+
- `dsh-home/profiles/web/package.json`:`"@luchenglong/cli-bridge": "file:C:/Users/lcl/Desktop/DSH插件开发/CLI Bridge"`,
|
|
111
|
+
并在 `dsh.profile.bundles` 登记 → 包内 `cordis.patch.yml` 作为 bundle 层自动挂载(宿主半 + 浏览器半 + 三 preset)。
|
|
112
|
+
- **改完必须刷新快照**(pnpm 对 `file:` 依赖按**版本**键控缓存):
|
|
113
|
+
```powershell
|
|
114
|
+
$installed = "$env:APPDATA\DSH Desktop\dsh-home\profiles\web\node_modules\@luchenglong\cli-bridge"
|
|
115
|
+
Remove-Item $installed -Recurse -Force
|
|
116
|
+
Push-Location "$env:APPDATA\DSH Desktop\dsh-home\profiles\web"; pnpm install --prefer-offline; Pop-Location
|
|
117
|
+
# 校验:Get-FileHash 对比仓库与快照同名文件
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 7.2 生效时机(**最容易踩**)
|
|
121
|
+
|
|
122
|
+
| 改动 | 生效方式 |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `lib/client.js`(浏览器半) | **刷新页面(F5)** 即生效,不用重启 |
|
|
125
|
+
| `lib/index.mjs`、`lib/settings.mjs`、`preset-declarations.mjs` | **必须重启 DSH**(宿主启动时加载) |
|
|
126
|
+
| `dsh-home/cordis.patch.yml`(家级补丁/插件行) | **必须重启 DSH** |
|
|
127
|
+
| 家级 `bin\*-mcp-server.mjs` | 子进程重启;但调度门禁是**每请求**读盘,改设置不用重启 |
|
|
128
|
+
| 统一 `mcp/cli-bridge-mcp.mjs` | 子进程重启后读新配置 |
|
|
129
|
+
|
|
130
|
+
### 7.3 家级补丁(`dsh-home\cordis.patch.yml`)
|
|
131
|
+
|
|
132
|
+
活跃 8 条:`session-cleaner`、`local-llm-bridge`、`bot-gateway`、`mobile-companion`,
|
|
133
|
+
以及 **四条 MCP 行**:`mcp-agy-global` / `mcp-codebuddy-global` / `mcp-playwright-global` / `mcp-mimo-global`
|
|
134
|
+
(2026-10-14 从注释状态恢复;备份 `cordis.patch.yml.bak-before-mcp-restore-20261014-234959`)。
|
|
135
|
+
|
|
136
|
+
⚠️ 该文件里 agy/codebuddy/playwright **各另有一份注释留档副本**(历史)。启用时**只能启用一份**,
|
|
137
|
+
否则同 id 重复 → loader 致命错误。改完必须用规范 YAML 解析器验证:
|
|
138
|
+
|
|
139
|
+
```powershell
|
|
140
|
+
node -e "const y=require('$env:APPDATA/DSH Desktop/backend/dsh/node_modules/yaml');const d=y.parse(require('fs').readFileSync('$env:APPDATA/DSH Desktop/dsh-home/cordis.patch.yml','utf8'));const ids=[];for(const e of d)for(const k of Object.keys(e))for(const i of e[k])ids.push(i.id);console.log(d.length, ids.join(','), 'dup:', ids.filter((x,n)=>ids.indexOf(x)!==n))"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 7.4 家级 MCP 材料
|
|
144
|
+
|
|
145
|
+
`dsh-home\bin\agy-mcp-server.mjs`、`bin\codebuddy-mcp-server.mjs`、`bin\cli-bridge-scheduling.mjs`(门禁 helper)、
|
|
146
|
+
`bin\mimo-mcp\{mcp\mimo-mcp-server.mjs,core\*}`、`plugins\playwright-mcp\`(含浏览器内核 `browsers\`)。
|
|
147
|
+
这些是**稳定副本**(仓库/包体变动不影响),但其权威源在仓库 `deploy/legacy-mcp/` + `mcp/`。
|
|
148
|
+
|
|
149
|
+
### 7.5 状态与设置
|
|
150
|
+
|
|
151
|
+
- 设置文件:`dsh-home\cli-bridge-settings.json`(交接时:`enabled=[codebuddy,agy]`、
|
|
152
|
+
`priority=[agy,codebuddy,mimo]` → `order=[agy,codebuddy]`,即 **mimo 停用、agy 优先级最高**)。
|
|
153
|
+
- 状态路由:`GET /cli-bridge/status`(含 `scheduling`);旧路由 `/<id>-indicator/status` 仍返回裸桶。
|
|
154
|
+
- GUI 需要登录态;**用 HTTP 探针即可**(不要把无凭据浏览器指向 GUI,会 401)。
|
|
155
|
+
|
|
156
|
+
## 8. 发布流程
|
|
157
|
+
|
|
158
|
+
见 `docs/RELEASE-SOP.md`(铁律 + 逐步命令)。要点:
|
|
159
|
+
|
|
160
|
+
1. `npm run check` 全绿 → 版本三锁同步(含 `CHANGELOG.md` 顶部)→ `node scripts/build.mjs` → commit → tag → push;
|
|
161
|
+
2. **scoped 包可见性权威开关是 `package.json` 的 `publishConfig.access="public"`**
|
|
162
|
+
(0.2.2 首发踩坑:只靠命令行 `--access public` 时,走 GUI/插件通道发布不带该参数 → `E402 Payment Required`);
|
|
163
|
+
3. 发布后可 `npm deprecate <old>@* "<msg>"` 迁移旧包;**先发布后弃用,顺序不可倒置**;
|
|
164
|
+
4. npm 首次创建 scoped 包返回 `PUT 202 Accepted`("may take a few minutes")**属正常,勿重复发布**。
|
|
165
|
+
|
|
166
|
+
本机发布通道:DSH 工具 `repo_push_access`(GUI 插件 “repo-push-access”,凭据存 DSH 凭据库)。
|
|
167
|
+
|
|
168
|
+
## 9. 凭据与安全
|
|
169
|
+
|
|
170
|
+
- 凭据库:`dsh-home\.credentials.yaml`。**键必须形如 `<scope>/<id>`**
|
|
171
|
+
(如 `repo-push-access/github-com`、`repo-push-access/registry-npmjs-org`)——
|
|
172
|
+
曾因键写成 `repo-push-access:github.com` 导致凭据文档整体不可读 → DSH 启动致命错 + profile 被隔离为
|
|
173
|
+
`profiles.broken-*`。
|
|
174
|
+
- 命令行侧**没有** npm/GitHub 登录态;需要 CLI 操作时从凭据库取用,**不要把 token 打进日志/翻译稿**。
|
|
175
|
+
- 仓库内不得出现任何 token;`.credentials.yaml` 及其备份不入库。
|
|
176
|
+
|
|
177
|
+
## 10. 排障手册(症状 → 原因 → 处置)
|
|
178
|
+
|
|
179
|
+
| 症状 | 原因 | 处置 |
|
|
180
|
+
| --- | --- | --- |
|
|
181
|
+
| 设置面板白屏 / 整个插件页崩 | client.js 引用了未声明标识符,或 load id ≠ 包名 | 跑 `tests/keystone/client-ui-surface.test.mjs`(含标识符扫描)与 `client-id.test.mjs` |
|
|
182
|
+
| 面板没有横向标签 / 没有调度卡 | 浏览器仍在用旧 client.js | F5 刷新;核对 `GET /cli-bridge/settings` 是否含 `scheduling`(不含=宿主也是旧代码 → 重启) |
|
|
183
|
+
| 面板改了启用/优先级但没生效 | ①POST 未成功 ②宿主形态需重启会话 ③统一 MCP 子进程需重启 | 看 `cli-bridge-settings.json` 是否写入 `_scheduling`;家级 `bin\` 服务器无需重启(每请求读盘) |
|
|
184
|
+
| 指示灯长期亮着 | 陈旧项目(>10 分钟无活动)驱动了顶层聚合 | 0.2.1 已修(客户端过滤 + 服务端聚合只看新鲜项目);复发查 `projects[].stale` 与 `PROJECT_TTL_MS` |
|
|
185
|
+
| `pnpm install` 后还是旧代码 | `file:` 依赖按版本键控缓存 | 先 `Remove-Item` 快照目录再 `pnpm install --prefer-offline`,然后哈希比对 |
|
|
186
|
+
| npm 发布 `E402 Payment Required` | scoped 包按私有发布 | `package.json` 加 `publishConfig.access="public"` |
|
|
187
|
+
| npm 发布后 `npm view` 404 | 首次创建 scoped 包在 processing / CDN 负缓存 | 看 npm 日志 `…\.npm-cache\_logs\*` 的 `PUT 202`;等几分钟,别重复发 |
|
|
188
|
+
| 家级补丁报 `Sequence item without - indicator` | 解除注释时把**段落注释**也剥了(前导 `#` 被误删) | 从 `*.bak-*` 还原,按**条目行区间**精确剥离;用 §7.3 的 YAML 校验命令 |
|
|
189
|
+
| 会话里没有 `mcp__*` 工具 | 家级补丁四条 MCP 行被注释 / 未重启 / 子进程未起 | 检查补丁活跃 id、重启 DSH、`Get-CimInstance Win32_Process` 找 node 子进程 |
|
|
190
|
+
| 出现 `profiles.broken-*` | 启动期插件激活失败被桌面壳隔离 | 看 `dsh-home\logs\startup-*.log` 定位首个 error;历史根因是凭据键格式 |
|
|
191
|
+
|
|
192
|
+
## 11. 待办与已知遗留
|
|
193
|
+
|
|
194
|
+
1. **重启 DSH** —— 恢复的四条 MCP 行需要重启才注册(交接时点刚改完配置,尚未重启)。
|
|
195
|
+
重启后:新会话出现 `mcp__agy__*`(4 个,含 quota)、`mcp__codebuddy__*`(3)、`mcp__playwright__*`;
|
|
196
|
+
`mcp__mimo__*` 因调度停用而**工具面为空**(这是设计行为,不是故障)。
|
|
197
|
+
2. 可选:`repo-push-access` 插件写临时 npmrc 时带的 `always-auth=true` 触发 npm 11 弃用警告
|
|
198
|
+
(源码在 `C:\Users\lcl\Desktop\DSH插件开发\git推送管理\repo-push-access`);删掉该行即可静音。
|
|
199
|
+
3. 可选:npm 上的 0.2.2 比仓库少 2 个**纯文档** commit(发布后的 release notes/SOP 补充);
|
|
200
|
+
如需 npm 侧完全一致,可发 0.2.3(无功能改动)。
|
|
201
|
+
4. `dsh-home\profiles.broken-*` 是历史隔离物(用户数据,未清理)。
|
|
202
|
+
|
|
203
|
+
## 12. 事故史速查(后来者避坑)
|
|
204
|
+
|
|
205
|
+
- **client 注册 id 必须等于包名**:旧包曾因 id 不匹配导致整屏崩溃 → 现在三重机器锁。
|
|
206
|
+
- **一次 edit 失败没复查**:渲染引用了未定义的 handler → 白屏,`node --check` 查不出 →
|
|
207
|
+
新增「渲染引用标识符必须已声明」静态测试。
|
|
208
|
+
- **React hook 顺序**:tab 的 `useState` 曾放在 `if (!view) return` 之后 → view 加载瞬间 hook 数量变化 → 崩面板;
|
|
209
|
+
现在有「hook 必须在早期 return 之前」的静态锁。
|
|
210
|
+
- **profile 隔离会吞掉用户层补丁**:`profiles/web/cordis.patch.yml` 曾随隔离重建丢失 →
|
|
211
|
+
机器级配置一律写 `dsh-home\cordis.patch.yml`(家级,不受隔离影响)。
|
|
212
|
+
- **双源挂载**:家级 `file://` 行与 bundle 层同时声明同一 id → `multiple active Loader sources` 致命错误;
|
|
213
|
+
统一包只走 bundle 层,家级只保留注释留档。
|
|
214
|
+
- **MCP 反复丢失**:家级三条 MCP 行曾在隔离重建中消失且被当基线固化;恢复时误留成注释 →
|
|
215
|
+
本次已恢复为活跃行(§7.3)。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### 交接最小检查单
|
|
220
|
+
|
|
221
|
+
- [ ] `npm run check` 全绿(271 测试)
|
|
222
|
+
- [ ] `node mcp/cli-bridge-mcp.mjs --check` 显示 `scheduling.source`
|
|
223
|
+
- [ ] `GET $env:DSH_WEB_URL/cli-bridge/status` 返回三家 + `scheduling`
|
|
224
|
+
- [ ] 重启 DSH 后确认四条 MCP 工具面(含 mimo 为空)
|
|
225
|
+
- [ ] 设置面板能勾选/排序/保存,且 `cli-bridge-settings.json` 落盘
|
|
226
|
+
- [ ] 知道红线在 §5、生效时机在 §7.2、排障在 §10
|