@luchenglong/cli-bridge 0.2.7 → 0.2.9
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 +97 -0
- package/PROVIDER-CONTRACT.md +53 -4
- package/README.md +12 -11
- package/docs/HANDOVER.md +9 -0
- package/docs/audits/2026-10-18-preset-0.2.1-contract-fix.md +160 -0
- package/docs/audits/README.md +15 -0
- package/dynamic/agy.host.js +1 -1
- package/dynamic/claude.host.js +1801 -0
- package/dynamic/claude.host.template.mjs +108 -0
- package/dynamic/codebuddy.host.js +1 -1
- package/dynamic/codex.host.js +1955 -0
- package/dynamic/codex.host.template.mjs +137 -0
- package/dynamic/mimo.host.js +1 -1
- package/dynamic/opencode.host.js +1 -1
- package/home-plugin/cli-bridge-indicator/lib/client.js +600 -606
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +87 -9
- package/mcp/cli-bridge-mcp.mjs +6 -4
- package/mcp/providers/claude.mjs +231 -0
- package/mcp/providers/codex.mjs +239 -0
- package/package.json +10 -6
- package/preset/agy-first/agent.cordis.yml +0 -5
- package/preset/agy-first/plugin-list.mjs +4 -1
- package/preset/claude-first/agent.cordis.yml +136 -0
- package/preset/claude-first/cli-bridge.mjs +131 -0
- package/preset/claude-first/plugin-list.mjs +28 -0
- package/preset/claude-first/preset.yml +3 -0
- package/preset/codebuddy-first/agent.cordis.yml +0 -5
- package/preset/codex-first/agent.cordis.yml +136 -0
- package/preset/codex-first/cli-bridge.mjs +160 -0
- package/preset/codex-first/plugin-list.mjs +28 -0
- package/preset/codex-first/preset.yml +3 -0
- package/preset/mimo-first/agent.cordis.yml +1 -7
- package/preset/mimo-first/plugin-list.mjs +18 -12
- package/preset/mimo-member/plugin-list.mjs +49 -0
- package/preset/opencode-first/agent.cordis.yml +0 -5
- package/preset/opencode-first/plugin-list.mjs +4 -2
- package/preset/standard-base.mjs +18 -6
- package/providers/claude-exe.mjs +112 -0
- package/providers/claude-models.mjs +334 -0
- package/providers/claude-policy.mjs +37 -0
- package/providers/claude.mjs +508 -0
- package/providers/codex-cli.mjs +471 -0
- package/providers/codex-models.mjs +107 -0
- package/providers/codex.mjs +544 -0
- package/providers/registry.mjs +25 -1
- package/scripts/build.mjs +1 -2
- package/scripts/verify.mjs +8 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// preset/codex-first/cli-bridge.mjs — codex-first preset 的桥接插件(preset 宿主形态)。
|
|
2
|
+
//
|
|
3
|
+
// PROVIDER-CONTRACT.md §5:本模块只做「宿主接线」——构造描述符、attach、注册三工具
|
|
4
|
+
// (+ codex_models)、心跳/事件发布、codex:policy 提示词段。全部执行/状态语义在
|
|
5
|
+
// providers/codex.mjs + core/bridge-kit.mjs。
|
|
6
|
+
//
|
|
7
|
+
// preset id 'codex-first' 由 plugin-list.mjs 单源声明。
|
|
8
|
+
//
|
|
9
|
+
// 本模块是宿主进程内真 ESM:可 import node: 内建与包内相对路径;禁止 import
|
|
10
|
+
// @deepseek-ai/*(解析根够不到 harness 包,历史论证 —— 契约 §8)。
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
|
|
13
|
+
import defineProvider, { codexResolveExePrefix, codexExeCandidates } from '../../providers/codex.mjs'
|
|
14
|
+
import { codexShimRejectReason } from '../../providers/codex-cli.mjs'
|
|
15
|
+
import { renderResult, renderStatus } from '../../core/bridge-kit.mjs'
|
|
16
|
+
import { isBridgeEnabled } from '../../core/scheduling.mjs'
|
|
17
|
+
import { PROVIDER_IDS } from '../../providers/registry.mjs'
|
|
18
|
+
|
|
19
|
+
export const name = 'cli-bridge-codex'
|
|
20
|
+
export const inject = ['tools', 'subprocess', 'systemPrompt', 'timer']
|
|
21
|
+
|
|
22
|
+
const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
|
|
23
|
+
|
|
24
|
+
export function apply(ctx) {
|
|
25
|
+
// 调度门禁(0.2.0):用户在设置面板关掉 codex → 本形态不注册任何工具/政策段/心跳。
|
|
26
|
+
// 每次会话装配时现读文件(改设置下次会话即生效,无需重启宿主);fail-open。
|
|
27
|
+
if (!isBridgeEnabled('codex', PROVIDER_IDS)) return
|
|
28
|
+
const subprocess = ctx.subprocess
|
|
29
|
+
const sandboxPolicy = ctx.get('sandboxPolicy')
|
|
30
|
+
|
|
31
|
+
const provider = defineProvider({ process, env: process.env })
|
|
32
|
+
|
|
33
|
+
// 状态快照 → 家级灯(keystone 听 'codex/status',事件是 app 级广播)。
|
|
34
|
+
function publish(snapshot) {
|
|
35
|
+
try { ctx.emit('codex/status', { snapshot }) } catch (e) { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// cwd 兜底:显式 cwd 参数 > env CODEX_BRIDGE_CWD > 沙箱 workspaceRoot > process.cwd()。
|
|
39
|
+
//(不用 CODEX_HOME —— 那是 codex 自己的配置目录,语义不同,误用会把工作区指到 ~/.codex。)
|
|
40
|
+
function getCwdFallback() {
|
|
41
|
+
if (process.env.CODEX_BRIDGE_CWD) return String(process.env.CODEX_BRIDGE_CWD)
|
|
42
|
+
if (sandboxPolicy && typeof sandboxPolicy.workspaceRoot === 'string' && sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot
|
|
43
|
+
return process.cwd()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// exe 解析优先级(与描述符同一套):面板 binPath(getSettings)> CODEX_BIN >
|
|
47
|
+
// CODEX_CLI > CODEX_CLI_PATH > PATH 里的 codex。.js/.mjs 目标经 node 包装。
|
|
48
|
+
const binPathSetting = (() => {
|
|
49
|
+
try {
|
|
50
|
+
const s = ctx.get && ctx.get('cliBridgeSettings')
|
|
51
|
+
const v = s && s.codex ? s.codex.binPath : ''
|
|
52
|
+
return typeof v === 'string' ? v : ''
|
|
53
|
+
} catch (e) { return '' }
|
|
54
|
+
})()
|
|
55
|
+
const candidates = codexResolveExePrefix(process.env, binPathSetting)
|
|
56
|
+
|
|
57
|
+
async function resolveExe(backend, signal) {
|
|
58
|
+
let bare = ''
|
|
59
|
+
for (const c of candidates) {
|
|
60
|
+
// .cmd/.bat shim 直接给出可行动错误:无 shell 时 spawn 不解析它,走 shell 又会
|
|
61
|
+
// 二次解释 prompt —— 与其抛难懂的 EINVAL,不如在解析阶段说清楚。
|
|
62
|
+
const shim = codexShimRejectReason(c)
|
|
63
|
+
if (shim) throw new Error(shim)
|
|
64
|
+
if (/\.(mjs|cjs|js)$/i.test(c)) return [process.execPath, c]
|
|
65
|
+
if (/[\\/]/.test(c)) { try { if (existsSync(c)) return c } catch (e) { /* 继续下一个候选 */ } continue }
|
|
66
|
+
if (!bare) bare = c
|
|
67
|
+
}
|
|
68
|
+
// PATH 兜底:第一个裸名候选(CODEX_BIN 也可能是裸名,如 'codex-alt')。
|
|
69
|
+
return await subprocess.resolveExecutable(bare || codexExeCandidates(process.env).slice(-1)[0], undefined, signal)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
provider.attach({ ctx, subprocess, resolveExe, getCwdFallback, publish })
|
|
73
|
+
|
|
74
|
+
// 宣告 codex 优先模式:家级灯据此决定常驻显示(presetActive)。挂载即宣告 +
|
|
75
|
+
// 每 30s 心跳续租(TTL 75s,契约 §2)。
|
|
76
|
+
function announceMode() {
|
|
77
|
+
try {
|
|
78
|
+
const sid = (ctx.session && ctx.session.id) || ctx.sessionId || null
|
|
79
|
+
ctx.emit('codex/mode', sid ? { active: true, sessionId: sid } : { active: true })
|
|
80
|
+
} catch (e) { /* ignore */ }
|
|
81
|
+
}
|
|
82
|
+
announceMode()
|
|
83
|
+
try {
|
|
84
|
+
const t = ctx.setInterval ? ctx.setInterval(announceMode, 30000) : null
|
|
85
|
+
if (t && ctx.effect) ctx.effect(() => () => { try { t() } catch (e) { /* ignore */ } })
|
|
86
|
+
} catch (e) { /* 心跳尽力而为 */ }
|
|
87
|
+
|
|
88
|
+
// ── 工具注册(执行进描述符三逻辑)────────────────────────────────────────────
|
|
89
|
+
ctx.effect(() => ctx.tools.register({
|
|
90
|
+
name: provider.tools.run.name,
|
|
91
|
+
description: provider.tools.run.description,
|
|
92
|
+
parameters: provider.tools.run.inputSchema,
|
|
93
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
94
|
+
execute: (args, exec) => provider.runToolLogic(args, exec)
|
|
95
|
+
}))
|
|
96
|
+
ctx.effect(() => ctx.tools.register({
|
|
97
|
+
name: provider.tools.continue.name,
|
|
98
|
+
description: provider.tools.continue.description,
|
|
99
|
+
parameters: provider.tools.continue.inputSchema,
|
|
100
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
101
|
+
execute: (args, exec) => provider.continueToolLogic(args, exec)
|
|
102
|
+
}))
|
|
103
|
+
ctx.effect(() => ctx.tools.register({
|
|
104
|
+
name: provider.tools.status.name,
|
|
105
|
+
description: provider.tools.status.description,
|
|
106
|
+
parameters: provider.tools.status.inputSchema,
|
|
107
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderStatus(value, provider) },
|
|
108
|
+
execute: (args) => provider.statusToolLogic(args)
|
|
109
|
+
}))
|
|
110
|
+
for (const et of provider.extraTools || []) {
|
|
111
|
+
ctx.effect(() => ctx.tools.register({
|
|
112
|
+
name: et.name,
|
|
113
|
+
description: et.description,
|
|
114
|
+
parameters: et.inputSchema,
|
|
115
|
+
output: { schema: OUTPUT_SCHEMA, render: et.render || ((args, value) => renderResult(value, provider)) },
|
|
116
|
+
execute: (args, exec) => et.logic(args, exec)
|
|
117
|
+
}))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// policy 段(新 provider 无旧文案可 verbatim 继承;按实测 CLI 能力撰写)。
|
|
121
|
+
const policyText = [
|
|
122
|
+
'codex-first execution policy (local OpenAI Codex CLI bridge).',
|
|
123
|
+
'',
|
|
124
|
+
'Local agent CLI "codex" handles coding tasks under full DSH control. Every dispatch is `codex exec --json --skip-git-repo-check` (never interactive, so approvals cannot block). By default codex runs in the background and returns a jobId immediately; collect results via job_output and watch live progress via codex_status anytime.',
|
|
125
|
+
'',
|
|
126
|
+
'Decision table for tool selection:',
|
|
127
|
+
'- Single read/glob/grep or short (<30s) command -> Native tools.',
|
|
128
|
+
'- Implementing, editing, refactoring, debugging, builds, multi-file investigation -> codex_run.',
|
|
129
|
+
'- Follow-ups on the same topic -> codex_continue (with latest=true or sessionId; codex keeps the thread).',
|
|
130
|
+
'- Final build/test verification of codex output -> Native tools.',
|
|
131
|
+
'',
|
|
132
|
+
'Context protocol (one topic = one codex thread):',
|
|
133
|
+
'- First dispatch on a topic: provide the task with a compact CONTEXT preamble (point to paths, do not paste file contents):',
|
|
134
|
+
' CONTEXT: 2-6 lines — goal; repo root; key paths; decisions/constraints; current state.',
|
|
135
|
+
' TASK: the complete instruction.',
|
|
136
|
+
'- Follow-ups: call codex_continue and state only what is new.',
|
|
137
|
+
'',
|
|
138
|
+
'Sandbox & approval:',
|
|
139
|
+
'- mode=auto follows the DSH plan state: plan -> read-only sandbox, otherwise workspace-write.',
|
|
140
|
+
'- codex exec has NO -a/--ask-for-approval flag; the bridge sets approval policy through -c approval_policy="…" only when the settings panel defines one. On resume the sandbox also goes through -c ("codex exec resume" has no -s and no -C).',
|
|
141
|
+
'- If a run fails because the sandbox denied an operation, raise sandboxMode (workspace-write -> danger-full-access) instead of looping retries.',
|
|
142
|
+
'',
|
|
143
|
+
'Execution & Fallback protocol:',
|
|
144
|
+
'- Dispatch defaults to background=true (set background=false only to block). Collect via job_output.',
|
|
145
|
+
'- On rate-limit or network failure, a confirmation dialog opens in BOTH foreground and background while in flight.',
|
|
146
|
+
'- If result has fallback=true (status FALLBACK_TO_DSH), the user chose DSH-local: finish with native tools and NEVER call codex again for this task.',
|
|
147
|
+
'- Never loop codex calls; never ask codex to call back into DSH.',
|
|
148
|
+
'',
|
|
149
|
+
'Models & credentials:',
|
|
150
|
+
'- Run codex_models (default source=live) to see what the configured endpoint really serves; ~/.codex/models.json and model_catalog_json are local catalog metadata, NOT a claim about server availability.',
|
|
151
|
+
'- Omit model unless you have a reason: codex then uses the CLI Bridge settings default, or ~/.codex/config.toml model.',
|
|
152
|
+
'- Credentials belong to codex (~/.codex/config.toml model_providers.<name>, auth.json): on auth failures tell the user to run `codex login` or fix the provider block; do not retry in a loop.'
|
|
153
|
+
].join('\n')
|
|
154
|
+
|
|
155
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
156
|
+
name: 'codex:policy',
|
|
157
|
+
order: (typeof ctx.systemPrompt.getSectionOrder === 'function' ? ctx.systemPrompt.getSectionOrder('TOOL_SUBAGENT') : 2800) + 10,
|
|
158
|
+
text: policyText
|
|
159
|
+
}))
|
|
160
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// preset/codex-first/plugin-list.mjs — codex-first 预设的单源插件清单(PROVIDER-CONTRACT §5)。
|
|
2
|
+
//
|
|
3
|
+
// = 官方 standard 基线(standard-base.mjs)+ codex 桥接行 + persona 差异。
|
|
4
|
+
// preset id 直接用 'codex-first'(与 opencode-first 同构):本包新增的 provider 无旧包
|
|
5
|
+
// 兼容包袱,不套用 agy 的 cordis-agy / mimo 的 cordis-mimo 旧前缀。
|
|
6
|
+
import { standardPluginRows, bridgeRow } from '../standard-base.mjs'
|
|
7
|
+
|
|
8
|
+
export const meta = {
|
|
9
|
+
id: 'codex-first',
|
|
10
|
+
name: 'Codex 执行代理',
|
|
11
|
+
description: '完整编码 Agent(standard 全部能力),额外提供 codex_run/codex_continue 工具并注入"Codex 优先"策略:各种模式下先把实现、编辑、调试、构建、跨文件排查派给本机 OpenAI Codex CLI(DSH 完全控制、非交互不弹审批、默认后台执行),原生工具仅用于只读查询与最终验证。',
|
|
12
|
+
order: 50
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildPlugins(deps) {
|
|
16
|
+
const rows = standardPluginRows()
|
|
17
|
+
// persona 段:prefix 与其它 preset 同构(dsh-persona 要求 prefix 必填)。
|
|
18
|
+
// 不设置 suffix —— DSH 只注册 provider/model 两个模板变量,工作目录由
|
|
19
|
+
// working-directory 服务以「不插值」的用户上下文提供;写 {{cwd}} 会让本预设整轮
|
|
20
|
+
// 组装失败(2026-10-18 实测,别重蹈覆辙)。
|
|
21
|
+
const persona = rows.find((r) => r.id === 'persona')
|
|
22
|
+
if (persona && persona.config) {
|
|
23
|
+
persona.config.prefix = 'You are a coding agent powered by the {{model}} model.'
|
|
24
|
+
delete persona.config.suffix
|
|
25
|
+
}
|
|
26
|
+
rows.push(bridgeRow('codex', deps && deps.bridgeName))
|
|
27
|
+
return rows
|
|
28
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
- id: persona
|
|
5
5
|
name: '@deepseek-ai/dsh-persona'
|
|
6
6
|
config:
|
|
7
|
-
suffix: 'Your working directory is {{cwd}}.'
|
|
8
7
|
prefix: 'You are a coding agent powered by the {{model}} model.'
|
|
9
8
|
- id: agent-instructions
|
|
10
9
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
@@ -86,22 +85,18 @@
|
|
|
86
85
|
provider: spawn
|
|
87
86
|
toolName: subagent
|
|
88
87
|
modelSelectionSettings: true
|
|
89
|
-
|
|
90
|
-
persona: 'Delegation directive: you are a child agent in a MiMo-first session. Before doing any coding, building, debugging, or multi-file investigation work yourself, check whether the mimo_run tool (or the MCP form mcp__mimo__mimo_run) is available to you; if it is, dispatch that work to mimo first per the MiMo-first policy, then verify its output with your own read-only tools. Reserve your own direct work for quick read-only lookups and final verification.'
|
|
88
|
+
agentPreset: cordis-mimo-member
|
|
91
89
|
- id: tool-subagent-fork
|
|
92
90
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
93
91
|
config:
|
|
94
92
|
provider: fork
|
|
95
93
|
toolName: subagent_fork
|
|
96
|
-
backgroundMode: continuable
|
|
97
|
-
persona: 'Delegation directive: you are a child agent in a MiMo-first session. Before doing any coding, building, debugging, or multi-file investigation work yourself, check whether the mimo_run tool (or the MCP form mcp__mimo__mimo_run) is available to you; if it is, dispatch that work to mimo first per the MiMo-first policy, then verify its output with your own read-only tools. Reserve your own direct work for quick read-only lookups and final verification.'
|
|
98
94
|
- id: tool-subagent-codex
|
|
99
95
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
100
96
|
disabled: true
|
|
101
97
|
config:
|
|
102
98
|
provider: codex
|
|
103
99
|
toolName: subagent_codex
|
|
104
|
-
backgroundMode: one-shot
|
|
105
100
|
maxDepth: provider-managed
|
|
106
101
|
- id: tool-subagent-claude-code
|
|
107
102
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
@@ -109,7 +104,6 @@
|
|
|
109
104
|
config:
|
|
110
105
|
provider: claude-code
|
|
111
106
|
toolName: subagent_claude_code
|
|
112
|
-
backgroundMode: one-shot
|
|
113
107
|
maxDepth: provider-managed
|
|
114
108
|
- id: workflow-ptc
|
|
115
109
|
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// preset/mimo-first/plugin-list.mjs — cordis-mimo 预设的单源插件清单(PROVIDER-CONTRACT §5)。
|
|
2
2
|
//
|
|
3
|
-
// = 官方 standard 基线(standard-base.mjs)+ mimo 桥接行 +
|
|
4
|
-
// preset/mimo-first/agent.cordis.yml 的 persona 与 tool-subagent 委派文案 verbatim)。
|
|
3
|
+
// = 官方 standard 基线(standard-base.mjs)+ mimo 桥接行 + 委派差异。
|
|
5
4
|
// preset id 'cordis-mimo' 是旧会话头引用,不可变。
|
|
6
5
|
import { standardPluginRows, bridgeRow } from '../standard-base.mjs'
|
|
7
6
|
|
|
@@ -12,26 +11,33 @@ export const meta = {
|
|
|
12
11
|
order: 10
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* 子 agent 所用的成员预设 id(人格与工具面在 preset/mimo-member/plugin-list.mjs)。
|
|
16
|
+
* 0.2.1 起角色人格不能再写在 dsh-tool-subagent 行上,只能由 agentPreset 指向独立预设。
|
|
17
|
+
*/
|
|
18
|
+
export const MEMBER_PRESET_ID = 'cordis-mimo-member'
|
|
18
19
|
|
|
19
20
|
export function buildPlugins(deps) {
|
|
20
21
|
const rows = standardPluginRows()
|
|
21
|
-
// persona
|
|
22
|
-
//
|
|
22
|
+
// persona 行:prefix 与官方 standard 逐字一致({{model}})。
|
|
23
|
+
// 2026-10-18:不再设置 suffix —— DSH 只注册 provider/model 两个变量,工作目录由
|
|
24
|
+
// working-directory 服务以不插值的用户上下文提供,{{cwd}} 会让本预设整轮组装失败。
|
|
23
25
|
const persona = rows.find((r) => r.id === 'persona')
|
|
24
26
|
if (persona && persona.config) {
|
|
25
27
|
persona.config.prefix = 'You are a coding agent powered by the {{model}} model.'
|
|
26
|
-
persona.config.suffix
|
|
28
|
+
delete persona.config.suffix
|
|
27
29
|
}
|
|
28
|
-
//
|
|
30
|
+
// 委派组:0.2.1 起人格与工具面属于【独立成员预设】,委派行只用 agentPreset 选择它。
|
|
29
31
|
const delegation = rows.find((r) => r.id === 'delegation')
|
|
30
32
|
if (delegation && Array.isArray(delegation.config)) {
|
|
31
33
|
for (const child of delegation.config) {
|
|
32
|
-
if (child
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
if (!child || child.name !== '@deepseek-ai/dsh-tool-subagent' || !child.config) continue
|
|
35
|
+
// persona/toolFilter 会被 dsh-tool-subagent 的 apply() 直接 throw,写进去就是
|
|
36
|
+
// 整个预设挂载失败 —— 一律删除(人格已迁到成员预设)。
|
|
37
|
+
delete child.config.persona
|
|
38
|
+
delete child.config.toolFilter
|
|
39
|
+
// agentPreset 只有 in-process spawn 支持;fork 与外部后端会拒绝它,故只给 spawn。
|
|
40
|
+
if (child.config.provider === 'spawn') child.config.agentPreset = MEMBER_PRESET_ID
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
rows.push(bridgeRow('mimo', deps && deps.bridgeName))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// preset/mimo-member/plugin-list.mjs — cordis-mimo 的【成员预设】单源清单(PROVIDER-CONTRACT §5)。
|
|
2
|
+
//
|
|
3
|
+
// 为什么需要它(2026-10-18 实测结论):
|
|
4
|
+
// dsh-tool-subagent 0.2.1 起,config 里的 `persona` / `toolFilter` 会在 apply() 里
|
|
5
|
+
// **显式 throw**("tool-subagent: persona is no longer supported; configure persona
|
|
6
|
+
// and tool plugins in an Agent Preset and select it with agentPreset (spawn only)")。
|
|
7
|
+
// mimo 预设此前把委派指令写在 tool-subagent.config.persona 上 ⇒ 整个 preset 挂载失败
|
|
8
|
+
// (与 dsh-kexi-crypto v1.9.21 踩的是同一个坑)。
|
|
9
|
+
// 新契约:角色人格只能是**独立的 Agent Preset**,由委派行经 `agentPreset` 选择。
|
|
10
|
+
//
|
|
11
|
+
// 本预设就是那个成员预设:人格(旧委派指令 verbatim)+ mimo 桥接工具面(子 agent 必须
|
|
12
|
+
// 能调 mimo_run,否则"派活"无从谈起)+ 只读文件面(够做只读查询与最终验证)。
|
|
13
|
+
// 由 preset-declarations.mjs 在**主预设之前**注册 —— 委派行的 validate() 会对每个
|
|
14
|
+
// agentPreset id 调 presets.readDocument(id),未注册即抛 agent-preset/not-found。
|
|
15
|
+
//
|
|
16
|
+
// 它不是独立会话入口:直接选择它只会得到一个只能派活、不能自己改文件的子代理。
|
|
17
|
+
import { bridgeRow } from '../standard-base.mjs'
|
|
18
|
+
|
|
19
|
+
/** Preset identity —— 旧会话头不可引用它(成员预设是本次新增),但委派行按此 id 选择。 */
|
|
20
|
+
export const meta = {
|
|
21
|
+
id: 'cordis-mimo-member',
|
|
22
|
+
name: 'MiMo 子代理(内部预设·勿直接选择)',
|
|
23
|
+
description: 'MiMo-first 会话里被委派的子 agent 所用成员预设:只挂 mimo 桥接工具与只读文件面,人格要求把编码工作派给 mimo_run。仅供 cordis-mimo 的委派行经 agentPreset 选择,不是独立会话入口;直接选择它只会得到一个只能派活、不能自己改文件的子代理。',
|
|
24
|
+
order: 501
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 旧 agent.cordis.yml 的 spawn/fork 委派指令文案 verbatim(0.2.1 起只能放进 persona)。 */
|
|
28
|
+
export const MIMO_DELEGATION_PERSONA = 'Delegation directive: you are a child agent in a MiMo-first session. Before doing any coding, building, debugging, or multi-file investigation work yourself, check whether the mimo_run tool (or the MCP form mcp__mimo__mimo_run) is available to you; if it is, dispatch that work to mimo first per the MiMo-first policy, then verify its output with your own read-only tools. Reserve your own direct work for quick read-only lookups and final verification.'
|
|
29
|
+
|
|
30
|
+
export function buildPlugins(deps) {
|
|
31
|
+
return [
|
|
32
|
+
// 人格:委派指令。prefix 必填;suffix 不设(DSH 只注册 provider/model 两个提示词变量,
|
|
33
|
+
// 工作目录由 working-directory 服务以不插值的用户上下文提供)。
|
|
34
|
+
{ id: 'persona', name: '@deepseek-ai/dsh-persona', config: {
|
|
35
|
+
prefix: MIMO_DELEGATION_PERSONA,
|
|
36
|
+
includeRuntimeContext: true,
|
|
37
|
+
} },
|
|
38
|
+
// 只读文件面:旧 toolFilter 白名单的新表达 —— 写/改工具直接不存在,比黑名单更严格
|
|
39
|
+
// (成员纪律:改文件一律经 mimo_run)。
|
|
40
|
+
{ id: 'tool-fs', name: '@deepseek-ai/dsh-tool-fs', config: {
|
|
41
|
+
read: true, readImage: true, write: false, edit: false,
|
|
42
|
+
} },
|
|
43
|
+
{ id: 'tool-fs-search', name: '@deepseek-ai/dsh-tool-fs-search', config: {
|
|
44
|
+
sampleOverCapGlobResults: false,
|
|
45
|
+
} },
|
|
46
|
+
// 桥接工具面:子 agent 必须能调 mimo_run/mimo_continue/ mimo_status。
|
|
47
|
+
bridgeRow('mimo', deps && deps.bridgeName),
|
|
48
|
+
]
|
|
49
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
- id: persona
|
|
5
5
|
name: '@deepseek-ai/dsh-persona'
|
|
6
6
|
config:
|
|
7
|
-
suffix: 'Your working directory is {{cwd}}.'
|
|
8
7
|
prefix: 'You are a coding agent powered by the {{model}} model.'
|
|
9
8
|
- id: agent-instructions
|
|
10
9
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
@@ -86,20 +85,17 @@
|
|
|
86
85
|
provider: spawn
|
|
87
86
|
toolName: subagent
|
|
88
87
|
modelSelectionSettings: true
|
|
89
|
-
backgroundMode: continuable
|
|
90
88
|
- id: tool-subagent-fork
|
|
91
89
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
92
90
|
config:
|
|
93
91
|
provider: fork
|
|
94
92
|
toolName: subagent_fork
|
|
95
|
-
backgroundMode: continuable
|
|
96
93
|
- id: tool-subagent-codex
|
|
97
94
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
98
95
|
disabled: true
|
|
99
96
|
config:
|
|
100
97
|
provider: codex
|
|
101
98
|
toolName: subagent_codex
|
|
102
|
-
backgroundMode: one-shot
|
|
103
99
|
maxDepth: provider-managed
|
|
104
100
|
- id: tool-subagent-claude-code
|
|
105
101
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
@@ -107,7 +103,6 @@
|
|
|
107
103
|
config:
|
|
108
104
|
provider: claude-code
|
|
109
105
|
toolName: subagent_claude_code
|
|
110
|
-
backgroundMode: one-shot
|
|
111
106
|
maxDepth: provider-managed
|
|
112
107
|
- id: workflow-ptc
|
|
113
108
|
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
@@ -15,11 +15,13 @@ export const meta = {
|
|
|
15
15
|
|
|
16
16
|
export function buildPlugins(deps) {
|
|
17
17
|
const rows = standardPluginRows()
|
|
18
|
-
// persona
|
|
18
|
+
// persona 段:prefix 与三家旧 preset 同构(dsh-persona 要求 prefix 必填)。
|
|
19
|
+
// 2026-10-18:不再设置 suffix —— DSH 只注册 provider/model 两个变量,工作目录由
|
|
20
|
+
// working-directory 服务以不插值的用户上下文提供,{{cwd}} 会让本预设整轮组装失败。
|
|
19
21
|
const persona = rows.find((r) => r.id === 'persona')
|
|
20
22
|
if (persona && persona.config) {
|
|
21
23
|
persona.config.prefix = 'You are a coding agent powered by the {{model}} model.'
|
|
22
|
-
persona.config.suffix
|
|
24
|
+
delete persona.config.suffix
|
|
23
25
|
}
|
|
24
26
|
rows.push(bridgeRow('opencode', deps && deps.bridgeName))
|
|
25
27
|
return rows
|
package/preset/standard-base.mjs
CHANGED
|
@@ -26,13 +26,16 @@ export const PLAN_MODE_SECTION = [
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* 官方 standard preset 的插件行表(不含 persona 差异与桥接行)。
|
|
29
|
-
* persona 行返回【副本】供 provider 修改 prefix
|
|
29
|
+
* persona 行返回【副本】供 provider 修改 prefix;其余行按原样返回。
|
|
30
30
|
* @returns {object[]}
|
|
31
31
|
*/
|
|
32
32
|
export function standardPluginRows() {
|
|
33
33
|
return [
|
|
34
|
+
// persona 只留 prefix。DSH 的提示词循环只注册 provider/model 两个变量
|
|
35
|
+
// (dsh-agent-loop),工作目录由 working-directory 服务以「不执行插值的用户
|
|
36
|
+
// 上下文」提供。写 {{cwd}} 会在组装期抛 unknown prompt variable,导致整个
|
|
37
|
+
// 预设不可用(2026-10-18 实测)。官方 presets 全部不带 suffix。
|
|
34
38
|
{ id: 'persona', name: '@deepseek-ai/dsh-persona', config: {
|
|
35
|
-
suffix: 'Your working directory is {{cwd}}.',
|
|
36
39
|
prefix: 'You are a coding agent powered by the {{model}} model.',
|
|
37
40
|
} },
|
|
38
41
|
{ id: 'agent-instructions', name: '@deepseek-ai/dsh-agent-instructions', config: { maxBytes: 65536 } },
|
|
@@ -66,20 +69,29 @@ export function standardPluginRows() {
|
|
|
66
69
|
{
|
|
67
70
|
id: 'delegation', name: 'cordis:group', group: true,
|
|
68
71
|
isolate: { workflowEngine: true },
|
|
72
|
+
// 2026-10-18:移除 backgroundMode —— dsh-tool-subagent 0.2.1(当前 0.2.1-alpha.5)
|
|
73
|
+
// 已删除该字段。机制经实装包双路径实测更正:未知/已删键会**通过 schema 校验并被
|
|
74
|
+
// 原样保留**(`Config['~standard'].validate({provider,backgroundMode})` 无 issues,
|
|
75
|
+
// 值幸存;schemastery 根本没有 strict 开关),只是**无人读取**该键
|
|
76
|
+
// (全 DSH 树对 backgroundMode 0 命中)⇒ 属「静默**忽略**」,不是「静默丢弃」。
|
|
77
|
+
// 危害:表面声明 continuable/one-shot 却毫无效果,且**运行时不报错**,
|
|
78
|
+
// 只能靠静态闸门(tests/keystone/persona-prompt-vars.test.mjs)拦。
|
|
79
|
+
// 当前 schema 接受:provider / toolName / modelSelectionSettings /
|
|
80
|
+
// agentOptions / agentPreset / allowedAgentPresets / maxDepth。
|
|
69
81
|
config: [
|
|
70
82
|
{ id: 'tool-subagent-control', name: '@deepseek-ai/dsh-tool-subagent-control' },
|
|
71
83
|
{ id: 'tool-subagent-list-agents', name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' },
|
|
72
84
|
{ id: 'tool-subagent', name: '@deepseek-ai/dsh-tool-subagent', config: {
|
|
73
|
-
provider: 'spawn', toolName: 'subagent', modelSelectionSettings: true,
|
|
85
|
+
provider: 'spawn', toolName: 'subagent', modelSelectionSettings: true,
|
|
74
86
|
} },
|
|
75
87
|
{ id: 'tool-subagent-fork', name: '@deepseek-ai/dsh-tool-subagent', config: {
|
|
76
|
-
provider: 'fork', toolName: 'subagent_fork',
|
|
88
|
+
provider: 'fork', toolName: 'subagent_fork',
|
|
77
89
|
} },
|
|
78
90
|
{ id: 'tool-subagent-codex', name: '@deepseek-ai/dsh-tool-subagent', disabled: true, config: {
|
|
79
|
-
provider: 'codex', toolName: 'subagent_codex',
|
|
91
|
+
provider: 'codex', toolName: 'subagent_codex', maxDepth: 'provider-managed',
|
|
80
92
|
} },
|
|
81
93
|
{ id: 'tool-subagent-claude-code', name: '@deepseek-ai/dsh-tool-subagent', disabled: true, config: {
|
|
82
|
-
provider: 'claude-code', toolName: 'subagent_claude_code',
|
|
94
|
+
provider: 'claude-code', toolName: 'subagent_claude_code', maxDepth: 'provider-managed',
|
|
83
95
|
} },
|
|
84
96
|
{ id: 'workflow-ptc', name: '@deepseek-ai/dsh-workflow-ptc', config: { provider: 'spawn' } },
|
|
85
97
|
{ id: 'tool-workflow', name: '@deepseek-ai/dsh-tool-workflow' },
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// providers/claude-exe.mjs — claude 可执行文件探测/包装(无宿主依赖的纯逻辑片)。
|
|
2
|
+
//
|
|
3
|
+
// 从 providers/claude.mjs 拆出,让两个文件都留在 600 行红线内(规则 §26)。
|
|
4
|
+
// scripts/build.mjs 会把 providers/claude-*.mjs 按文件名字典序内联进 dynamic 沙箱的
|
|
5
|
+
// 同一个作用域,所以沙箱里这些函数天然可见;ESM 形态走本文件的 import。
|
|
6
|
+
// ⚠️ 注释里绝不写出口令标记字面量(build 全局替换会把注释劈开 → 生成物语法崩)。
|
|
7
|
+
//
|
|
8
|
+
// 本机实测事实(Claude Code 2.0.76,Windows):
|
|
9
|
+
// • PATH 上的 `claude` 是原生 exe(.cherrystudio\bin\claude.exe);官方原生 installer
|
|
10
|
+
// 给 claude.exe,npm 全局安装给 claude.cmd + node_modules/@anthropic-ai/claude-code/cli.js。
|
|
11
|
+
// • Windows 上 Node 的 spawn 不能直接执行 .cmd/.bat(CreateProcess 只补 .exe),
|
|
12
|
+
// 必须 shell:true;.js/.mjs 目标必须经 node 包装。这两条决定下面的 splitExeSpec。
|
|
13
|
+
|
|
14
|
+
// 沙箱可能没有 fs/path:一律经 getBuiltinModule + typeof 守卫读取,读不到就退保守值。
|
|
15
|
+
function claudeBuiltin(name) {
|
|
16
|
+
try {
|
|
17
|
+
const proc = globalThis.process
|
|
18
|
+
return (proc && typeof proc.getBuiltinModule === 'function') ? proc.getBuiltinModule(name) : null
|
|
19
|
+
} catch (e) { return null }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// PATH 兜底名(Windows 由 CreateProcess 自动补 .exe)。
|
|
23
|
+
export const CLAUDE_EXE_BASENAME = 'claude'
|
|
24
|
+
|
|
25
|
+
// 用户可以显式指定可执行文件的既有环境变量(按优先级)。
|
|
26
|
+
export const CLAUDE_BIN_ENV_VARS = ['CLAUDE_BIN', 'CLAUDE_CLI', 'CLAUDE_CODE_BIN']
|
|
27
|
+
|
|
28
|
+
// 已知的本地安装位置(native installer / 官方 local 目录);存在才收,不做版本猜测。
|
|
29
|
+
const CLAUDE_LOCAL_REL = [
|
|
30
|
+
['.claude', 'local', 'claude.exe'],
|
|
31
|
+
['.claude', 'local', 'claude'],
|
|
32
|
+
['.local', 'bin', 'claude.exe'],
|
|
33
|
+
['.local', 'bin', 'claude']
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 把「一个可执行文件路径」拆成 spawn 需要的形态。
|
|
38
|
+
* .js/.cjs/.mjs → 经当前 node 启动(测试 fixture 与 npm 安装的 cli.js 都是这形态);
|
|
39
|
+
* .cmd/.bat → 只能 shell 执行(Windows 限制),调用方必须带 shell:true;
|
|
40
|
+
* 其余 → 直接执行(原生 exe / PATH 名字)。
|
|
41
|
+
* @param {string} exe
|
|
42
|
+
* @returns {{cmd: string, prefixArgs: string[], shell: boolean, kind: string}}
|
|
43
|
+
*/
|
|
44
|
+
export function splitExeSpec(exe) {
|
|
45
|
+
const s = String(exe || '')
|
|
46
|
+
if (/\.(mjs|cjs|js)$/i.test(s)) {
|
|
47
|
+
const proc = globalThis.process
|
|
48
|
+
const node = (proc && proc.execPath) ? proc.execPath : 'node'
|
|
49
|
+
return { cmd: node, prefixArgs: [s], shell: false, kind: 'node' }
|
|
50
|
+
}
|
|
51
|
+
if (/\.(cmd|bat)$/i.test(s)) return { cmd: s, prefixArgs: [], shell: true, kind: 'shell' }
|
|
52
|
+
return { cmd: s, prefixArgs: [], shell: false, kind: 'binary' }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** shell:true 下的命令行拼装(只用于参数集合固定的探测调用,不拼装用户 prompt)。 */
|
|
56
|
+
export function quoteShellArg(a) {
|
|
57
|
+
const s = String(a)
|
|
58
|
+
return /[\s"&|<>^()]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 候选可执行文件列表(顺序 = 优先级):设置面板 binPath > env 变量 > 已知本地安装 > PATH 名字。
|
|
63
|
+
* 纯函数、零副作用;无 fs 能力时只返回设置/env/PATH 名,绝不抛。
|
|
64
|
+
* @param {object} env 环境变量
|
|
65
|
+
* @param {string} [binPath] 设置面板里显式填写的路径
|
|
66
|
+
* @returns {string[]}
|
|
67
|
+
*/
|
|
68
|
+
export function claudeExeCandidates(env, binPath) {
|
|
69
|
+
const e = env || {}
|
|
70
|
+
const out = []
|
|
71
|
+
const push = (p) => { const v = String(p || '').trim(); if (v && out.indexOf(v) < 0) out.push(v) }
|
|
72
|
+
if (binPath) push(binPath)
|
|
73
|
+
for (const k of CLAUDE_BIN_ENV_VARS) if (e[k]) push(e[k])
|
|
74
|
+
const fs = claudeBuiltin('node:fs')
|
|
75
|
+
const path = claudeBuiltin('node:path')
|
|
76
|
+
const home = e.USERPROFILE || e.HOME || ''
|
|
77
|
+
if (fs && path && typeof fs.existsSync === 'function' && typeof path.join === 'function' && home) {
|
|
78
|
+
for (const rel of CLAUDE_LOCAL_REL) {
|
|
79
|
+
const p = path.join.apply(path, [home].concat(rel))
|
|
80
|
+
try { if (fs.existsSync(p)) push(p) } catch (err) { }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
push(CLAUDE_EXE_BASENAME)
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 探测时优先尝试的候选(去掉 PATH 兜底名——它由 spawn 的 PATH 搜索处理,且不是绝对路径)。
|
|
89
|
+
* @returns {string[]}
|
|
90
|
+
*/
|
|
91
|
+
export function claudeProbeCandidates(env, binPath) {
|
|
92
|
+
return claudeExeCandidates(env, binPath).filter((c) => c !== CLAUDE_EXE_BASENAME)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* buildArgv 需要的前缀数组:设置 binPath > env > 本地安装 > PATH 名字。
|
|
97
|
+
* .js/.mjs 目标返回 [node, file]。宿主形态(preset/dynamic)也会用它做 existsSync 校验。
|
|
98
|
+
* @returns {string[]}
|
|
99
|
+
*/
|
|
100
|
+
export function resolveExePrefix(env, binPath) {
|
|
101
|
+
const cands = claudeExeCandidates(env, binPath)
|
|
102
|
+
const spec = splitExeSpec(cands[0] || CLAUDE_EXE_BASENAME)
|
|
103
|
+
return [spec.cmd].concat(spec.prefixArgs)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** PATH 里的 claude 是否真的可执行(仅诊断用:返回路径或空串)。 */
|
|
107
|
+
export function claudePathHint(env) {
|
|
108
|
+
const e = env || {}
|
|
109
|
+
for (const k of CLAUDE_BIN_ENV_VARS) if (e[k]) return String(e[k])
|
|
110
|
+
const cands = claudeExeCandidates(e, '')
|
|
111
|
+
return cands.length ? String(cands[0]) : ''
|
|
112
|
+
}
|