@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.
- package/CHANGELOG.md +148 -0
- package/LICENSE +21 -0
- package/MCP-POLICY.md +53 -0
- package/PROVIDER-CONTRACT.md +235 -0
- package/README.en.md +98 -0
- package/README.md +150 -2
- package/assets/indicator-states.svg +62 -0
- package/bin/agy-quota.mjs +223 -0
- package/core/bridge-kit.mjs +549 -0
- package/core/scheduling.mjs +149 -0
- package/docs/ARCHITECTURE.md +117 -0
- package/docs/FALLBACK-AND-INDICATOR.md +93 -0
- package/docs/INSTALL.md +118 -0
- package/docs/MCP-POLICY.zh.md +49 -0
- package/docs/MIGRATION.md +43 -0
- package/docs/RELEASE-SOP.md +77 -0
- package/docs/history/agy/CHANGELOG.md +326 -0
- package/docs/history/codebuddy/CHANGELOG.md +668 -0
- package/docs/history/mimo/CHANGELOG.md +346 -0
- package/dynamic/agy.host.js +1237 -0
- package/dynamic/agy.host.template.mjs +140 -0
- package/dynamic/codebuddy.host.js +1787 -0
- package/dynamic/codebuddy.host.template.mjs +162 -0
- package/dynamic/mimo.host.js +1732 -0
- package/dynamic/mimo.host.template.mjs +169 -0
- package/home-plugin/cli-bridge-indicator/cordis.patch.yml +31 -0
- package/home-plugin/cli-bridge-indicator/lib/client.js +606 -0
- package/home-plugin/cli-bridge-indicator/lib/index.mjs +420 -0
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +137 -0
- package/home-plugin/cli-bridge-indicator/lib/settings.mjs +300 -0
- package/mcp/agy-mcp-server.mjs +6 -0
- package/mcp/cli-bridge-mcp.mjs +184 -0
- package/mcp/codebuddy-mcp-server.mjs +6 -0
- package/mcp/mimo-mcp-server.mjs +6 -0
- package/mcp/providers/agy.mjs +254 -0
- package/mcp/providers/codebuddy.mjs +397 -0
- package/mcp/providers/mimo.mjs +290 -0
- package/package.json +87 -4
- package/preset/agy-first/agent.cordis.yml +141 -0
- package/preset/agy-first/cli-bridge.mjs +169 -0
- package/preset/agy-first/plugin-list.mjs +26 -0
- package/preset/agy-first/preset.yml +3 -0
- package/preset/codebuddy-first/agent.cordis.yml +141 -0
- package/preset/codebuddy-first/cli-bridge.mjs +188 -0
- package/preset/codebuddy-first/plugin-list.mjs +20 -0
- package/preset/codebuddy-first/preset.yml +3 -0
- package/preset/mimo-first/agent.cordis.yml +143 -0
- package/preset/mimo-first/cli-bridge.mjs +236 -0
- package/preset/mimo-first/plugin-list.mjs +39 -0
- package/preset/mimo-first/preset.yml +3 -0
- package/preset/standard-base.mjs +102 -0
- package/providers/agy.mjs +398 -0
- package/providers/codebuddy.mjs +926 -0
- package/providers/mimo.mjs +864 -0
- package/providers/registry.mjs +34 -0
- package/scripts/build.mjs +235 -0
- package/scripts/deploy.mjs +61 -0
- package/scripts/verify.mjs +148 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// preset/agy-first/cli-bridge.mjs — cordis-agy preset 的桥接插件(preset 宿主形态)。
|
|
2
|
+
//
|
|
3
|
+
// PROVIDER-CONTRACT.md §5:本模块只做「宿主接线」——构造描述符、attach、注册三工具
|
|
4
|
+
// (+agy_quota)、心跳/事件发布、agy:policy 提示词段。全部执行/状态语义在
|
|
5
|
+
// providers/agy.mjs + core/bridge-kit.mjs。
|
|
6
|
+
//
|
|
7
|
+
// 移植基线:agy-first-bridge v1.7.1 preset/agy-first/agy-first-bridge.mjs 的 apply()。
|
|
8
|
+
// preset id 'cordis-agy' 由 plugin-list.mjs 单源声明(旧会话头引用,不可变)。
|
|
9
|
+
//
|
|
10
|
+
// 本模块是宿主进程内真 ESM:可 import node: 内建与包内相对路径;禁止 import
|
|
11
|
+
// @deepseek-ai/*(解析根够不到 harness 包,历史论证 —— 契约 §8)。
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
import defineProvider from '../../providers/agy.mjs'
|
|
16
|
+
import { renderResult, renderStatus } from '../../core/bridge-kit.mjs'
|
|
17
|
+
import { isBridgeEnabled } from '../../core/scheduling.mjs'
|
|
18
|
+
import { PROVIDER_IDS } from '../../providers/registry.mjs'
|
|
19
|
+
|
|
20
|
+
export const name = 'cli-bridge-agy'
|
|
21
|
+
export const inject = ['tools', 'subprocess', 'systemPrompt', 'timer']
|
|
22
|
+
|
|
23
|
+
const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
|
|
24
|
+
|
|
25
|
+
// 额度脚本定位(契约 §8):env CLI_BRIDGE_AGY_QUOTA > AGY_QUOTA_SCRIPT(旧兼容)>
|
|
26
|
+
// 本包 bin/agy-quota.mjs(相对本模块推导;存在才注入)。描述符内部同样优先读 env。
|
|
27
|
+
function resolveQuotaScript() {
|
|
28
|
+
const e = process.env.CLI_BRIDGE_AGY_QUOTA || process.env.AGY_QUOTA_SCRIPT || ''
|
|
29
|
+
if (e && existsSync(e)) return e
|
|
30
|
+
// fileURLToPath 而非 .pathname:路径含空格("DSH Desktop")时 .pathname 给出
|
|
31
|
+
// 百分号编码(%20)导致脚本永远打不开(旧仓注释里踩过的坑,逐字保留教训)。
|
|
32
|
+
try {
|
|
33
|
+
const p = fileURLToPath(new URL('../../bin/agy-quota.mjs', import.meta.url))
|
|
34
|
+
return existsSync(p) ? p : null
|
|
35
|
+
} catch (e2) { return null }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function apply(ctx) {
|
|
39
|
+
// 调度门禁(0.2.0):用户在设置面板关掉 agy → 本形态不注册任何工具/政策段/心跳。
|
|
40
|
+
// 每次会话装配时现读文件(改设置下次会话即生效,无需重启宿主);fail-open。
|
|
41
|
+
if (!isBridgeEnabled('agy', PROVIDER_IDS)) return
|
|
42
|
+
const subprocess = ctx.subprocess
|
|
43
|
+
const planMode = ctx.get('planMode')
|
|
44
|
+
const sandboxPolicy = ctx.get('sandboxPolicy')
|
|
45
|
+
|
|
46
|
+
const provider = defineProvider({
|
|
47
|
+
process,
|
|
48
|
+
env: process.env,
|
|
49
|
+
quotaScript: resolveQuotaScript()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// 状态快照 → 家级灯(keystone 听 'agy/status',事件是 app 级广播)。
|
|
53
|
+
function publish(snapshot) {
|
|
54
|
+
try { ctx.emit('agy/status', { snapshot }) } catch (e) { /* ignore */ }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 旧仓 CWD_FALLBACK 硬编码 'C:\\Users\\lcl\\Desktop\\DSH'(机器特定,他机必坏)。
|
|
58
|
+
// 新语义:显式 cwd 参数 > env AGY_MCP_CWD > 沙箱 workspaceRoot > process.cwd()。
|
|
59
|
+
function getCwdFallback() {
|
|
60
|
+
if (process.env.AGY_MCP_CWD) return String(process.env.AGY_MCP_CWD)
|
|
61
|
+
if (sandboxPolicy && typeof sandboxPolicy.workspaceRoot === 'string' && sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot
|
|
62
|
+
return process.cwd()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 旧仓 exe 解析逻辑:AGY_BIN(旧包兼容名 AGY_CLI)> PATH 查找 agy > 报错。
|
|
66
|
+
// .js/.mjs 目标经 process.execPath 包装(返回数组前缀,buildArgv 接受)。
|
|
67
|
+
async function resolveExe(backend, signal) {
|
|
68
|
+
const bin = process.env.AGY_BIN || process.env.AGY_CLI || ''
|
|
69
|
+
if (bin) {
|
|
70
|
+
if (/\.(mjs|cjs|js)$/i.test(bin)) return [process.execPath, bin]
|
|
71
|
+
return bin
|
|
72
|
+
}
|
|
73
|
+
return await subprocess.resolveExecutable('agy', undefined, signal)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 旧 planActiveFor verbatim:DSH plan 态决定 agy 的 --mode。
|
|
77
|
+
function planActiveFor(exec) {
|
|
78
|
+
try {
|
|
79
|
+
if (planMode && exec && exec.agent) {
|
|
80
|
+
const st = planMode.get(exec.agent)
|
|
81
|
+
return !!(st && st.active)
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
provider.attach({ ctx, subprocess, resolveExe, getCwdFallback, planActiveFor, publish })
|
|
88
|
+
|
|
89
|
+
// 宣告 agy 优先模式:家级灯据此决定常驻显示(presetActive)。挂载即宣告 +
|
|
90
|
+
// 每 30s 心跳续租(TTL 75s,契约 §2)。
|
|
91
|
+
function announceMode() {
|
|
92
|
+
try {
|
|
93
|
+
const sid = (ctx.session && ctx.session.id) || ctx.sessionId || null
|
|
94
|
+
ctx.emit('agy/mode', sid ? { active: true, sessionId: sid } : { active: true })
|
|
95
|
+
} catch (e) { /* ignore */ }
|
|
96
|
+
}
|
|
97
|
+
announceMode()
|
|
98
|
+
try { const t = ctx.setInterval ? ctx.setInterval(announceMode, 30000) : null; if (t && ctx.effect) ctx.effect(() => () => { try { t() } catch (e) {} }) } catch (e) {}
|
|
99
|
+
|
|
100
|
+
// ── 工具注册(schema verbatim 旧 preset;执行进描述符三逻辑)──────────────────
|
|
101
|
+
ctx.effect(() => ctx.tools.register({
|
|
102
|
+
name: provider.tools.run.name,
|
|
103
|
+
description: provider.tools.run.description,
|
|
104
|
+
parameters: provider.tools.run.inputSchema,
|
|
105
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
106
|
+
execute: (args, exec) => provider.runToolLogic(args, exec)
|
|
107
|
+
}))
|
|
108
|
+
ctx.effect(() => ctx.tools.register({
|
|
109
|
+
name: provider.tools.continue.name,
|
|
110
|
+
description: provider.tools.continue.description,
|
|
111
|
+
parameters: provider.tools.continue.inputSchema,
|
|
112
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
113
|
+
execute: (args, exec) => provider.continueToolLogic(args, exec)
|
|
114
|
+
}))
|
|
115
|
+
ctx.effect(() => ctx.tools.register({
|
|
116
|
+
name: provider.tools.status.name,
|
|
117
|
+
description: provider.tools.status.description,
|
|
118
|
+
parameters: provider.tools.status.inputSchema,
|
|
119
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderStatus(value, provider) },
|
|
120
|
+
execute: (args) => provider.statusToolLogic(args)
|
|
121
|
+
}))
|
|
122
|
+
// agy_quota:preset 形态有 process/subprocess → 恒注册(描述符工厂决定)。
|
|
123
|
+
for (const et of provider.extraTools) {
|
|
124
|
+
ctx.effect(() => ctx.tools.register({
|
|
125
|
+
name: et.name,
|
|
126
|
+
description: et.description,
|
|
127
|
+
parameters: et.inputSchema,
|
|
128
|
+
output: { schema: OUTPUT_SCHEMA, render: et.render || ((args, value) => renderResult(value, provider)) },
|
|
129
|
+
execute: (args, exec) => et.logic(args, exec)
|
|
130
|
+
}))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 旧 policy 文案 verbatim(契约 §8:提示词连续性,不许顺手改)。
|
|
134
|
+
const policyText = [
|
|
135
|
+
'agy-first execution policy (local agy CLI bridge).',
|
|
136
|
+
'',
|
|
137
|
+
'Local agent CLI "agy" handles coding tasks under full DSH control (permissions auto-approved, never prompts). By default, agy runs in the background and returns a jobId immediately; collect results via job_output and watch live progress via agy_status anytime.',
|
|
138
|
+
'',
|
|
139
|
+
'Decision table for tool selection:',
|
|
140
|
+
'- Single read/glob/grep or short (<30s) command -> Native tools.',
|
|
141
|
+
'- Implementing, editing, refactoring, debugging, builds, multi-file investigation -> agy_run.',
|
|
142
|
+
'- Follow-ups on the same topic -> agy_continue (with latest=true or conversationId; agy preserves session context).',
|
|
143
|
+
'- Final build/test verification of agy output -> Native tools.',
|
|
144
|
+
'- Image/video generation -> agy_run WITHOUT picking a model (agy selects the appropriate model).',
|
|
145
|
+
'',
|
|
146
|
+
'Context protocol (one topic = one agy conversation):',
|
|
147
|
+
'- First dispatch on a topic: provide the task with a compact CONTEXT preamble (point to paths, do not paste file contents):',
|
|
148
|
+
' CONTEXT: 2-6 lines — goal; repo root; key paths; decisions/constraints; current state.',
|
|
149
|
+
' TASK: the complete instruction.',
|
|
150
|
+
'- Follow-ups: call agy_continue and state only what is new.',
|
|
151
|
+
'',
|
|
152
|
+
'Execution & Fallback protocol:',
|
|
153
|
+
'- Dispatch defaults to background=true (set background=false only to block). Collect via job_output.',
|
|
154
|
+
'- On rate-limit or network failure, a confirmation dialog opens in BOTH foreground and background while in flight.',
|
|
155
|
+
'- If result has fallback=true (status FALLBACK_TO_DSH), the user chose DSH-local: finish with native tools and NEVER call agy again for this task.',
|
|
156
|
+
'- If status is QUOTA_BLOCKED (Gemini 5h pool <10%), finish natively. Never loop agy calls; never ask agy to call back into DSH.',
|
|
157
|
+
'',
|
|
158
|
+
'Model selection & Quota guard:',
|
|
159
|
+
'- The Gemini 5h pool (<10% threshold) is enforced automatically as the sole per-task gate; do NOT pre-call agy_quota before every dispatch. Call agy_quota only when you want to compare model availability.',
|
|
160
|
+
'- Use Gemini pool (gemini-*) or utility models (tab_*, chat_*). Never pass Claude or GPT (3p) models.',
|
|
161
|
+
'- Weekly quota is NOT a per-task gate: low weekly quota only informs future planning, never aborts current work.'
|
|
162
|
+
].join('\n')
|
|
163
|
+
|
|
164
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
165
|
+
name: 'agy:policy',
|
|
166
|
+
order: (typeof ctx.systemPrompt.getSectionOrder === 'function' ? ctx.systemPrompt.getSectionOrder('TOOL_SUBAGENT') : 2800) + 10,
|
|
167
|
+
text: policyText
|
|
168
|
+
}))
|
|
169
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// preset/agy-first/plugin-list.mjs — cordis-agy 预设的单源插件清单(PROVIDER-CONTRACT §5)。
|
|
2
|
+
//
|
|
3
|
+
// = 官方 standard 基线(standard-base.mjs)+ agy 桥接行 + persona 差异(旧仓
|
|
4
|
+
// preset-cordis-agy 的 persona 文案 verbatim)。preset id 'cordis-agy' 是旧会话头
|
|
5
|
+
// 引用(agy-first-bridge v1.7.1 cordis.patch.yml),不可变。
|
|
6
|
+
import { standardPluginRows, bridgeRow } from '../standard-base.mjs'
|
|
7
|
+
|
|
8
|
+
export const meta = {
|
|
9
|
+
id: 'cordis-agy',
|
|
10
|
+
name: 'AGY 执行代理',
|
|
11
|
+
description: '完整编码 Agent(standard 全部能力),额外提供 agy_run/agy_continue 工具并注入"agy 优先"策略:各种模式下先把实现、编辑、调试、构建、跨文件排查派给本机 agy CLI(DSH 完全控制、agy 无提示改文件、默认后台执行),原生工具仅用于只读查询与最终验证。',
|
|
12
|
+
order: 10
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildPlugins(deps) {
|
|
16
|
+
const rows = standardPluginRows()
|
|
17
|
+
// 旧仓 agent.cordis.yml persona 段 verbatim(v1.5.15:dsh-persona 要求
|
|
18
|
+
// prefix 必填 + suffix 可选;旧单 text: 字段被 schema 拒绝)。
|
|
19
|
+
const persona = rows.find((r) => r.id === 'persona')
|
|
20
|
+
if (persona && persona.config) {
|
|
21
|
+
persona.config.prefix = 'You are a coding agent powered by the {{model}} model.'
|
|
22
|
+
persona.config.suffix = 'Your working directory is {{cwd}}.'
|
|
23
|
+
}
|
|
24
|
+
rows.push(bridgeRow('agy', deps && deps.bridgeName))
|
|
25
|
+
return rows
|
|
26
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# 生成物 — 由 node scripts/build.mjs 从 preset/codebuddy-first/plugin-list.mjs 生成,勿手改。
|
|
2
|
+
# legacy 目录形态($DSH_HOME/.agent-presets/codebuddy-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
|
+
suffix: 'Your working directory is {{cwd}}.'
|
|
8
|
+
prefix: 'You are a coding agent powered by the {{model}} model.'
|
|
9
|
+
- id: agent-instructions
|
|
10
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
11
|
+
config:
|
|
12
|
+
maxBytes: 65536
|
|
13
|
+
- id: tool-bash
|
|
14
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
15
|
+
disabled: !!js 'process.platform === ''win32'''
|
|
16
|
+
- id: tool-pwsh
|
|
17
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
18
|
+
disabled: !!js 'process.platform !== ''win32'''
|
|
19
|
+
- id: tool-fs
|
|
20
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
21
|
+
- id: tool-fs-search
|
|
22
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
23
|
+
config:
|
|
24
|
+
sampleOverCapGlobResults: false
|
|
25
|
+
- id: tool-jobs
|
|
26
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
27
|
+
- id: skill-filesystem
|
|
28
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
29
|
+
- id: tool-skill
|
|
30
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
31
|
+
- id: command-goal
|
|
32
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
33
|
+
- id: tool-goal
|
|
34
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
35
|
+
- id: planning
|
|
36
|
+
name: 'cordis:group'
|
|
37
|
+
group: true
|
|
38
|
+
isolate:
|
|
39
|
+
planMode: true
|
|
40
|
+
config:
|
|
41
|
+
- id: plan-mode
|
|
42
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
43
|
+
config:
|
|
44
|
+
section: |
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
- id: compaction
|
|
57
|
+
name: 'cordis:group'
|
|
58
|
+
group: true
|
|
59
|
+
isolate:
|
|
60
|
+
compaction: true
|
|
61
|
+
toolResultPruner: true
|
|
62
|
+
config:
|
|
63
|
+
- id: compaction-basic
|
|
64
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
65
|
+
- id: command-compact
|
|
66
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
67
|
+
- id: tool-result-pruner
|
|
68
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
69
|
+
config:
|
|
70
|
+
thresholdChars: 8192
|
|
71
|
+
headChars: 4096
|
|
72
|
+
tailChars: 1024
|
|
73
|
+
- id: delegation
|
|
74
|
+
name: 'cordis:group'
|
|
75
|
+
group: true
|
|
76
|
+
isolate:
|
|
77
|
+
workflowEngine: true
|
|
78
|
+
config:
|
|
79
|
+
- id: tool-subagent-control
|
|
80
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
81
|
+
- id: tool-subagent-list-agents
|
|
82
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
83
|
+
- id: tool-subagent
|
|
84
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
85
|
+
config:
|
|
86
|
+
provider: spawn
|
|
87
|
+
toolName: subagent
|
|
88
|
+
modelSelectionSettings: true
|
|
89
|
+
backgroundMode: continuable
|
|
90
|
+
- id: tool-subagent-fork
|
|
91
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
92
|
+
config:
|
|
93
|
+
provider: fork
|
|
94
|
+
toolName: subagent_fork
|
|
95
|
+
backgroundMode: continuable
|
|
96
|
+
- id: tool-subagent-codex
|
|
97
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
98
|
+
disabled: true
|
|
99
|
+
config:
|
|
100
|
+
provider: codex
|
|
101
|
+
toolName: subagent_codex
|
|
102
|
+
backgroundMode: one-shot
|
|
103
|
+
maxDepth: provider-managed
|
|
104
|
+
- id: tool-subagent-claude-code
|
|
105
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
106
|
+
disabled: true
|
|
107
|
+
config:
|
|
108
|
+
provider: claude-code
|
|
109
|
+
toolName: subagent_claude_code
|
|
110
|
+
backgroundMode: one-shot
|
|
111
|
+
maxDepth: provider-managed
|
|
112
|
+
- id: workflow-ptc
|
|
113
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
114
|
+
config:
|
|
115
|
+
provider: spawn
|
|
116
|
+
- id: tool-workflow
|
|
117
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
118
|
+
- id: tool-ralph
|
|
119
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
120
|
+
disabled: true
|
|
121
|
+
config:
|
|
122
|
+
subagentProvider: spawn
|
|
123
|
+
maxRounds: 64
|
|
124
|
+
- id: tool-ask-user
|
|
125
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
126
|
+
- id: tool-todo
|
|
127
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
128
|
+
config:
|
|
129
|
+
allowParallelInProgress: true
|
|
130
|
+
- id: tool-web
|
|
131
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
132
|
+
config:
|
|
133
|
+
fetch: true
|
|
134
|
+
searchTimeoutMs: 60000
|
|
135
|
+
- id: present
|
|
136
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
137
|
+
- id: tool-plugin-manager
|
|
138
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
139
|
+
disabled: true
|
|
140
|
+
- id: cli-bridge-codebuddy
|
|
141
|
+
name: '@luchenglong/cli-bridge/preset-codebuddy'
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// preset/codebuddy-first/cli-bridge.mjs — codebuddy-first preset 的桥接插件(preset 宿主形态)。
|
|
2
|
+
//
|
|
3
|
+
// PROVIDER-CONTRACT.md §5:本模块只做「宿主接线」——构造描述符、attach、注册三工具、
|
|
4
|
+
// 心跳/事件发布、codebuddy:policy 提示词段。全部执行/状态语义在
|
|
5
|
+
// providers/codebuddy.mjs + core/bridge-kit.mjs。
|
|
6
|
+
//
|
|
7
|
+
// 移植基线:codebuddy-first-bridge v1.6.x preset/codebuddy-first/codebuddy-first-bridge.mjs
|
|
8
|
+
// 的 apply():resolveExe 三面候选链、CWD_FALLBACK、心跳(SELF_SESSION_ID 兜底通道 +
|
|
9
|
+
// 30s 续租 + dispose 撤销)、noteSession、systemPrompt order 5 —— 全部 verbatim。
|
|
10
|
+
//
|
|
11
|
+
// 沙箱论证:本插件只注册工具与提示段,不发服务、不开端口、不写盘(设置文件由
|
|
12
|
+
// 家级指示灯路由单一写者负责)——无需 isolate 包装(旧包同款结论,逐字保留)。
|
|
13
|
+
//
|
|
14
|
+
// 本模块是宿主进程内真 ESM:可 import node: 内建与包内相对路径;禁止 import
|
|
15
|
+
// @deepseek-ai/*(解析根够不到 harness 包,历史论证 —— 契约 §8)。
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
|
|
19
|
+
import defineProvider from '../../providers/codebuddy.mjs'
|
|
20
|
+
import { renderResult, renderStatus } from '../../core/bridge-kit.mjs'
|
|
21
|
+
import { isBridgeEnabled } from '../../core/scheduling.mjs'
|
|
22
|
+
import { PROVIDER_IDS } from '../../providers/registry.mjs'
|
|
23
|
+
// 统一设置文件(keystone 单源:含旧 codebuddy-bridge-settings.json 字段级迁移)。
|
|
24
|
+
import { readSettingsFile, effectiveSettings } from '../../home-plugin/cli-bridge-indicator/lib/settings.mjs'
|
|
25
|
+
|
|
26
|
+
export const name = 'cli-bridge-codebuddy'
|
|
27
|
+
export const inject = ['tools', 'subprocess', 'systemPrompt', 'timer']
|
|
28
|
+
|
|
29
|
+
const OUTPUT_SCHEMA = { type: 'object', additionalProperties: true }
|
|
30
|
+
// 旧 preset CWD_FALLBACK verbatim(机器默认路径,Lead 定案保留:动态解析工作目录
|
|
31
|
+
// 只在 keystone indicator 侧做,preset 形态行为与旧包逐字一致)。
|
|
32
|
+
const CWD_FALLBACK = 'C:\\Users\\lcl\\Desktop\\codebuddy-bridge'
|
|
33
|
+
|
|
34
|
+
export function apply(ctx, config) {
|
|
35
|
+
// 调度门禁(0.2.0):用户关掉 codebuddy → 本形态不注册工具/政策段/心跳;fail-open。
|
|
36
|
+
if (!isBridgeEnabled('codebuddy', PROVIDER_IDS)) return
|
|
37
|
+
const subprocess = ctx.subprocess
|
|
38
|
+
const planMode = ctx.get('planMode')
|
|
39
|
+
const sandboxPolicy = ctx.get('sandboxPolicy')
|
|
40
|
+
const cfg = config || {}
|
|
41
|
+
|
|
42
|
+
const provider = defineProvider({ process, env: process.env, nodeFs: fs, nodePath: path })
|
|
43
|
+
|
|
44
|
+
// 设置读数:每次现读统一文件(面板保存即时生效,无需重启);文件缺失/损坏 →
|
|
45
|
+
// effectiveSettings(默认值 + 旧包文件字段级回退)。行 config 只做最后兜底。
|
|
46
|
+
function codebuddySettings() {
|
|
47
|
+
try {
|
|
48
|
+
const snap = readSettingsFile() || effectiveSettings()
|
|
49
|
+
const part = snap && snap.codebuddy
|
|
50
|
+
if (part && typeof part === 'object') return part
|
|
51
|
+
} catch (e) { /* 无 fs / 定位失败:落到行 config */ }
|
|
52
|
+
return provider.normalizeSettings(cfg)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 状态快照 → 家级灯(keystone 听 'codebuddy/status',事件是 app 级广播;旧 payload 形态)。
|
|
56
|
+
function publish(snapshot) {
|
|
57
|
+
try { ctx.emit('codebuddy/status', { snapshot }) } catch (e) { /* ignore */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 旧 preset resolveExe verbatim(L265-284):三个后端各有独立安装包,明确报错
|
|
61
|
+
// 而不跨面回退(各产品面登录互斥,混用会污染同一会话存储)。
|
|
62
|
+
// codebuddy npm 全局包 @tencent-ai/codebuddy-code:PATH → CODEBUDDY_BIN →
|
|
63
|
+
// npm 全局 bin(.cmd shim 走 node+bin,规避 CVE-2024-27980 后
|
|
64
|
+
// spawn .cmd/.bat 的 EINVAL)。
|
|
65
|
+
// codebuddy-en WorkBuddy AI 国际版自带 CLI:CODEBUDDY_EN_BIN → C:\Program Files\WorkBuddyAI\…
|
|
66
|
+
// workbuddy WorkBuddy 国内版桌面自带 CLI:WORKBUDDY_BIN → C:\Program Files\WorkBuddy\…
|
|
67
|
+
async function resolveExe(backend, execSignal) {
|
|
68
|
+
const nodeExe = process.execPath || 'node'
|
|
69
|
+
if (backend === 'workbuddy') {
|
|
70
|
+
const wbBin = process.env.WORKBUDDY_BIN || 'C:\\Program Files\\WorkBuddy\\resources\\app.asar.unpacked\\cli\\bin\\codebuddy'
|
|
71
|
+
return [nodeExe, wbBin]
|
|
72
|
+
}
|
|
73
|
+
if (backend === 'codebuddy-en') {
|
|
74
|
+
const enBin = process.env.CODEBUDDY_EN_BIN || 'C:\\Program Files\\WorkBuddyAI\\resources\\app.asar.unpacked\\cli\\bin\\codebuddy'
|
|
75
|
+
return [nodeExe, enBin]
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const exe = await subprocess.resolveExecutable('codebuddy', undefined, execSignal)
|
|
79
|
+
// npm 安装的 codebuddy 只有 .cmd shim,Node 直接 spawn .cmd/.bat 会 EINVAL
|
|
80
|
+
// (CVE-2024-27980 加固后);命中 .cmd/.bat 时改走 node + bin 脚本。
|
|
81
|
+
if (!/\.(cmd|bat)$/i.test(exe)) return exe
|
|
82
|
+
} catch (e) {}
|
|
83
|
+
// 回退:node 直接跑 npm 全局 bin 脚本(codebuddy 通常不在 DSH 进程 PATH 里)。
|
|
84
|
+
const binPath = process.env.CODEBUDDY_BIN || (process.env.APPDATA || 'C:\\Users\\lcl\\AppData\\Roaming') + '\\npm\\node_modules\\@tencent-ai\\codebuddy-code\\bin\\codebuddy'
|
|
85
|
+
return [nodeExe, binPath]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 旧 planActiveFor verbatim:DSH plan 态决定 codebuddy 的 --permission-mode。
|
|
89
|
+
function planActiveFor(exec) {
|
|
90
|
+
try {
|
|
91
|
+
if (planMode && exec && exec.agent) {
|
|
92
|
+
const st = planMode.get(exec.agent)
|
|
93
|
+
return !!(st && st.active)
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {}
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
provider.attach({
|
|
100
|
+
ctx,
|
|
101
|
+
subprocess,
|
|
102
|
+
resolveExe,
|
|
103
|
+
getCwdFallback: function () {
|
|
104
|
+
return (sandboxPolicy && typeof sandboxPolicy.workspaceRoot === 'string' && sandboxPolicy.workspaceRoot) || CWD_FALLBACK
|
|
105
|
+
},
|
|
106
|
+
planActiveFor,
|
|
107
|
+
defaultMode: 'auto',
|
|
108
|
+
getSettings: codebuddySettings,
|
|
109
|
+
getPreferredBackend: function () {
|
|
110
|
+
const s = codebuddySettings()
|
|
111
|
+
return (s && s.preferredBackend) || provider.defaultBackend
|
|
112
|
+
},
|
|
113
|
+
publish
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// ── 模式宣告心跳(旧 preset verbatim)─────────────────────────────────────────
|
|
117
|
+
// 本会话身份(兜底通道):host 侧首选自己实时枚举 agents.list() +
|
|
118
|
+
// agentPresets.composedPreset() 判定哪些会话是 codebuddy-first;这里的上报是
|
|
119
|
+
// 那条通道失灵时的备份。apply() 阶段 agent 通常尚未就位(preset 正在被组合
|
|
120
|
+
// 进去),所以:① 先试 agents.currentInitiator();② 拿不到就等第一次工具调用,
|
|
121
|
+
// 从 exec.agent 补记。全程防御:取不到就退化为不带 sessionId 的旧全局语义。
|
|
122
|
+
let SELF_SESSION_ID = null
|
|
123
|
+
function sessionIdOf(agent) {
|
|
124
|
+
if (!agent) return null
|
|
125
|
+
const sid = agent.id || (agent.session && agent.session.id)
|
|
126
|
+
return typeof sid === 'string' && sid ? sid : null
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const agents = typeof ctx.get === 'function' ? ctx.get('agents') : undefined
|
|
130
|
+
if (agents && typeof agents.currentInitiator === 'function') SELF_SESSION_ID = sessionIdOf(agents.currentInitiator())
|
|
131
|
+
} catch (e) { }
|
|
132
|
+
|
|
133
|
+
function announceMode(active) {
|
|
134
|
+
try {
|
|
135
|
+
const payload = { active: active !== false }
|
|
136
|
+
if (SELF_SESSION_ID) payload.sessionId = SELF_SESSION_ID
|
|
137
|
+
ctx.emit('codebuddy/mode', payload)
|
|
138
|
+
} catch (e) { }
|
|
139
|
+
}
|
|
140
|
+
// 第一次工具调用时补记会话身份(apply 阶段拿不到的情况)。
|
|
141
|
+
function noteSession(exec) {
|
|
142
|
+
if (SELF_SESSION_ID) return
|
|
143
|
+
try {
|
|
144
|
+
const sid = sessionIdOf(exec && exec.agent)
|
|
145
|
+
if (sid) { SELF_SESSION_ID = sid; announceMode(true) }
|
|
146
|
+
} catch (e) { }
|
|
147
|
+
}
|
|
148
|
+
announceMode(true)
|
|
149
|
+
try {
|
|
150
|
+
const t = ctx.setInterval ? ctx.setInterval(function () { announceMode(true) }, 30000) : null
|
|
151
|
+
if (ctx.effect) ctx.effect(() => () => {
|
|
152
|
+
try { if (t) t() } catch (e) { }
|
|
153
|
+
announceMode(false)
|
|
154
|
+
})
|
|
155
|
+
} catch (e) { }
|
|
156
|
+
|
|
157
|
+
// ── 工具注册(schema 经描述符 verbatim;执行进描述符三逻辑)───────────────────
|
|
158
|
+
const runTool = {
|
|
159
|
+
name: provider.tools.run.name,
|
|
160
|
+
description: provider.tools.run.description,
|
|
161
|
+
parameters: provider.tools.run.inputSchema,
|
|
162
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
163
|
+
execute(args, exec) { noteSession(exec); return provider.runToolLogic(args, exec) }
|
|
164
|
+
}
|
|
165
|
+
const continueTool = {
|
|
166
|
+
name: provider.tools.continue.name,
|
|
167
|
+
description: provider.tools.continue.description,
|
|
168
|
+
parameters: provider.tools.continue.inputSchema,
|
|
169
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderResult(value, provider) },
|
|
170
|
+
execute(args, exec) { noteSession(exec); return provider.continueToolLogic(args, exec) }
|
|
171
|
+
}
|
|
172
|
+
const statusTool = {
|
|
173
|
+
name: provider.tools.status.name,
|
|
174
|
+
description: provider.tools.status.description,
|
|
175
|
+
parameters: provider.tools.status.inputSchema,
|
|
176
|
+
output: { schema: OUTPUT_SCHEMA, render: (args, value) => renderStatus(value, provider) },
|
|
177
|
+
execute(args) { return provider.statusToolLogic(args) }
|
|
178
|
+
}
|
|
179
|
+
for (const tool of [runTool, continueTool, statusTool]) ctx.tools.register(tool)
|
|
180
|
+
|
|
181
|
+
// 提示词策略段(旧 preset verbatim:name 'codebuddy:policy',order 5,
|
|
182
|
+
// POLICY_TEXT 全文在 providers/codebuddy.mjs —— 提示词连续性红线)。
|
|
183
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
184
|
+
name: 'codebuddy:policy',
|
|
185
|
+
order: 5,
|
|
186
|
+
text: provider.policyText
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// preset/codebuddy-first/plugin-list.mjs — codebuddy-first 预设的单源插件清单(PROVIDER-CONTRACT §5)。
|
|
2
|
+
//
|
|
3
|
+
// = 官方 standard 基线(standard-base.mjs)+ codebuddy 桥接行;persona 保持 standard
|
|
4
|
+
// 默认(旧包 preset/codebuddy-first/agent.cordis.yml 就没有 persona 定制 —— 不顺手加)。
|
|
5
|
+
// preset id 'codebuddy-first' 是旧会话头引用(codebuddy-first-bridge 的
|
|
6
|
+
// preset.yml name: CodeBuddy-First 执行代理),不可变。
|
|
7
|
+
import { standardPluginRows, bridgeRow } from '../standard-base.mjs'
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
id: 'codebuddy-first',
|
|
11
|
+
name: 'CodeBuddy-First 执行代理',
|
|
12
|
+
description: '完整编码 Agent(standard 全部能力),额外提供 codebuddy_run/codebuddy_continue 工具并注入"codebuddy 优先"策略:各种模式下先把实现、编辑、调试、构建、跨文件排查派给本机 codebuddy CLI(DSH 完全控制、codebuddy 无提示改文件),原生工具仅用于只读查询与最终验证。',
|
|
13
|
+
order: 10
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function buildPlugins(deps) {
|
|
17
|
+
const rows = standardPluginRows()
|
|
18
|
+
rows.push(bridgeRow('codebuddy', deps && deps.bridgeName))
|
|
19
|
+
return rows
|
|
20
|
+
}
|