@luchenglong/cli-bridge 0.0.0-stage → 0.2.2

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/LICENSE +21 -0
  3. package/MCP-POLICY.md +53 -0
  4. package/PROVIDER-CONTRACT.md +235 -0
  5. package/README.en.md +98 -0
  6. package/README.md +150 -2
  7. package/assets/indicator-states.svg +62 -0
  8. package/bin/agy-quota.mjs +223 -0
  9. package/core/bridge-kit.mjs +549 -0
  10. package/core/scheduling.mjs +149 -0
  11. package/docs/ARCHITECTURE.md +117 -0
  12. package/docs/FALLBACK-AND-INDICATOR.md +93 -0
  13. package/docs/INSTALL.md +118 -0
  14. package/docs/MCP-POLICY.zh.md +49 -0
  15. package/docs/MIGRATION.md +43 -0
  16. package/docs/RELEASE-SOP.md +77 -0
  17. package/docs/history/agy/CHANGELOG.md +326 -0
  18. package/docs/history/codebuddy/CHANGELOG.md +668 -0
  19. package/docs/history/mimo/CHANGELOG.md +346 -0
  20. package/dynamic/agy.host.js +1237 -0
  21. package/dynamic/agy.host.template.mjs +140 -0
  22. package/dynamic/codebuddy.host.js +1787 -0
  23. package/dynamic/codebuddy.host.template.mjs +162 -0
  24. package/dynamic/mimo.host.js +1732 -0
  25. package/dynamic/mimo.host.template.mjs +169 -0
  26. package/home-plugin/cli-bridge-indicator/cordis.patch.yml +31 -0
  27. package/home-plugin/cli-bridge-indicator/lib/client.js +606 -0
  28. package/home-plugin/cli-bridge-indicator/lib/index.mjs +420 -0
  29. package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +137 -0
  30. package/home-plugin/cli-bridge-indicator/lib/settings.mjs +300 -0
  31. package/mcp/agy-mcp-server.mjs +6 -0
  32. package/mcp/cli-bridge-mcp.mjs +184 -0
  33. package/mcp/codebuddy-mcp-server.mjs +6 -0
  34. package/mcp/mimo-mcp-server.mjs +6 -0
  35. package/mcp/providers/agy.mjs +254 -0
  36. package/mcp/providers/codebuddy.mjs +397 -0
  37. package/mcp/providers/mimo.mjs +290 -0
  38. package/package.json +87 -4
  39. package/preset/agy-first/agent.cordis.yml +141 -0
  40. package/preset/agy-first/cli-bridge.mjs +169 -0
  41. package/preset/agy-first/plugin-list.mjs +26 -0
  42. package/preset/agy-first/preset.yml +3 -0
  43. package/preset/codebuddy-first/agent.cordis.yml +141 -0
  44. package/preset/codebuddy-first/cli-bridge.mjs +188 -0
  45. package/preset/codebuddy-first/plugin-list.mjs +20 -0
  46. package/preset/codebuddy-first/preset.yml +3 -0
  47. package/preset/mimo-first/agent.cordis.yml +143 -0
  48. package/preset/mimo-first/cli-bridge.mjs +236 -0
  49. package/preset/mimo-first/plugin-list.mjs +39 -0
  50. package/preset/mimo-first/preset.yml +3 -0
  51. package/preset/standard-base.mjs +102 -0
  52. package/providers/agy.mjs +398 -0
  53. package/providers/codebuddy.mjs +926 -0
  54. package/providers/mimo.mjs +864 -0
  55. package/providers/registry.mjs +34 -0
  56. package/scripts/build.mjs +235 -0
  57. package/scripts/deploy.mjs +61 -0
  58. 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` 等保留为别名)。
@@ -0,0 +1,118 @@
1
+ # 安装指南(CLI Bridge)
2
+
3
+ 一个包四种形态,可组合使用:
4
+
5
+ 1. **持久 Agent Preset**(推荐主形态)—— 三家工具 + 优先策略 + 回退弹窗 + `<id>_status`:
6
+ `cordis-agy` / `codebuddy-first` / `cordis-mimo`(preset id 与旧包一致,旧会话直接续用);
7
+ 2. **家级指示灯插件** —— 状态灯、统一设置面板、preset 声明注册,随 DSH 启动自动加载、
8
+ 所有会话显示、无需审批(`home-plugin/cli-bridge-indicator`,经包内 `dsh.bundle.patch` 挂载);
9
+ 3. **动态 Cordis 插件** —— 沙箱内临时形态(`dynamic/<id>.host.js`),给不方便装 preset 的
10
+ 组合用;
11
+ 4. **MCP stdio server** —— 给 DSH 之外的任何 MCP 客户端(Claude Code / Cursor / …):
12
+ `node mcp/cli-bridge-mcp.mjs`(或单家入口 `mcp/<id>-mcp-server.mjs`)。
13
+
14
+ **推荐组合:方式 A + 方式 B** —— 一次接线,三家工具与常驻状态灯齐备。
15
+
16
+ ---
17
+
18
+ ## 前提
19
+
20
+ - 一套可运行的 **DeepSeek Harness (DSH)**(`>=0.1.1-rc.2 <0.3.0-0`,本机 0.2.1-alpha.1 验证)。
21
+ - 至少装好一家你要用的本机 CLI(工具会在缺失时亮灰灯并给出安装指引,不会崩):
22
+ - `agy`(Google Antigravity CLI)—— 需已登录(`agy login`);
23
+ - `codebuddy`(`npm i -g @tencent-ai/codebuddy-code`);workbuddy/codebuddy-en 走桌面捆绑路径;
24
+ - `mimo`(MiMoCode CLI)或运行中的 Xiaomi MiMo Desktop(mimo-desktop 后端)。
25
+ - 会话组合挂载这些 Host 服务(`standard` 系 preset 默认都有):`tools`、`subprocess`、
26
+ `systemPrompt`、`timer`;可选增强:`jobs`(后台任务)、`planMode`(plan 自动判定)、
27
+ `sandboxPolicy`(默认 cwd)、`userQuestions`(回退弹窗)。
28
+
29
+ ---
30
+
31
+ ## 方式 A + B:bundle 接线(一次装齐,推荐)
32
+
33
+ 本包是「双面 bundle」:`dsh.bundle.patch` 只带一行 indicator(家级灯 + 设置 + 路由),
34
+ 三家 preset 由 indicator 内 **声明制自注册**(Form-A,`agentPresets.register`),
35
+ 任何受支持 DSH 版本一步到位。
36
+
37
+ 1. 安装依赖到 profile(停 DSH 后端后操作;运行中改 bundles 有损坏风险,红线):
38
+
39
+ ```powershell
40
+ npm run deploy:local # 暂存到 {DSH_HOME}/staging/cli-bridge(只拷贝,不改 profile)
41
+ ```
42
+
43
+ 或全局安装后直接引用 npm 包:`npm i -g @luchenglong/cli-bridge`。
44
+
45
+ 2. 在 profile 的 `package.json` 加依赖(file: 或版本号),并把包名加入 bundles 列表:
46
+
47
+ ```jsonc
48
+ {
49
+ "dependencies": { "@luchenglong/cli-bridge": "file:C:/Users/<you>/AppData/Roaming/DSH Desktop/dsh-home/staging/cli-bridge" },
50
+ "bundles": ["@luchenglong/cli-bridge"]
51
+ }
52
+ ```
53
+
54
+ 3. 若之前挂着三个旧桥接包:删除它们的依赖与 bundle 条目(迁移见 [docs/MIGRATION.md](docs/MIGRATION.md))。
55
+
56
+ 4. 重启 DSH,验证:
57
+ - 模式菜单出现 **AGY 执行代理 / CodeBuddy 执行代理 / MiMo 执行代理** 三个 preset;
58
+ - 标题栏出现一盏聚合 pill(AGY · CB · MIMO 分区);
59
+ - `GET http://127.0.0.1:<port>/cli-bridge/status` 返回三桶 JSON;
60
+ - 设置页出现「CLI Bridge」段(三家卡片 + 诊断)。
61
+
62
+ ## 方式 C:动态宿主(无 preset 的组合)
63
+
64
+ 把 `dynamic/<id>.host.js`(仓库已带生成物;改模板后 `node scripts/build.mjs` 重新生成)
65
+ 粘贴进 Cordis 组合的 `code.host`。沙箱内零 import,宿主能力走 `harness.*`;状态经
66
+ `cliBridgeCollector`(旧 `agyCollector/codebuddyCollector/mimoCollector` 别名兼容)汇入灯。
67
+
68
+ ## 方式 D:MCP server(DSH 之外)
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "cli-bridge": {
74
+ "command": "node",
75
+ "args": ["<安装路径>/@luchenglong/cli-bridge/mcp/cli-bridge-mcp.mjs"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ 只要一家:`args` 换成 `mcp/agy-mcp-server.mjs`(等价 `--provider=agy`),其余配置与旧包一致。
82
+ 自检:`node mcp/cli-bridge-mcp.mjs --check`。
83
+
84
+ **MCP 形态的状态灯**:MCP 进程把状态原子写到 `{DSH_HOME}/cli-bridge-mcp-<id>.json`
85
+ (90s 内有效),DSH 里常驻的家级灯自动合并显示;家级插件不在线时,灯自然熄灭(快照过期),
86
+ 不会误报「运行中」。旧包快照文件(`codebuddy-indicator-mcp.json`、
87
+ `plugins/mimo-indicator/mcp-live.json`)仍被兼容读取。
88
+
89
+ ---
90
+
91
+ ## 设置
92
+
93
+ DSH 设置页 →「CLI Bridge」:每家一张卡片(字段由 provider 声明),token 类字段只显掩码
94
+ (保存留空 = 保持现值,勾选清除 = 显式删除)。落盘 `{DSH_HOME}/cli-bridge-settings.json`
95
+ (按 provider 分区;env `CLI_BRIDGE_SETTINGS_FILE` 可覆盖)。
96
+ 旧包设置文件(`codebuddy-bridge-settings.json`、`{APPDATA}/mimo-first-bridge/settings.json`)
97
+ 会被**一次性字段级迁移**:新文件没写过该字段则自动回退旧值,写过即以新值为准;永不写旧文件。
98
+
99
+ ## 环境变量总表(与旧包逐字一致,另加统一项)
100
+
101
+ | 变量 | 作用 |
102
+ | --- | --- |
103
+ | `AGY_BIN` / `AGY_CLI` | agy 可执行覆盖 |
104
+ | `AGY_MCP_CWD` | agy 默认工作目录 |
105
+ | `CLI_BRIDGE_AGY_QUOTA`(兼容旧 `AGY_QUOTA_SCRIPT`) | 额度脚本路径覆盖(默认包内 bin) |
106
+ | `CODEBUDDY_BIN` / `CODEBUDDY_EN_BIN` / `WORKBUDDY_BIN` | codebuddy 各家可执行覆盖 |
107
+ | `CODEBUDDY_AUTH_TOKEN` / `CODEBUDDY_BASE_URL` / `CODEBUDDY_INTERNET_ENVIRONMENT` | codebuddy 端点与凭据注入 |
108
+ | `CODEBUDDY_AUTH_DIR` / `CODEBUDDY_CREDENTIALS_DIR` / `CODEBUDDY_MODELS_FILE` | 登录域文件目录 / DSH 凭据库目录(内放 `WORKBUDDY_TOKEN:` 等)/ 模型目录注入(旧包即有,多为测试与诊断覆盖;`WORKBUDDY_TOKEN` 本身不是环境变量,是凭据文件里的键) |
109
+ | `CODEBUDDY_MCP_CWD` | codebuddy MCP 默认工作目录(仅 MCP 面;preset/dynamic 不读) |
110
+ | `CODEBUDDY_AUTH_TOKEN` / `CODEBUDDY_BASE_URL` / `CODEBUDDY_INTERNET_ENVIRONMENT` | codebuddy 端点与凭据注入 |
111
+ | `CODEBUDDY_SETTINGS_FILE` | 旧 codebuddy 设置文件覆盖(迁移读) |
112
+ | `CODEBUDDY_MCP_ALLOWED_ROOTS` | codebuddy MCP 的 cwd 白名单 |
113
+ | `MIMO_BIN` / `MIMO_CLI` | mimo CLI 覆盖 |
114
+ | `MIMO_BRIDGE_BACKEND` / `MIMO_BACKEND` / `MIMO_DESKTOP_MODEL` | mimo 后端/桌面模型 env |
115
+ | `MIMO_BRIDGE_SETTINGS` | 旧 mimo 设置文件覆盖(迁移读) |
116
+ | `MIMO_MCP_CWD` | mimo 默认工作目录 |
117
+ | `DSH_HOME` | dsh-home 定位(设置/MCP 快照文件根) |
118
+ | `CLI_BRIDGE_SETTINGS_FILE` | 统一设置文件整体覆盖 |
@@ -0,0 +1,49 @@
1
+ # CLI Bridge 优先策略(cli-bridge MCP)· 中文版
2
+
3
+ [English](../MCP-POLICY.md) | 中文 —— 权威文本;开发机上同步安装到
4
+ `~/.claude/CLAUDE.md` 与 `~/.codex/AGENTS.md`。
5
+
6
+ 本机三个编码 CLI agent —— **`agy`**(Google Antigravity)、**`codebuddy`**(腾讯
7
+ CodeBuddy Code,四种产品形态)与 **`mimo`**(小米 MiMoCode CLI / MiMo Desktop)——
8
+ 经 MCP server **`cli-bridge`** 暴露为工具:`agy_run / agy_continue / agy_status /
9
+ agy_quota`、`codebuddy_run / codebuddy_continue / codebuddy_status`、
10
+ `mimo_run / mimo_continue / mimo_status / mimo_settings`(宿主侧带服务前缀,如
11
+ `mcp__cli-bridge__agy_run`)。所有 CLI **全程受宿主掌控、完全非交互**:权限自动批准、
12
+ 绝不弹提示,可以直接改文件、跑构建、做跨文件排查。
13
+
14
+ 一个 server 同时暴露三家;用 `--provider=agy`(或单家入口 `mcp/<id>-mcp-server.mjs`)
15
+ 可只开一家,工具面与旧包配置逐字兼容。
16
+
17
+ ## 优先规则 —— 决策表与上下文协议
18
+
19
+ ### 工具选择(三家同构)
20
+ - **单条 read/glob/grep 或 <30 秒短命令** → 用宿主原生工具。
21
+ - **实现、编辑、重构、调试、构建、跨文件排查** → 优先 `<id>_run`(按账号/额度选家族)。
22
+ - **同主题后续** → `<id>_continue` 带 `sessionId`/`conversationId` 或 `latest: true`
23
+ (会话上下文由服务端保留)。
24
+ - **对 CLI 产出做最终构建/测试验证** → 用宿主原生工具。
25
+ - **图像/视频生成** → 派给 `agy_run`,不指定模型。
26
+
27
+ ### 上下文协议(一个主题 = 一个会话)
28
+ - **主题首次派发**:带一段紧凑 CONTEXT 前言(指向路径,不要粘贴文件内容;CLI 自己读仓库):
29
+ ```
30
+ CONTEXT: 2-6 行 —— 目标;仓库根;关键路径;决定/约束;当前状态。
31
+ TASK: 完整指令。
32
+ ```
33
+ - **后续**:调用 `<id>_continue`,只说新增部分。
34
+ - 任务暂时不许写文件时,`<id>_run` 用 `mode: "plan"`。
35
+ - 委派子代理时,要求其同样遵守本 CLI 优先策略。
36
+ - `<id>_run`/`<id>_continue` 运行期间可随时 `<id>_status` 看各项目此刻步骤与最近轨迹,
37
+ 无需等待完成。
38
+
39
+ ## 额度感知(agy)
40
+
41
+ `agy_quota` 返回 Google AI 套餐各池余量。**Gemini 5 小时窗口是唯一的任务级门禁**
42
+ (余量低于约 10% 时桥接会直接拦截派发);周池耗尽不会中止工作 —— 只代表本周剩余时间
43
+ agy 不是好选择。codebuddy 无额度 API:用 `codebuddy_status` 观察各项目
44
+ `runs`/`totalTokens`。`mimo_settings` 查看生效后端(CLI vs Desktop)。
45
+
46
+ ## 回退 —— 绝不循环
47
+
48
+ CLI **被限流或网络不通**时,工具结果文本会明确说明。**不要循环重试。** 用自己的工具
49
+ 完成任务,或询问用户是否回退到本地模型/API 配置。绝不让被派发的 CLI 回调宿主。
@@ -0,0 +1,43 @@
1
+ # 从三个旧包迁移到 @luchenglong/cli-bridge
2
+
3
+ 三个旧包(`agy-first-bridge` ≤1.7.1、`codebuddy-first-bridge` ≤1.6.2、
4
+ `mimo-first-bridge` ≤1.2.9)已全部并入本包并 deprecated。**行为与接口的兼容性是逐字级
5
+ 的**,迁移只是「换包名 + 换路径」:
6
+
7
+ ## 不变的东西(刻意设计)
8
+
9
+ | 项 | 旧 | 新 | 变化 |
10
+ | --- | --- | --- | --- |
11
+ | 工具名 | `agy_run` / `codebuddy_run` / `mimo_run` …(含 continue/status/quota/settings) | 同左 | **无** |
12
+ | 工具入参/返回语义 | 旧包定义 | verbatim 移植 | **无** |
13
+ | preset id | `cordis-agy` / `codebuddy-first` / `cordis-mimo` | 同左 | **无**(旧会话头直接续用) |
14
+ | 优先策略/POLICY 文本 | 各旧包 | verbatim | **无** |
15
+ | 回退弹窗三选一 | 各旧包 | 统一内核实现,标签/语义一致 | 无 |
16
+ | env 变量 | 各旧包(见 INSTALL 总表) | 全部保留 | 无 |
17
+ | 家级路由 | `/{agy,codebuddy,mimo}-indicator/status` 等 | 保留为兼容别名,新增 `/cli-bridge/status` 聚合 | 兼容 |
18
+ | 设置语义(token 掩码/空=保持/Clear=清除) | codebuddy v1.3+ | 统一面板逐字段保留 | 兼容 |
19
+ | 旧设置文件 | `codebuddy-bridge-settings.json`、`{APPDATA}/mimo-first-bridge/settings.json` | 自动一次性迁移(新分区没写过该字段才回退旧值;写新不写旧) | 无损 |
20
+ | MCP 快照文件 | `{DSH_HOME}/plugins/agy-indicator/mcp-live.json`、`{DSH_HOME}/codebuddy-indicator-mcp.json`、`{DSH_HOME}/plugins/mimo-indicator/mcp-live.json` | 新名 `cli-bridge-mcp-<id>.json`;三个旧名仍被家级灯兼容读取 | 无损 |
21
+
22
+ ## 需要动手的三处
23
+
24
+ 1. **DSH profile / bundles**:删除三个旧包条目,加入 `@luchenglong/cli-bridge`
25
+ (操作前必须停止 DSH 后端——运行时改接线有损坏风险)。
26
+ 2. **外部 MCP 配置**(Claude Code / Cursor 等):`args` 路径从
27
+ `<旧包>/mcp/<id>-mcp-server.mjs` 换成 `<新包>/mcp/<id>-mcp-server.mjs`(同名兼容入口,
28
+ 也可换 `mcp/cli-bridge-mcp.mjs` 一次拿全部三家)。
29
+ 3. **直接引用过旧包内部路径的脚本**(如有):旧 `core/*` → 新
30
+ `providers/<id>.mjs` + `core/bridge-kit.mjs`;旧 preset 模块路径 → 新
31
+ `@luchenglong/cli-bridge/preset-<id>`(exports 子路径)。
32
+
33
+ ## 版本对应
34
+
35
+ `0.1.0` 的功能基线 = agy 1.7.1 + codebuddy 1.6.2 + mimo 1.2.9 之和。
36
+ 三家完整历史在 [docs/history/](history/)(verbatim);统一包自己的演进在
37
+ [CHANGELOG.md](../CHANGELOG.md)。
38
+
39
+ ## 旧包仓库
40
+
41
+ 三个旧仓库将只读归档(README 顶部挂迁移横幅)。npm 上已执行
42
+ `npm deprecate <旧包>@"*" "replaced by @luchenglong/cli-bridge"`。
43
+ 不要再向旧包提 issue/PR —— 一律发到新仓库。
@@ -0,0 +1,77 @@
1
+ # 发布与维护 SOP(npm + GitHub)
2
+
3
+ > 本包发布与日常维护的操作手册。每一步的「为什么」都指向旧包 lineage 里的历史事故
4
+ > (agy v1.6.1、codebuddy v1.1.8/v1.7.1、mimo v1.2.9),不要跳步。
5
+
6
+ ## 0. 铁律(违反任何一条都不要发布)
7
+
8
+ 1. **npm publish 必须带 `--access public`**:包是 `@luchenglong` scope,scoped 包默认
9
+ restricted,漏了直接 402/私有可见性事故。
10
+ 2. **client 注册 id === 包名**(`@luchenglong/cli-bridge`,红线 4)。`scripts/verify.mjs`
11
+ 与 `tests/keystone/client-id.test.mjs` 双重机器闸门。改包名必须同改 client.js 的
12
+ `__ModuleLoader__.load({ id })`,否则 client combo 加载失败 → DSH 启动致命屏(agy
13
+ v1.6.1 / codebuddy v1.1.8 事故原样)。
14
+ 3. **先 git 后 npm**:commit → tag → push → GitHub Release → `npm publish` 顺序不可倒置。
15
+ 发布前 `npm run check` 全绿(build --check 防生成物漂移 + verify 红线 + node --test +
16
+ mcp --check)。
17
+ 4. **生成物入库**:`dynamic/*.host.js`、`preset/*/agent.cordis.yml`、`preset.yml` 提交;
18
+ `scripts/build.mjs --check` 漂移即红。改了 plugin-list/模板/kit/providers 必须重跑
19
+ `node scripts/build.mjs` 并把生成物放进同一个 commit。
20
+ 5. **provider 语义 verbatim**:工具名、入参 schema、preset id、POLICY 文案是对外的兼容
21
+ 承诺(旧会话头/提示词在用)。要改 = 大版本 + MIGRATION.md 补条目。
22
+ 6. **禁止新增运行时依赖与 `@deepseek-ai/dsh*` peer 依赖**(verify/测试双锁;加 dsh peer
23
+ 会武装 0.2.x 兼容闸门,历史论证)。
24
+
25
+ ## 1. 标准发布流程
26
+
27
+ ```powershell
28
+ # ① 全量校验
29
+ npm run check
30
+
31
+ # ② 版本同步三处:package.json / CHANGELOG.md 顶部 / mcp/cli-bridge-mcp.mjs 的
32
+ # SERVER_VERSION(verify.mjs 机器比对,漏改即红)
33
+
34
+ # ③ 提交 + tag + 推送
35
+ git add <相关文件>; git commit -m "chore(release): v0.1.1"
36
+ git tag -a v0.1.1 -m "v0.1.1"
37
+ git push origin main; git push origin v0.1.1
38
+
39
+ # ④ 发布(prepack 自动再跑 verify + build --check)
40
+ npm publish --access public
41
+
42
+ # ⑤ 发布后审计
43
+ npm view @luchenglong/cli-bridge version dist.tarball
44
+ # GitHub 建 Release 挂 CHANGELOG 条目
45
+ ```
46
+
47
+ ## 2. 首次建仓(当前凭据失效,恢复后照此执行)
48
+
49
+ ```powershell
50
+ gh auth login # 或 gh auth refresh -s repo,workflow
51
+ gh repo create new-256/cli-bridge --public --source . --push --description "Unified CLI bridge for DSH: agy + CodeBuddy + MiMo in one plugin"
52
+ npm login
53
+ npm publish --access public
54
+ # 旧包下线(deprecate 不删除,旧安装仍可用但提示迁移)
55
+ npm deprecate agy-first-bridge@"*" "replaced by @luchenglong/cli-bridge"
56
+ npm deprecate codebuddy-first-bridge@"*" "replaced by @luchenglong/cli-bridge"
57
+ npm deprecate mimo-first-bridge@"*" "replaced by @luchenglong/cli-bridge"
58
+ ```
59
+
60
+ > 360 安全软件曾静默删除带 GitHub remote 丢失的目录——**每个仓库必须有 remote**。
61
+ > 三个旧仓库在 README 挂迁移横幅后于 GitHub 归档(Archive)。
62
+
63
+ ## 3. 日常维护闸门
64
+
65
+ - 任意代码改动:`npm run check`(等价 CI)。
66
+ - 只改一家:`node scripts/build.mjs --only=<id>` + `node --test tests/<id>`。
67
+ - 指示灯/设置层改动:`node --test tests/keystone`。
68
+ - 排查线上「灯不对」:先看 `GET /cli-bridge/status` 三桶 JSON,再看
69
+ `{DSH_HOME}/cli-bridge-mcp-<id>.json` 的 updatedAt(>90s = MCP 侧停更),
70
+ 再看设置卡诊断行。
71
+ - 运行时接线永远「先停 DSH 后端」再改 profile(红线 2)。
72
+
73
+ ## 4. 回滚
74
+
75
+ npm 侧:`npm deprecate @luchenglong/cli-bridge@<坏版本> "..."` + 发补丁版(npm 不删
76
+ 已发布内容)。git 侧:revert 提交重发。DSH 侧最坏情况(client id 写错上线):profile
77
+ 里摘掉该 bundle 重启即可恢复,与旧包时代一致。