@luchenglong/cli-bridge 0.2.8 → 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 +49 -0
- package/PROVIDER-CONTRACT.md +53 -4
- package/README.md +12 -11
- 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 +2 -3
- package/mcp/cli-bridge-mcp.mjs +5 -3
- package/mcp/providers/claude.mjs +231 -0
- package/mcp/providers/codex.mjs +239 -0
- package/package.json +6 -2
- 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/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/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,136 @@
|
|
|
1
|
+
# 生成物 — 由 node scripts/build.mjs 从 preset/claude-first/plugin-list.mjs 生成,勿手改。
|
|
2
|
+
# legacy 目录形态($DSH_HOME/.agent-presets/claude-first/);新 DSH 走声明制
|
|
3
|
+
# (home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs 直接 import 同一源)。
|
|
4
|
+
- id: persona
|
|
5
|
+
name: '@deepseek-ai/dsh-persona'
|
|
6
|
+
config:
|
|
7
|
+
prefix: 'You are a coding agent powered by the {{model}} model.'
|
|
8
|
+
- id: agent-instructions
|
|
9
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
10
|
+
config:
|
|
11
|
+
maxBytes: 65536
|
|
12
|
+
- id: tool-bash
|
|
13
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
14
|
+
disabled: !!js 'process.platform === ''win32'''
|
|
15
|
+
- id: tool-pwsh
|
|
16
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
17
|
+
disabled: !!js 'process.platform !== ''win32'''
|
|
18
|
+
- id: tool-fs
|
|
19
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
20
|
+
- id: tool-fs-search
|
|
21
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
22
|
+
config:
|
|
23
|
+
sampleOverCapGlobResults: false
|
|
24
|
+
- id: tool-jobs
|
|
25
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
26
|
+
- id: skill-filesystem
|
|
27
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
28
|
+
- id: tool-skill
|
|
29
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
30
|
+
- id: command-goal
|
|
31
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
32
|
+
- id: tool-goal
|
|
33
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
34
|
+
- id: planning
|
|
35
|
+
name: 'cordis:group'
|
|
36
|
+
group: true
|
|
37
|
+
isolate:
|
|
38
|
+
planMode: true
|
|
39
|
+
config:
|
|
40
|
+
- id: plan-mode
|
|
41
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
42
|
+
config:
|
|
43
|
+
section: |
|
|
44
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
45
|
+
|
|
46
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
47
|
+
|
|
48
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
49
|
+
|
|
50
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
51
|
+
|
|
52
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
53
|
+
|
|
54
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
55
|
+
- id: compaction
|
|
56
|
+
name: 'cordis:group'
|
|
57
|
+
group: true
|
|
58
|
+
isolate:
|
|
59
|
+
compaction: true
|
|
60
|
+
toolResultPruner: true
|
|
61
|
+
config:
|
|
62
|
+
- id: compaction-basic
|
|
63
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
64
|
+
- id: command-compact
|
|
65
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
66
|
+
- id: tool-result-pruner
|
|
67
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
68
|
+
config:
|
|
69
|
+
thresholdChars: 8192
|
|
70
|
+
headChars: 4096
|
|
71
|
+
tailChars: 1024
|
|
72
|
+
- id: delegation
|
|
73
|
+
name: 'cordis:group'
|
|
74
|
+
group: true
|
|
75
|
+
isolate:
|
|
76
|
+
workflowEngine: true
|
|
77
|
+
config:
|
|
78
|
+
- id: tool-subagent-control
|
|
79
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
80
|
+
- id: tool-subagent-list-agents
|
|
81
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
82
|
+
- id: tool-subagent
|
|
83
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
84
|
+
config:
|
|
85
|
+
provider: spawn
|
|
86
|
+
toolName: subagent
|
|
87
|
+
modelSelectionSettings: true
|
|
88
|
+
- id: tool-subagent-fork
|
|
89
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
90
|
+
config:
|
|
91
|
+
provider: fork
|
|
92
|
+
toolName: subagent_fork
|
|
93
|
+
- id: tool-subagent-codex
|
|
94
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
95
|
+
disabled: true
|
|
96
|
+
config:
|
|
97
|
+
provider: codex
|
|
98
|
+
toolName: subagent_codex
|
|
99
|
+
maxDepth: provider-managed
|
|
100
|
+
- id: tool-subagent-claude-code
|
|
101
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
102
|
+
disabled: true
|
|
103
|
+
config:
|
|
104
|
+
provider: claude-code
|
|
105
|
+
toolName: subagent_claude_code
|
|
106
|
+
maxDepth: provider-managed
|
|
107
|
+
- id: workflow-ptc
|
|
108
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
109
|
+
config:
|
|
110
|
+
provider: spawn
|
|
111
|
+
- id: tool-workflow
|
|
112
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
113
|
+
- id: tool-ralph
|
|
114
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
115
|
+
disabled: true
|
|
116
|
+
config:
|
|
117
|
+
subagentProvider: spawn
|
|
118
|
+
maxRounds: 64
|
|
119
|
+
- id: tool-ask-user
|
|
120
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
121
|
+
- id: tool-todo
|
|
122
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
123
|
+
config:
|
|
124
|
+
allowParallelInProgress: true
|
|
125
|
+
- id: tool-web
|
|
126
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
127
|
+
config:
|
|
128
|
+
fetch: true
|
|
129
|
+
searchTimeoutMs: 60000
|
|
130
|
+
- id: present
|
|
131
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
132
|
+
- id: tool-plugin-manager
|
|
133
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
134
|
+
disabled: true
|
|
135
|
+
- id: cli-bridge-claude
|
|
136
|
+
name: '@luchenglong/cli-bridge/preset-claude'
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// preset/claude-first/cli-bridge.mjs — claude-first preset 的桥接插件(preset 宿主形态)。
|
|
2
|
+
//
|
|
3
|
+
// PROVIDER-CONTRACT.md §5:本模块只做「宿主接线」——构造描述符、attach、注册三工具 +
|
|
4
|
+
// claude_models、心跳/事件发布、claude:policy 提示词段。全部执行/状态语义在
|
|
5
|
+
// providers/claude.mjs + core/bridge-kit.mjs。
|
|
6
|
+
//
|
|
7
|
+
// preset id 'claude-first' 由 plugin-list.mjs 单源声明(本文件不含 id 字面量清单)。
|
|
8
|
+
//
|
|
9
|
+
// 本模块是宿主进程内真 ESM:可 import node: 内建与包内相对路径;禁止 import
|
|
10
|
+
// @deepseek-ai/*(解析根够不到 harness 包,历史论证 —— 契约 §8)。
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
|
|
13
|
+
import defineProvider, { claudeExeCandidates } from '../../providers/claude.mjs'
|
|
14
|
+
import { POLICY_TEXT } from '../../providers/claude-policy.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-claude'
|
|
20
|
+
export const inject = ['tools', 'subprocess', 'systemPrompt', 'timer']
|
|
21
|
+
|
|
22
|
+
const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
|
|
23
|
+
|
|
24
|
+
export function apply(ctx) {
|
|
25
|
+
// 调度门禁:用户在设置面板关掉 claude → 本形态不注册任何工具/政策段/心跳。
|
|
26
|
+
// 每次会话装配时现读文件(改设置下次会话即生效,无需重启宿主);fail-open。
|
|
27
|
+
if (!isBridgeEnabled('claude', 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 听 'claude/status',事件是 app 级广播)。
|
|
34
|
+
function publish(snapshot) {
|
|
35
|
+
try { ctx.emit('claude/status', { snapshot }) } catch (e) { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// cwd 兜底:显式 cwd 参数 > env CLAUDE_MCP_CWD > 沙箱 workspaceRoot > process.cwd()。
|
|
39
|
+
function getCwdFallback() {
|
|
40
|
+
if (process.env.CLAUDE_MCP_CWD) return String(process.env.CLAUDE_MCP_CWD)
|
|
41
|
+
if (sandboxPolicy && typeof sandboxPolicy.workspaceRoot === 'string' && sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot
|
|
42
|
+
return process.cwd()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 设置面板分区(每次 getSettings 现读 ctx.get,面板改默认模型后下一次调用即生效)。
|
|
46
|
+
function panelSettings() {
|
|
47
|
+
try {
|
|
48
|
+
const s = ctx.get && ctx.get('cliBridgeSettings')
|
|
49
|
+
return (s && s.claude) || null
|
|
50
|
+
} catch (e) { return null }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// exe 解析优先级(与描述符 claudeExeCandidates 同一套):
|
|
54
|
+
// 面板 binPath > env CLAUDE_BIN/CLAUDE_CLI/CLAUDE_CODE_BIN > 已知本地安装 > PATH 名 claude。
|
|
55
|
+
// .js/.mjs 目标经 process.execPath 包装(返回数组前缀,buildArgv 接受)。
|
|
56
|
+
// ⚠️ npm 全局安装会在 Windows 给 claude.cmd —— 本形态经 DSH subprocess 启动,.cmd 能否
|
|
57
|
+
// 执行取决于宿主;探测不到原生 exe 时如实落到 resolveExecutable,失败会明确报
|
|
58
|
+
// CLAUDE_UNAVAILABLE/SPAWN_ERROR(绝不静默改走别的后端)。
|
|
59
|
+
const binPathSetting = (() => {
|
|
60
|
+
const s = panelSettings()
|
|
61
|
+
const v = s ? s.binPath : ''
|
|
62
|
+
return typeof v === 'string' ? v : ''
|
|
63
|
+
})()
|
|
64
|
+
const exeCandidates = claudeExeCandidates(process.env, binPathSetting)
|
|
65
|
+
|
|
66
|
+
async function resolveExe(backend, signal) {
|
|
67
|
+
for (const c of exeCandidates) {
|
|
68
|
+
if (!c) continue
|
|
69
|
+
if (/\.(mjs|cjs|js)$/i.test(c)) return [process.execPath, c]
|
|
70
|
+
try { if (existsSync(c)) return c } catch (e) { }
|
|
71
|
+
}
|
|
72
|
+
return await subprocess.resolveExecutable('claude', undefined, signal)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
provider.attach({
|
|
76
|
+
ctx, subprocess, resolveExe, getCwdFallback, publish,
|
|
77
|
+
getSettings: () => {
|
|
78
|
+
const s = panelSettings()
|
|
79
|
+
return s && typeof s === 'object' ? s : {}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// 宣告 claude 优先模式:家级灯据此决定常驻显示(presetActive)。挂载即宣告 +
|
|
84
|
+
// 每 30s 心跳续租(TTL 75s,契约 §2)。
|
|
85
|
+
function announceMode() {
|
|
86
|
+
try {
|
|
87
|
+
const sid = (ctx.session && ctx.session.id) || ctx.sessionId || null
|
|
88
|
+
ctx.emit('claude/mode', sid ? { active: true, sessionId: sid } : { active: true })
|
|
89
|
+
} catch (e) { /* ignore */ }
|
|
90
|
+
}
|
|
91
|
+
announceMode()
|
|
92
|
+
try { const t = ctx.setInterval ? ctx.setInterval(announceMode, 30000) : null; if (t && ctx.effect) ctx.effect(() => () => { try { t() } catch (e) {} }) } catch (e) {}
|
|
93
|
+
|
|
94
|
+
// ── 工具注册(执行进描述符三逻辑 + extraTools)──────────────────────────────
|
|
95
|
+
ctx.effect(() => ctx.tools.register({
|
|
96
|
+
name: provider.tools.run.name,
|
|
97
|
+
description: provider.tools.run.description,
|
|
98
|
+
parameters: provider.tools.run.inputSchema,
|
|
99
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
100
|
+
execute: (args, exec) => provider.runToolLogic(args, exec)
|
|
101
|
+
}))
|
|
102
|
+
ctx.effect(() => ctx.tools.register({
|
|
103
|
+
name: provider.tools.continue.name,
|
|
104
|
+
description: provider.tools.continue.description,
|
|
105
|
+
parameters: provider.tools.continue.inputSchema,
|
|
106
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
107
|
+
execute: (args, exec) => provider.continueToolLogic(args, exec)
|
|
108
|
+
}))
|
|
109
|
+
ctx.effect(() => ctx.tools.register({
|
|
110
|
+
name: provider.tools.status.name,
|
|
111
|
+
description: provider.tools.status.description,
|
|
112
|
+
parameters: provider.tools.status.inputSchema,
|
|
113
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderStatus(value, provider) },
|
|
114
|
+
execute: (args) => provider.statusToolLogic(args)
|
|
115
|
+
}))
|
|
116
|
+
for (const et of provider.extraTools || []) {
|
|
117
|
+
ctx.effect(() => ctx.tools.register({
|
|
118
|
+
name: et.name,
|
|
119
|
+
description: et.description,
|
|
120
|
+
parameters: et.inputSchema,
|
|
121
|
+
output: { schema: OUTPUT_SCHEMA, render: et.render || ((args, value) => renderResult(value, provider)) },
|
|
122
|
+
execute: (args, exec) => et.logic(args, exec)
|
|
123
|
+
}))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
127
|
+
name: 'claude:policy',
|
|
128
|
+
order: (typeof ctx.systemPrompt.getSectionOrder === 'function' ? ctx.systemPrompt.getSectionOrder('TOOL_SUBAGENT') : 2800) + 10,
|
|
129
|
+
text: POLICY_TEXT
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// preset/claude-first/plugin-list.mjs — claude-first 预设的单源插件清单(PROVIDER-CONTRACT §5)。
|
|
2
|
+
//
|
|
3
|
+
// = 官方 standard 基线(standard-base.mjs)+ claude 桥接行 + persona 差异。
|
|
4
|
+
// preset id 'claude-first':本包新增 provider,无旧包兼容包袱,id 直接用 provider id + '-first'
|
|
5
|
+
// (与 opencode-first 同构;cordis-agy / cordis-mimo 的 cordis-* 前缀是旧包历史遗留,不可套用)。
|
|
6
|
+
import { standardPluginRows, bridgeRow } from '../standard-base.mjs'
|
|
7
|
+
|
|
8
|
+
export const meta = {
|
|
9
|
+
id: 'claude-first',
|
|
10
|
+
name: 'Claude Code 执行代理',
|
|
11
|
+
description: '完整编码 Agent(standard 全部能力),额外提供 claude_run/claude_continue 工具并注入"claude 优先"策略:各种模式下先把实现、编辑、调试、构建、跨文件排查派给本机 Claude Code CLI(DSH 完全控制、claude 无提示改文件、默认后台执行),原生工具仅用于只读查询与最终验证。模型清单以服务端权威探测为准(claude_models)。',
|
|
12
|
+
order: 50
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildPlugins(deps) {
|
|
16
|
+
const rows = standardPluginRows()
|
|
17
|
+
// persona 段:prefix 必填;**不设 suffix** —— DSH 只注册 provider/model 两个提示词变量,
|
|
18
|
+
// 写 {{cwd}} 会在组装期抛 unknown prompt variable,使整个预设不可用(工作目录由
|
|
19
|
+
// working-directory 服务以不插值的用户上下文提供)。
|
|
20
|
+
const persona = rows.find((r) => r.id === 'persona')
|
|
21
|
+
if (persona && persona.config) {
|
|
22
|
+
persona.config.prefix = 'You are a coding agent powered by the {{model}} model.'
|
|
23
|
+
delete persona.config.suffix
|
|
24
|
+
}
|
|
25
|
+
// 桥接行:第二参数由 build(scripts/build.mjs)注入裸包说明符,绝不在这里写死路径。
|
|
26
|
+
rows.push(bridgeRow('claude', deps && deps.bridgeName))
|
|
27
|
+
return rows
|
|
28
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# 生成物 — 由 node scripts/build.mjs 从 preset/codex-first/plugin-list.mjs 生成,勿手改。
|
|
2
|
+
# legacy 目录形态($DSH_HOME/.agent-presets/codex-first/);新 DSH 走声明制
|
|
3
|
+
# (home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs 直接 import 同一源)。
|
|
4
|
+
- id: persona
|
|
5
|
+
name: '@deepseek-ai/dsh-persona'
|
|
6
|
+
config:
|
|
7
|
+
prefix: 'You are a coding agent powered by the {{model}} model.'
|
|
8
|
+
- id: agent-instructions
|
|
9
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
10
|
+
config:
|
|
11
|
+
maxBytes: 65536
|
|
12
|
+
- id: tool-bash
|
|
13
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
14
|
+
disabled: !!js 'process.platform === ''win32'''
|
|
15
|
+
- id: tool-pwsh
|
|
16
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
17
|
+
disabled: !!js 'process.platform !== ''win32'''
|
|
18
|
+
- id: tool-fs
|
|
19
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
20
|
+
- id: tool-fs-search
|
|
21
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
22
|
+
config:
|
|
23
|
+
sampleOverCapGlobResults: false
|
|
24
|
+
- id: tool-jobs
|
|
25
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
26
|
+
- id: skill-filesystem
|
|
27
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
28
|
+
- id: tool-skill
|
|
29
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
30
|
+
- id: command-goal
|
|
31
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
32
|
+
- id: tool-goal
|
|
33
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
34
|
+
- id: planning
|
|
35
|
+
name: 'cordis:group'
|
|
36
|
+
group: true
|
|
37
|
+
isolate:
|
|
38
|
+
planMode: true
|
|
39
|
+
config:
|
|
40
|
+
- id: plan-mode
|
|
41
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
42
|
+
config:
|
|
43
|
+
section: |
|
|
44
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
45
|
+
|
|
46
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
47
|
+
|
|
48
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
49
|
+
|
|
50
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
51
|
+
|
|
52
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
53
|
+
|
|
54
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
55
|
+
- id: compaction
|
|
56
|
+
name: 'cordis:group'
|
|
57
|
+
group: true
|
|
58
|
+
isolate:
|
|
59
|
+
compaction: true
|
|
60
|
+
toolResultPruner: true
|
|
61
|
+
config:
|
|
62
|
+
- id: compaction-basic
|
|
63
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
64
|
+
- id: command-compact
|
|
65
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
66
|
+
- id: tool-result-pruner
|
|
67
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
68
|
+
config:
|
|
69
|
+
thresholdChars: 8192
|
|
70
|
+
headChars: 4096
|
|
71
|
+
tailChars: 1024
|
|
72
|
+
- id: delegation
|
|
73
|
+
name: 'cordis:group'
|
|
74
|
+
group: true
|
|
75
|
+
isolate:
|
|
76
|
+
workflowEngine: true
|
|
77
|
+
config:
|
|
78
|
+
- id: tool-subagent-control
|
|
79
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
80
|
+
- id: tool-subagent-list-agents
|
|
81
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
82
|
+
- id: tool-subagent
|
|
83
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
84
|
+
config:
|
|
85
|
+
provider: spawn
|
|
86
|
+
toolName: subagent
|
|
87
|
+
modelSelectionSettings: true
|
|
88
|
+
- id: tool-subagent-fork
|
|
89
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
90
|
+
config:
|
|
91
|
+
provider: fork
|
|
92
|
+
toolName: subagent_fork
|
|
93
|
+
- id: tool-subagent-codex
|
|
94
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
95
|
+
disabled: true
|
|
96
|
+
config:
|
|
97
|
+
provider: codex
|
|
98
|
+
toolName: subagent_codex
|
|
99
|
+
maxDepth: provider-managed
|
|
100
|
+
- id: tool-subagent-claude-code
|
|
101
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
102
|
+
disabled: true
|
|
103
|
+
config:
|
|
104
|
+
provider: claude-code
|
|
105
|
+
toolName: subagent_claude_code
|
|
106
|
+
maxDepth: provider-managed
|
|
107
|
+
- id: workflow-ptc
|
|
108
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
109
|
+
config:
|
|
110
|
+
provider: spawn
|
|
111
|
+
- id: tool-workflow
|
|
112
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
113
|
+
- id: tool-ralph
|
|
114
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
115
|
+
disabled: true
|
|
116
|
+
config:
|
|
117
|
+
subagentProvider: spawn
|
|
118
|
+
maxRounds: 64
|
|
119
|
+
- id: tool-ask-user
|
|
120
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
121
|
+
- id: tool-todo
|
|
122
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
123
|
+
config:
|
|
124
|
+
allowParallelInProgress: true
|
|
125
|
+
- id: tool-web
|
|
126
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
127
|
+
config:
|
|
128
|
+
fetch: true
|
|
129
|
+
searchTimeoutMs: 60000
|
|
130
|
+
- id: present
|
|
131
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
132
|
+
- id: tool-plugin-manager
|
|
133
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
134
|
+
disabled: true
|
|
135
|
+
- id: cli-bridge-codex
|
|
136
|
+
name: '@luchenglong/cli-bridge/preset-codex'
|
|
@@ -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
|
+
}
|