@ifrankwang/openspec-agents 0.145.0 → 0.146.0
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/.dsh-plugin/opx-tools.mjs +9054 -0
- package/.mcp-server/cli.mjs +86 -75
- package/README.md +2 -1
- package/assets/skills/openspec-standard/SKILL.md +1 -1
- package/assets/skills/openspec-standard/templates/openspec/config.yaml +3 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/README.md +5 -3
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/schema.yaml +31 -2
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/tasks.md +5 -0
- package/assets/skills/orchestrator/SKILL.md +15 -18
- package/assets/workflows/review-simple.yaml +2 -2
- package/assets/workflows/review.yaml +2 -2
- package/assets/workflows/task-simple.yaml +1 -1
- package/assets/workflows/task.yaml +2 -2
- package/dsh/cordis.patch.yml +21 -25
- package/package.json +6 -3
- package/src/adapters/deepseek-harness/index.ts +51 -37
- package/src/adapters/deepseek-harness/native-tools.ts +196 -0
- package/src/adapters/deepseek-harness/plugin.ts +61 -0
- package/src/adapters/deepseek-harness/schema-dsl.ts +129 -0
- package/src/adapters/mcp-common/cli.ts +1 -1
- package/src/adapters/mcp-common/server.ts +14 -41
- package/src/adapters/plugin-common/index.ts +35 -1
- package/src/adapters/tool-specs.ts +75 -0
- package/src/types/dsh-runtime.d.ts +162 -0
|
@@ -3,17 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* DSH 的插件形态不是 Claude/Codex/ZCode 的 plugin.json,而是 npm 包 +
|
|
5
5
|
* package.json 中 `dsh.bundle.patch` 指向的 cordis patch 文件。本适配器复用
|
|
6
|
-
* plugin-common 的
|
|
7
|
-
*
|
|
6
|
+
* plugin-common 的 assets 构建,生成一个可直接用 `dsh plugin --profile <name> add <dir>`
|
|
7
|
+
* 安装的 bundle 包。
|
|
8
8
|
*
|
|
9
|
-
* 生成的 patch
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 子代理 persona 取自对应 agent.md,真正接入 DSH 子代理体系。
|
|
9
|
+
* 生成的 patch 挂载两类能力:
|
|
10
|
+
* - 本包自有的 `openspec-opx-tools` cordis 插件:6 个 opx_* 编排工具注册为 DSH 原生工具,
|
|
11
|
+
* 每次调用从 `exec.agent.session.header.cwd` 取会话项目目录。多项目共用同一插件进程时
|
|
12
|
+
* 各自作用于各自项目(MCP 形态因 worktree 固定在进程启动参数上而做不到,见 patch 注释)。
|
|
13
|
+
* - DSH 官方能力:`@deepseek-ai/dsh-skill-filesystem`(skill 根)、`@deepseek-ai/dsh-tool-subagent`
|
|
14
|
+
* (每个 assets/agents 子代理一个 DSH 原生 subagent 工具)。opx_* 工具不再经
|
|
15
|
+
* `@deepseek-ai/dsh-mcp-client` 桥接——原生插件已直接注册工具,patch 内不挂 MCP client。
|
|
17
16
|
*/
|
|
18
17
|
import { writeFileSync, mkdirSync, rmSync, readFileSync, readdirSync } from "node:fs"
|
|
19
18
|
import { join } from "node:path"
|
|
@@ -21,7 +20,7 @@ import * as yaml from "js-yaml"
|
|
|
21
20
|
import { parseAgentMd, resolve, EXCLUDED_AGENTS } from "../agent-md.ts"
|
|
22
21
|
import {
|
|
23
22
|
PLUGIN_DESCRIPTION,
|
|
24
|
-
|
|
23
|
+
bundleDshNativeTools,
|
|
25
24
|
buildAgents,
|
|
26
25
|
buildSkills,
|
|
27
26
|
copyWorkflows,
|
|
@@ -43,34 +42,43 @@ export const DSH_PLUGIN_DIR = DEEP_SEEK_HARNESS_PLUGIN_DIR
|
|
|
43
42
|
|
|
44
43
|
const MANIFEST_PATCH_FILENAME = "cordis.patch.yml"
|
|
45
44
|
|
|
46
|
-
/**
|
|
47
|
-
const
|
|
45
|
+
/** DSH 原生工具插件 bundle 在包内的相对路径(bundle 产物目录 + 文件名)。 */
|
|
46
|
+
export const DSH_NATIVE_TOOLS_RELATIVE_PATH = ".dsh-plugin/opx-tools.mjs"
|
|
47
|
+
/**
|
|
48
|
+
* bundle package.json 中声明的 exports 子路径(Node 要求深层路径显式导出,
|
|
49
|
+
* 直接 import 深层路径报 ERR_PACKAGE_PATH_NOT_EXPORTED)。
|
|
50
|
+
*/
|
|
51
|
+
export const DSH_NATIVE_TOOLS_EXPORTS_KEY = "./.dsh-plugin/opx-tools.mjs"
|
|
52
|
+
/**
|
|
53
|
+
* cordis patch 的 insert.name:裸包名 + 子路径,二者之间是 `/` 而非 `./`——
|
|
54
|
+
* loader 走 Node 的裸包名解析,`pkg./x` 会被当成包名 `pkg.` 而 ERR_MODULE_NOT_FOUND。
|
|
55
|
+
*/
|
|
56
|
+
export const DSH_NATIVE_TOOLS_ROW_NAME = `${DSH_PLUGIN_NAME}${DSH_NATIVE_TOOLS_EXPORTS_KEY.slice(1)}`
|
|
57
|
+
/** cordis patch 中挂载原生工具插件的 insert 行 id。 */
|
|
58
|
+
export const DSH_NATIVE_TOOLS_ROW_ID = "openspec-opx-tools"
|
|
59
|
+
|
|
60
|
+
/** 原生工具插件与 skill 根的基础 patch;子代理工具行由 buildDshPatchContent 动态追加。 */
|
|
61
|
+
const DSH_BASE_PATCH_YAML = `# DeepSeek Harness (DSH) bundle patch: expose OpenSpec orchestration tools and skills.
|
|
48
62
|
# The patch is applied as a bundle layer under dsh.profile.bundles.
|
|
49
63
|
# baseUrl 指向 profile 根目录;bundle 安装后位于 profile 的 node_modules 下,
|
|
50
64
|
# 因此从这里引用 node_modules/@ifrankwang/openspec-agents/... 是稳定的。
|
|
65
|
+
#
|
|
66
|
+
# openspec-opx-tools:本包自有的 cordis 插件,把 6 个 opx_* 编排工具注册为 DSH 原生工具。
|
|
67
|
+
# 工具在 DSH 进程内执行,每次调用从 exec.agent.session.header.cwd 取会话项目目录,
|
|
68
|
+
# 因此「单 profile 装一份插件、多项目共用同一进程」下各项目互不干扰——这正是 MCP 形态做不到的
|
|
69
|
+
# (MCP server 是子进程,worktree 只能由启动参数固定一份,与用户打开的项目无关)。
|
|
51
70
|
- insert:
|
|
52
|
-
- id:
|
|
53
|
-
name: '
|
|
71
|
+
- id: ${DSH_NATIVE_TOOLS_ROW_ID}
|
|
72
|
+
name: '${DSH_NATIVE_TOOLS_ROW_NAME}'
|
|
54
73
|
config:
|
|
55
|
-
|
|
56
|
-
transport: stdio
|
|
57
|
-
command: node
|
|
58
|
-
args:
|
|
59
|
-
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('./node_modules/@ifrankwang/openspec-agents/.mcp-server/cli.mjs', baseUrl))"
|
|
60
|
-
- '--transport'
|
|
61
|
-
- 'stdio'
|
|
62
|
-
- '--worktree'
|
|
63
|
-
- !!js "process.cwd()"
|
|
64
|
-
- '--unattended'
|
|
65
|
-
- '--strip-opx-prefix'
|
|
66
|
-
cwd: !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('./node_modules/@ifrankwang/openspec-agents', baseUrl))"
|
|
74
|
+
unattended: true
|
|
67
75
|
- id: openspec-skills
|
|
68
76
|
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
69
77
|
config:
|
|
70
78
|
providerName: openspec-filesystem
|
|
71
79
|
includeDefaultRoots: false
|
|
72
80
|
customSkillDirs:
|
|
73
|
-
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('./node_modules
|
|
81
|
+
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('./node_modules/${DSH_PLUGIN_NAME}/assets/skills', baseUrl))"
|
|
74
82
|
`
|
|
75
83
|
|
|
76
84
|
interface DshSubagentEntry {
|
|
@@ -100,16 +108,17 @@ function denyEditTools(frontmatter: Record<string, unknown>): boolean {
|
|
|
100
108
|
/** DSH 子代理 persona 前置的工具接入说明,避免子代理在首次 opx_status 前用 shell 全盘搜索工具。 */
|
|
101
109
|
const DSH_SUBAGENT_TOOL_ACCESS_PREAMBLE = `## DSH 工具接入(必读)
|
|
102
110
|
|
|
103
|
-
- 本流程的 opx_* 工具在 DSH 中以 \`
|
|
104
|
-
- \`
|
|
105
|
-
- \`
|
|
111
|
+
- 本流程的 opx_* 工具在 DSH 中以 \`opx_*\` 原生工具形式出现(注册在本插件包内,无 \`mcp__\` 前缀),子代理通常只需要:
|
|
112
|
+
- \`opx_status\`
|
|
113
|
+
- \`opx_agent_submit\`
|
|
114
|
+
- 调用工具时必须携带 \`_agent\` 参数传自身角色名(如 \`openspec-developer\`),否则身份无法判定、会被按编排主代理视角处理。
|
|
106
115
|
- 如果当前工具列表中没有这些工具,请立即调用 \`dev_tool_search\` 搜索 \`opx\`,或一次性解锁:
|
|
107
116
|
\`\`\`json
|
|
108
|
-
{"toolNames": ["
|
|
117
|
+
{"toolNames": ["opx_status", "opx_agent_submit"]}
|
|
109
118
|
\`\`\`
|
|
110
119
|
如果计划使用 \`todo_write\` 跟踪任务,也请在同一轮一并解锁。
|
|
111
120
|
- 严禁使用 \`find\` / \`which\` / \`grep\` 等 shell 命令搜索 \`opx\`、\`openspec\`、MCP 配置或安装路径;工具未出现是因为尚未解锁,不是未安装。
|
|
112
|
-
- 解锁后第一件事是调用 \`
|
|
121
|
+
- 解锁后第一件事是调用 \`opx_status\` 获取上下文,不要先探查仓库或读取状态文件。
|
|
113
122
|
`
|
|
114
123
|
|
|
115
124
|
/** 从 assets/agents/*.md 收集 DSH 子代理工具(跳过主代理模板)。 */
|
|
@@ -143,7 +152,7 @@ function collectDshSubagentEntries(): DshSubagentEntry[] {
|
|
|
143
152
|
return entries
|
|
144
153
|
}
|
|
145
154
|
|
|
146
|
-
/** 生成完整的 DSH cordis patch
|
|
155
|
+
/** 生成完整的 DSH cordis patch 内容:基础原生工具插件行 + skill 根 + 每个子代理一个 DSH 原生 subagent 工具。 */
|
|
147
156
|
export function buildDshPatchContent(): string {
|
|
148
157
|
const subagents = collectDshSubagentEntries()
|
|
149
158
|
if (subagents.length === 0) return DSH_BASE_PATCH_YAML
|
|
@@ -159,7 +168,7 @@ export type DeepSeekHarnessPluginBuildResult = PluginPackageResult
|
|
|
159
168
|
|
|
160
169
|
/**
|
|
161
170
|
* 生成 DeepSeek Harness bundle 插件包到 outDir(默认 dist/deepseek-harness-plugin/):
|
|
162
|
-
* package.json(含 dsh.bundle.patch)、cordis.patch.yml、.
|
|
171
|
+
* package.json(含 dsh.bundle.patch)、cordis.patch.yml、.dsh-plugin/opx-tools.mjs bundle、
|
|
163
172
|
* assets/skills/、assets/workflows/。
|
|
164
173
|
*
|
|
165
174
|
* 返回生成的组件清单供调用方/测试断言;DSH 原生不消费 Markdown agent 文件,
|
|
@@ -170,13 +179,13 @@ export function buildDeepSeekHarnessPlugin(outDir: string = DEEP_SEEK_HARNESS_PL
|
|
|
170
179
|
mkdirSync(join(outDir, "agents"), { recursive: true })
|
|
171
180
|
mkdirSync(join(outDir, "skills"), { recursive: true })
|
|
172
181
|
mkdirSync(join(outDir, "assets"), { recursive: true })
|
|
173
|
-
mkdirSync(join(outDir, ".
|
|
182
|
+
mkdirSync(join(outDir, ".dsh-plugin"), { recursive: true })
|
|
174
183
|
|
|
175
184
|
const version = readPkgVersion()
|
|
176
185
|
// DSH 通过上方 subagent 工具消费 agent 定义;agents/ 目录仍保留原始 markdown 供参考/审计。
|
|
177
186
|
const agents = buildAgents(join(outDir, "agents"))
|
|
178
187
|
const skills = buildSkills(join(outDir, "skills"))
|
|
179
|
-
|
|
188
|
+
bundleDshNativeTools(outDir)
|
|
180
189
|
copyWorkflows(outDir)
|
|
181
190
|
copyLicense(outDir)
|
|
182
191
|
|
|
@@ -191,6 +200,11 @@ export function buildDeepSeekHarnessPlugin(outDir: string = DEEP_SEEK_HARNESS_PL
|
|
|
191
200
|
private: true,
|
|
192
201
|
author: PLUGIN_AUTHOR_STRING,
|
|
193
202
|
license: "MIT",
|
|
203
|
+
// cordis 以裸包名 + 子路径 import 原生工具插件;Node 要求该子路径在 exports 中显式声明
|
|
204
|
+
// (深层路径直接 import 报 ERR_PACKAGE_PATH_NOT_EXPORTED)。
|
|
205
|
+
exports: {
|
|
206
|
+
[DSH_NATIVE_TOOLS_EXPORTS_KEY]: `./${DSH_NATIVE_TOOLS_RELATIVE_PATH}`,
|
|
207
|
+
},
|
|
194
208
|
dsh: {
|
|
195
209
|
bundle: {
|
|
196
210
|
patch: `./${MANIFEST_PATCH_FILENAME}`,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness 原生工具插件:把 6 个 opx_* 编排工具注册为 DSH 原生工具。
|
|
3
|
+
*
|
|
4
|
+
* 与 MCP 形态的根本差异(本次改造的核心):
|
|
5
|
+
* - MCP server 是常驻子进程,启动时只能拿到一份固定的 `--worktree`,与用户当前打开的项目无关;
|
|
6
|
+
* DSH 是「单 profile 全局装一份插件、多项目共用同一 MCP server 进程」,该固定目录指向
|
|
7
|
+
* `~/.dsh/profiles/desktop`,工具因此报「无法从 tasks.md 解析出任务组」。
|
|
8
|
+
* - 原生工具在进程内执行,每次调用都能从 `exec.agent.session.header.cwd` 取到本次会话的项目目录,
|
|
9
|
+
* 目录错配从根上消失(与官方文件工具同一机制,见 dsh-tool-fs/lib/index.js 的 sessionCwd)。
|
|
10
|
+
*
|
|
11
|
+
* 业务逻辑不重写:所有编排行为继续由 src/core/tools 下的 *Execute 承担,本文件只做三件事:
|
|
12
|
+
* 注册名、参数 schema 投影、上下文注入。
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolExecution } from "@deepseek-ai/dsh-tools"
|
|
15
|
+
import type { ToolContext } from "../../core/tools/types.ts"
|
|
16
|
+
import {
|
|
17
|
+
initExecute, setWorktreeExecute, statusExecute,
|
|
18
|
+
completeTaskGroupExecute, setUnattendedExecute,
|
|
19
|
+
} from "../../core/tools/lifecycle.ts"
|
|
20
|
+
import { agentSubmitExecute } from "../../core/tools/submit.ts"
|
|
21
|
+
import { readStateByWorktree, writeState } from "../../core/state.ts"
|
|
22
|
+
import { jsonSchemaToToolParameters, type ToolParameters } from "./schema-dsl.ts"
|
|
23
|
+
import { OPX_TOOL_SPECS, OPX_TOOL_NAMES } from "../tool-specs.ts"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* DSH 工具执行上下文(本插件只消费三处,均已在 DSH 0.2.0-rc.2 源码中核实):
|
|
27
|
+
* - agent.session.header.cwd:本次会话的项目目录(会话目录,非进程启动目录);
|
|
28
|
+
* 官方文件工具同机制,见 dsh-tool-fs/lib/index.js:161-173 的 sessionCwd。
|
|
29
|
+
* - agent.id:与 agent.session.id 同值(dsh-agent/lib/index.js:510 的 enter() 校验)。
|
|
30
|
+
* - signal:协作式取消信号。
|
|
31
|
+
* 结构用 ambient 声明(src/types/dsh-runtime.d.ts)而非本地手写,避免与官方形状漂移。
|
|
32
|
+
*/
|
|
33
|
+
export type DshToolExec = Pick<ToolExecution, "agent" | "signal">
|
|
34
|
+
|
|
35
|
+
/** 编排主代理视角的固定身份(与 MCP 侧 resolveContext 缺省口径一致)。 */
|
|
36
|
+
export const PRIMARY_AGENT = "primary"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 解析调用上下文。
|
|
40
|
+
*
|
|
41
|
+
* worktree 取会话项目目录:原生工具每次调用都拿到本次会话自己的 cwd,多项目共用同一插件进程时
|
|
42
|
+
* 各自作用于各自的项目。
|
|
43
|
+
*
|
|
44
|
+
* 会话目录缺失时**不静默降级**:DSH 进程的 cwd 正是 profile 目录(如 ~/.dsh/profiles/desktop),
|
|
45
|
+
* 恰是本次改造要消灭的那个错误目录——静默降级等于把原缺陷(状态写进错误目录且返回成功)以
|
|
46
|
+
* 无人察觉的方式复现。故 agent 调用路径下取不到会话目录即明确报错,调用方无从「不知情地作用于
|
|
47
|
+
* 错误目录」;唯一的例外是无 agent 上下文的非 agent 调用(DSH 运行时不会这样调用本工具),
|
|
48
|
+
* 与官方 dsh-tool-fs 一样不在工具边界读进程 cwd 由后端兜底,此处后端即进程 cwd。
|
|
49
|
+
*
|
|
50
|
+
* agent 身份:DSH 未把子代理的注册名(如 openspec_architect)写入 Agent 或 session header——
|
|
51
|
+
* Agent 只有 { id, options, session, inbox, status, ctx }(见 dsh-agent 的 Agent 接口声明),
|
|
52
|
+
* subagent/descriptor 事件的 label 来自模型自填的 description(dsh-tool-subagent/lib/index.js:510),
|
|
53
|
+
* 都不是稳定角色标识。因此按官方可判定的口径分流:
|
|
54
|
+
* - 调用方通过 `_agent` 显式声明角色名(分派 prompt 中携带,与 MCP 形态同一范式)→ 采用声明值;
|
|
55
|
+
* - 顶层会话未声明(orchestrator 的正常形态)→ 编排主代理视角(orchestrator: true),
|
|
56
|
+
* 与 MCP 侧 `_agent` 缺省口径一致;
|
|
57
|
+
* - 子会话未声明 → 无法判定角色,按编排视角放行但标记身份未声明,
|
|
58
|
+
* 由 opx_status 视图输出身份补传提示(与 MCP 侧 identityDeclared 兜底同一机制)。
|
|
59
|
+
*/
|
|
60
|
+
export function resolveToolContext(
|
|
61
|
+
exec: DshToolExec,
|
|
62
|
+
args: Record<string, unknown>,
|
|
63
|
+
opts: { processCwd: string },
|
|
64
|
+
): ToolContext {
|
|
65
|
+
const worktree = resolveSessionWorktree(exec, opts.processCwd)
|
|
66
|
+
const raw = args[AGENT_ARG]
|
|
67
|
+
const declared = typeof raw === "string" ? raw : ""
|
|
68
|
+
const agent = declared !== "" ? declared : PRIMARY_AGENT
|
|
69
|
+
return {
|
|
70
|
+
worktree,
|
|
71
|
+
agent,
|
|
72
|
+
orchestrator: agent === PRIMARY_AGENT,
|
|
73
|
+
// 身份未显式声明时置 false:子代理首次查状态会落入编排视角,视图据此追加身份补传提示。
|
|
74
|
+
// DSH 不暴露子代理的注册名给工具(见上),故此处只能声明「未声明」,无法替其推断角色。
|
|
75
|
+
identityDeclared: declared !== "",
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 解析本次调用的项目目录。
|
|
81
|
+
* - 有 agent 上下文:只认 `agent.session.header.cwd`;缺失(undefined / 空串)即报错,
|
|
82
|
+
* 绝不回落到进程 cwd(进程 cwd 是 DSH profile 目录,回落会把状态写进错误目录)。
|
|
83
|
+
* - 无 agent 上下文(非 agent 调用):回落进程 cwd,与官方 dsh-tool-fs 一样把兜底交给后端
|
|
84
|
+
* (本工具的后端就是进程自身)。
|
|
85
|
+
*/
|
|
86
|
+
export function resolveSessionWorktree(exec: DshToolExec, processCwd: string): string {
|
|
87
|
+
const sessionCwd = exec.agent?.session.header.cwd
|
|
88
|
+
if (typeof sessionCwd === "string" && sessionCwd !== "") return sessionCwd
|
|
89
|
+
if (exec.agent) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
"无法确定本次调用所属的项目目录:DSH 执行上下文未提供 agent.session.header.cwd(会话项目目录)。" +
|
|
92
|
+
"进程启动目录是 DSH profile 目录(如 ~/.dsh/profiles/desktop),不是任何项目,继续执行会把编排状态写进错误目录," +
|
|
93
|
+
"因此本工具拒绝在缺会话目录时执行。请在项目会话中重新调用本工具。",
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
return processCwd
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 私有身份参数:下划线前缀表示非业务参数,与 MCP 形态的 `_agent` 同名同义。 */
|
|
100
|
+
export const AGENT_ARG = "_agent"
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 默认无人值守:DSH 形态保持与 MCP 侧一致的默认(DSH patch 分发时开启)。
|
|
104
|
+
* 会话已初始化且 state.unattended 未设置时自动置 true;幂等,不覆盖用户显式关闭的会话。
|
|
105
|
+
*/
|
|
106
|
+
async function ensureDefaultUnattended(args: Record<string, unknown>, ctx: ToolContext): Promise<void> {
|
|
107
|
+
const changeId = typeof args.change_id === "string" ? args.change_id : undefined
|
|
108
|
+
if (!changeId) return
|
|
109
|
+
try {
|
|
110
|
+
const state = await readStateByWorktree(ctx.worktree, changeId)
|
|
111
|
+
if (state && state.unattended === undefined) {
|
|
112
|
+
state.unattended = true
|
|
113
|
+
await writeState(ctx.worktree, state)
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// 状态不可读/不可写时静默跳过(不阻断工具主流程)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 构造 6 个工具的 execute 包装:注入会话上下文与取消信号,业务逻辑直接委托给内核 *Execute。
|
|
122
|
+
* @param unattended - 是否启用默认无人值守(DSH 分发形态为 true)
|
|
123
|
+
* @param processCwd - 无 agent 上下文的非 agent 调用时的兜底目录(agent 调用缺会话目录即报错,不兜底)
|
|
124
|
+
*/
|
|
125
|
+
export function buildNativeToolExecutes(
|
|
126
|
+
unattended: boolean,
|
|
127
|
+
processCwd: string,
|
|
128
|
+
): Record<string, (args: Record<string, unknown>, exec: DshToolExec) => Promise<string>> {
|
|
129
|
+
const wrap = (execute: (args: any, ctx: ToolContext) => Promise<string>) =>
|
|
130
|
+
async (args: Record<string, unknown>, exec: DshToolExec): Promise<string> => {
|
|
131
|
+
// 取消采用协作式:调度前已取消的调用不进入内核(与 DSH 官方工具一致的 ABORTED_BEFORE_DISPATCH 语义)。
|
|
132
|
+
exec.signal?.throwIfAborted?.()
|
|
133
|
+
const ctx = resolveToolContext(exec, args, { processCwd })
|
|
134
|
+
// 私有身份参数不进入内核参数(内核校验的是纯业务 schema)。
|
|
135
|
+
const cleanArgs: Record<string, unknown> = { ...args }
|
|
136
|
+
delete cleanArgs[AGENT_ARG]
|
|
137
|
+
if (unattended) await ensureDefaultUnattended(cleanArgs, ctx)
|
|
138
|
+
const result = await execute(cleanArgs, ctx)
|
|
139
|
+
if (unattended) await ensureDefaultUnattended(cleanArgs, ctx)
|
|
140
|
+
return result
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
opx_orch_init: wrap((args, ctx) => initExecute(args, ctx)),
|
|
145
|
+
opx_orch_set_worktree: wrap((args, ctx) => setWorktreeExecute(args, ctx)),
|
|
146
|
+
opx_status: wrap((args, ctx) => statusExecute({ change_id: args.change_id as string }, ctx)),
|
|
147
|
+
opx_orch_complete_task_group: wrap((args, ctx) => completeTaskGroupExecute(args, ctx)),
|
|
148
|
+
opx_orch_set_unattended: wrap((args, ctx) => setUnattendedExecute(args, ctx)),
|
|
149
|
+
opx_agent_submit: wrap((args, ctx) => agentSubmitExecute(args, ctx)),
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface NativeToolDefinition {
|
|
154
|
+
name: string
|
|
155
|
+
description: string
|
|
156
|
+
parameters: ToolParameters
|
|
157
|
+
output: { schema: { type: "string" }; render: (args: any, value: string) => { type: "text"; text: string }[] }
|
|
158
|
+
execute: (args: Record<string, unknown>, exec: DshToolExec) => Promise<string>
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 组装 6 个 DSH 原生工具的完整定义(可直接交给 defineTool)。
|
|
163
|
+
* 参数定义来自内核纯 JSON Schema(src/core/tools/schemas.ts),只做协议形态投影,不复制参数语义。
|
|
164
|
+
*/
|
|
165
|
+
export function buildNativeToolDefinitions(
|
|
166
|
+
unattended: boolean,
|
|
167
|
+
processCwd: string,
|
|
168
|
+
): NativeToolDefinition[] {
|
|
169
|
+
const executes = buildNativeToolExecutes(unattended, processCwd)
|
|
170
|
+
return OPX_TOOL_SPECS.map((spec) => ({
|
|
171
|
+
name: spec.name,
|
|
172
|
+
description: spec.description,
|
|
173
|
+
// 附加 _agent 身份参数,与 MCP 形态对齐(子代理显式声明自身角色名)。
|
|
174
|
+
parameters: withAgentArg(jsonSchemaToToolParameters(spec.schema)),
|
|
175
|
+
output: {
|
|
176
|
+
// 工具返回体一律 markdown 文本(见 AGENTS.md「工具返回体须为 markdown 格式」)。
|
|
177
|
+
schema: { type: "string" as const },
|
|
178
|
+
render: (_args: any, value: string) => [{ type: "text" as const, text: value }],
|
|
179
|
+
},
|
|
180
|
+
execute: executes[spec.name]!,
|
|
181
|
+
}))
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 附加 _agent 身份参数(仅暴露层,模型侧可见;内核执行前会剔除)。 */
|
|
185
|
+
function withAgentArg(parameters: ToolParameters): ToolParameters {
|
|
186
|
+
return {
|
|
187
|
+
...parameters,
|
|
188
|
+
[AGENT_ARG]: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description:
|
|
191
|
+
"调用者 agent 标识(子代理调用时传自身角色名,如 openspec-reviewer-tool;缺省为编排主代理视角)",
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { OPX_TOOL_NAMES }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH cordis 插件入口:把 6 个 opx_* 编排工具注册进 DSH 原生工具注册表。
|
|
3
|
+
*
|
|
4
|
+
* 挂载方式:bundle 的 cordis.patch.yml 以 `insert` 插入本包(与官方 dsh-skill-filesystem 等行同构)。
|
|
5
|
+
* 本文件由 `bun build --target node` 打成单文件 `.dsh-plugin/opx-tools.mjs` 随包分发,
|
|
6
|
+
* `@deepseek-ai/*` 保持 external——插件在 profile 的 node_modules 下被 cordis import 时,
|
|
7
|
+
* 这些官方包由 DSH 安装目录的 node_modules 提供,不随本包分发。
|
|
8
|
+
*
|
|
9
|
+
* cordis 插件契约三处缺一不可(官方 dsh-tool-skill / dsh-tool-fs / dsh-mcp-client 同形):
|
|
10
|
+
* - `Config` 必须是 Standard Schema(schemastery 构造)——cordis 直接取 `Config["~standard"].validate`,
|
|
11
|
+
* 普通对象会在加载期崩于 `Cannot read properties of undefined (reading 'validate')`;
|
|
12
|
+
* - `inject` 必须声明 apply 内访问的服务——fiber 的上下文代理对未 inject 的属性访问抛
|
|
13
|
+
* `cannot get property "tools" without inject`;
|
|
14
|
+
* - 导出形态为命名导出 `{ Config, apply, inject, name }`,由 loaders 的 unwrapExports 直接取插件对象。
|
|
15
|
+
*
|
|
16
|
+
* 与 MCP 形态的差异只有「工具在谁进程里跑」:
|
|
17
|
+
* - MCP:常驻子进程,worktree 与身份都靠启动参数/入参传入,多项目共用一个进程 → 目录与项目无关;
|
|
18
|
+
* - 原生:本进程,每次调用从 exec 注入的会话上下文取 worktree 与取消信号,多项目天然隔离。
|
|
19
|
+
*/
|
|
20
|
+
import z from "@deepseek-ai/schemastery"
|
|
21
|
+
import { defineTool, type ToolExecution } from "@deepseek-ai/dsh-tools"
|
|
22
|
+
import { buildNativeToolDefinitions, type DshToolExec } from "./native-tools.ts"
|
|
23
|
+
|
|
24
|
+
/** DSH 插件配置:cordis 按 Standard Schema 校验并注入默认值。默认无人值守与 MCP 侧 --unattended 同口径。 */
|
|
25
|
+
const Config = z.object({
|
|
26
|
+
/** 默认无人值守:会话已初始化且未显式设置时自动开启。默认 true,与 MCP 侧 --unattended 同口径。 */
|
|
27
|
+
unattended: z.boolean().default(true),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/** cordis 插件名。 */
|
|
31
|
+
const name = "openspec-opx-tools"
|
|
32
|
+
|
|
33
|
+
/** cordis 依赖声明:apply 内访问 ctx.tools,未声明即抛「cannot get property "tools" without inject」。 */
|
|
34
|
+
const inject = ["tools"]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 插件主体:注册 6 个 opx_* 原生工具。
|
|
38
|
+
*
|
|
39
|
+
* worktree 每次调用从 `exec.agent.session.header.cwd` 取会话项目目录(该字段缺失即报错,不静默回落进程 cwd);
|
|
40
|
+
* `exec.signal` 在每次调用入口检查取消;调用者身份由 `_agent` 入参声明(缺省=编排主代理视角)。
|
|
41
|
+
*/
|
|
42
|
+
function apply(ctx: any, config: { unattended?: boolean } = {}): void {
|
|
43
|
+
const unattended = config.unattended !== false
|
|
44
|
+
// 进程 cwd 仅用于「无 agent 上下文的非 agent 调用」兜底;agent 调用缺会话目录时工具直接报错。
|
|
45
|
+
const processCwd = process.cwd()
|
|
46
|
+
for (const spec of buildNativeToolDefinitions(unattended, processCwd)) {
|
|
47
|
+
ctx.tools.register(
|
|
48
|
+
defineTool({
|
|
49
|
+
name: spec.name,
|
|
50
|
+
description: spec.description,
|
|
51
|
+
parameters: spec.parameters,
|
|
52
|
+
output: spec.output,
|
|
53
|
+
async execute(args: any, exec: ToolExecution) {
|
|
54
|
+
return await spec.execute(args as Record<string, unknown>, exec as unknown as DshToolExec)
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { Config, apply, inject, name }
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内核纯 JSON Schema → DSH 原生工具 schema DSL 的投影。
|
|
3
|
+
*
|
|
4
|
+
* DSH 的 `defineTool` 参数不是 draft-07 JSON Schema,而是它自己的统一 schema DSL
|
|
5
|
+
* (见 dsh-tools/lib/types/schema.js 的 parameterSchemaSpecToJsonSchema):
|
|
6
|
+
* - 参数根是「隐式对象」——每个 key 是一个 value schema,必填用 `required: true` 标注在属性上,
|
|
7
|
+
* 而不是 JSON Schema 的 `required: string[]`;
|
|
8
|
+
* - value schema 只接受 type(string/number/integer/boolean/null/array/object/json)、properties、
|
|
9
|
+
* items、enum、const、oneOf 与注解(description/title/default/examples)。
|
|
10
|
+
*
|
|
11
|
+
* DSH 强制执行的 raw JSON Schema 子集不含 minLength / minItems / minimum / maximum
|
|
12
|
+
* (见 dsh-tools/lib/types/json-schema.js 的 CONSTRAINT_KEYWORDS),这些约束无法在该层表达,
|
|
13
|
+
* 故按 DSH 官方口径丢弃并降级为描述文本:参数的业务语义与运行时校验仍以内核 *Execute 为准
|
|
14
|
+
* (内核对同一份 JSON Schema 定义的必填/取值做二次校验)。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DSH schema DSL 的 value schema。
|
|
19
|
+
*
|
|
20
|
+
* 形态与 DSH 官方 dsh-tools 的 ValueSchemaSpec 对齐(见 dsh-tools/lib/types/schema.js 的
|
|
21
|
+
* runSchemaCompiler):类型用 `type`,必填用属性上的 `required: true`(而非 required 数组),
|
|
22
|
+
* 并且只接受 type/properties/items/enum/const/oneOf 与注解字段——多一个 key 即报作者错误。
|
|
23
|
+
*
|
|
24
|
+
* 声明为 type alias 而非 interface:type alias 的隐式索引签名使其可直接赋给
|
|
25
|
+
* `ParameterSchemaSpec`(官方形状为开放记录),interface 则不满足索引签名约束。
|
|
26
|
+
*/
|
|
27
|
+
export type DshValueSchema = {
|
|
28
|
+
type: "string" | "number" | "integer" | "boolean" | "null" | "array" | "object" | "json"
|
|
29
|
+
description?: string
|
|
30
|
+
default?: unknown
|
|
31
|
+
properties?: Record<string, DshValueSchema>
|
|
32
|
+
/** 必填标注:DSH DSL 用属性上的 `required: true`,内核 JSON Schema 用根上的 required 数组。 */
|
|
33
|
+
required?: true
|
|
34
|
+
items?: DshValueSchema
|
|
35
|
+
enum?: readonly (string | number | boolean)[]
|
|
36
|
+
additionalProperties?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** DSH 工具的 parameters:一个隐式对象根的「属性名 → value schema」映射。 */
|
|
40
|
+
export type ToolParameters = Record<string, DshValueSchema>
|
|
41
|
+
|
|
42
|
+
/** 内核纯 JSON Schema(draft-07 子集),与 src/core/provider.ts 的 JSONSchema 同形。 */
|
|
43
|
+
export interface CoreJsonSchema {
|
|
44
|
+
type?: string
|
|
45
|
+
description?: string
|
|
46
|
+
properties?: Record<string, CoreJsonSchema>
|
|
47
|
+
items?: CoreJsonSchema
|
|
48
|
+
required?: readonly string[]
|
|
49
|
+
enum?: readonly (string | number | boolean)[]
|
|
50
|
+
default?: unknown
|
|
51
|
+
additionalProperties?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** DSH 原生不支持的约束关键字(丢弃并降级为描述,避免模型看不到约束)。 */
|
|
55
|
+
const UNSUPPORTED_CONSTRAINTS = [
|
|
56
|
+
{ key: "minLength", suffix: "(不得为空)" },
|
|
57
|
+
{ key: "minItems", suffix: "(至少含 1 项)" },
|
|
58
|
+
{ key: "minimum", suffix: "(不小于该值)" },
|
|
59
|
+
{ key: "maximum", suffix: "(不大于该值)" },
|
|
60
|
+
] as const
|
|
61
|
+
|
|
62
|
+
/** 拼接降级后的描述文本:把无法在 DSH 层表达的约束以自然语言形式追加到 description。 */
|
|
63
|
+
function describeWithDroppedConstraints(schema: CoreJsonSchema): string | undefined {
|
|
64
|
+
const parts: string[] = []
|
|
65
|
+
if (typeof schema.description === "string" && schema.description !== "") parts.push(schema.description)
|
|
66
|
+
for (const { key, suffix } of UNSUPPORTED_CONSTRAINTS) {
|
|
67
|
+
const value = (schema as Record<string, unknown>)[key]
|
|
68
|
+
if (typeof value === "number") parts.push(`${key}=${value}${suffix}`)
|
|
69
|
+
}
|
|
70
|
+
return parts.length > 0 ? parts.join(" ") : undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 把一个内核 JSON Schema 节点投影为 DSH value schema。 */
|
|
74
|
+
function projectValueSchema(schema: CoreJsonSchema): DshValueSchema {
|
|
75
|
+
const description = describeWithDroppedConstraints(schema)
|
|
76
|
+
const annotation = description !== undefined ? { description } : {}
|
|
77
|
+
switch (schema.type) {
|
|
78
|
+
case "object": {
|
|
79
|
+
const properties = projectProperties(schema)
|
|
80
|
+
return {
|
|
81
|
+
type: "object",
|
|
82
|
+
...annotation,
|
|
83
|
+
...properties,
|
|
84
|
+
additionalProperties: schema.additionalProperties === true,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
case "array":
|
|
88
|
+
return {
|
|
89
|
+
type: "array",
|
|
90
|
+
...annotation,
|
|
91
|
+
// DSH 的 array 节点要求 items;内核允许省略(等价于 string),此处按 DSH 口径补齐。
|
|
92
|
+
items: schema.items !== undefined ? projectValueSchema(schema.items) : { type: "string" },
|
|
93
|
+
}
|
|
94
|
+
case "string":
|
|
95
|
+
case "number":
|
|
96
|
+
case "integer":
|
|
97
|
+
case "boolean":
|
|
98
|
+
case "null":
|
|
99
|
+
return {
|
|
100
|
+
type: schema.type,
|
|
101
|
+
...annotation,
|
|
102
|
+
...(Array.isArray(schema.enum) ? { enum: [...schema.enum] } : {}),
|
|
103
|
+
...(schema.default !== undefined ? { default: schema.default } : {}),
|
|
104
|
+
}
|
|
105
|
+
default:
|
|
106
|
+
// 内核 schema 未声明 type(无约束对象)时按 DSH 的无约束 JSON 节点投影。
|
|
107
|
+
return { type: "json", ...annotation }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 投影对象节点的 properties,并按内核 required 列表把必填项标注为 `required: true`。 */
|
|
112
|
+
function projectProperties(schema: CoreJsonSchema): { properties: Record<string, DshValueSchema> } | Record<string, never> {
|
|
113
|
+
const source = schema.properties ?? {}
|
|
114
|
+
const required = new Set(schema.required ?? [])
|
|
115
|
+
const properties: Record<string, DshValueSchema> = {}
|
|
116
|
+
for (const [key, sub] of Object.entries(source)) {
|
|
117
|
+
const projected = projectValueSchema(sub)
|
|
118
|
+
properties[key] = required.has(key) ? { ...projected, required: true } : projected
|
|
119
|
+
}
|
|
120
|
+
return Object.keys(properties).length > 0 ? { properties } : {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 把一份内核纯 JSON Schema(工具参数根)投影为 DSH 工具 parameters。
|
|
125
|
+
* 参数根恒为对象:DSH 的 parameters 就是属性映射本身,不再额外包一层 object。
|
|
126
|
+
*/
|
|
127
|
+
export function jsonSchemaToToolParameters(schema: CoreJsonSchema): ToolParameters {
|
|
128
|
+
return projectProperties(schema).properties ?? {}
|
|
129
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - --port <n> HTTP transport 端口(默认 4525)
|
|
5
5
|
* - --transport http|stdio(默认 http)
|
|
6
6
|
* - --unattended 默认无人值守(claude code / codex / zcode 适配器分发时开启)
|
|
7
|
-
* - --strip-opx-prefix 去掉 MCP 工具名的 opx_
|
|
7
|
+
* - --strip-opx-prefix 去掉 MCP 工具名的 opx_ 前缀(得到 status 这类短名,供 MCP client 桥接形态使用)
|
|
8
8
|
*
|
|
9
9
|
* 运行:node <entry> --transport stdio --worktree . --unattended
|
|
10
10
|
*/
|
|
@@ -18,12 +18,9 @@ import {
|
|
|
18
18
|
completeTaskGroupExecute, setUnattendedExecute,
|
|
19
19
|
} from "../../core/tools/lifecycle.ts"
|
|
20
20
|
import { agentSubmitExecute } from "../../core/tools/submit.ts"
|
|
21
|
-
import {
|
|
22
|
-
orchInitSchema, setWorktreeSchema, statusSchema,
|
|
23
|
-
completeTaskGroupSchema, setUnattendedSchema, agentSubmitSchema,
|
|
24
|
-
} from "../../core/tools/schemas.ts"
|
|
25
21
|
import { readStateByWorktree, writeState } from "../../core/state.ts"
|
|
26
22
|
import { jsonSchemaToZod } from "./json-schema.ts"
|
|
23
|
+
import { OPX_TOOL_SPECS } from "../tool-specs.ts"
|
|
27
24
|
|
|
28
25
|
/** 私有身份参数:附加到每个工具的 MCP 暴露 schema(下划线前缀表示非业务参数)。 */
|
|
29
26
|
const AGENT_ARG = "_agent"
|
|
@@ -34,45 +31,21 @@ interface ToolSpec {
|
|
|
34
31
|
execute(args: Record<string, unknown>, ctx: ToolContext): Promise<string>
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
description:
|
|
46
|
-
"确保目标组的 git worktree 就绪。change 会话按 change 模型 create-or-reuse:分支 change/{changeId} 不存在时从基准分支 tip 创建,worktree 常驻于 .worktree/{changeId}/ws,全部任务组串行复用(复用校验:目录缺失自愈重建、openspec 文档脏自动提交、代码文件脏拒绝)。只补齐资源,不改变阶段。",
|
|
47
|
-
schema: setWorktreeSchema,
|
|
48
|
-
execute: (args, ctx) => setWorktreeExecute(args as any, ctx),
|
|
49
|
-
},
|
|
50
|
-
opx_status: {
|
|
51
|
-
description:
|
|
52
|
-
"统一状态/上下文查询(只读为主)。按调用者角色路由:编排视角→统计+worktree;architect→spec/blocker;developer→worktree/boundary/task/issue;reviewer-tool→tool 层控件 issue;reviewer-task→task 验证状态;quality reviewer→自维度既有 issue。",
|
|
53
|
-
schema: statusSchema,
|
|
54
|
-
execute: (args, ctx) => statusExecute({ change_id: (args as any).change_id }, ctx),
|
|
55
|
-
},
|
|
56
|
-
opx_orch_complete_task_group: {
|
|
57
|
-
description:
|
|
58
|
-
"完成任务组收尾。非最后任务组仅做门禁与范围标记(不合并、不销毁);最后一个任务组收口时把 change 分支(change/{changeId})一次性合并回 baseBranch(漂移无害——文档、openspec 规划路径或仅版本号变更的 package.json——时直接合并收口;漂移含任一其他文件或文本冲突时回退到收尾验证 verify_cleanup 并返回 blocked),成功后销毁 worktree 并删分支。须在收尾验证(verify_cleanup)通过后调用。主仓库本地改动文件与合并写入文件重合、或存在部分暂存文件时中止并返回 blocked(保留 worktree/分支);主仓库无关脏文件不阻塞合并。",
|
|
59
|
-
schema: completeTaskGroupSchema,
|
|
60
|
-
execute: (args, ctx) => completeTaskGroupExecute(args as any, ctx),
|
|
61
|
-
},
|
|
62
|
-
opx_orch_set_unattended: {
|
|
63
|
-
description:
|
|
64
|
-
"开启/关闭无人值守模式。开启后编排流程不再向用户提问:analyze 确认模式由架构师自行裁决(不确认用户);重试检查点、状态异常、blocker 处理等需拍板事项由主代理按编排行为准则自行决策并提交——检查点决策是 opx_agent_submit 工具调用,主代理可自行执行,不因无人值守而抑制。",
|
|
65
|
-
schema: setUnattendedSchema,
|
|
66
|
-
execute: (args, ctx) => setUnattendedExecute(args as any, ctx),
|
|
67
|
-
},
|
|
68
|
-
opx_agent_submit: {
|
|
69
|
-
description:
|
|
70
|
-
"通用 step 提交,按 step_id 路由到 workflow 对应 step。校验调用者属于该 step 的 agents(越权直接拒绝),提交后推进 workflow 状态机并写回编排状态。可通过 exempt_adjudications 对已申请豁免的 issue 进行裁定(dismissed→cancelled、rejected→回 todo);可通过 recheck_adjudications 复核已修复待复核(review 态)的 issue(passed→done、rejected→回 todo 并记 refix_count 与 reject_reason,谁提谁裁定)。",
|
|
71
|
-
schema: agentSubmitSchema,
|
|
72
|
-
execute: (args, ctx) => agentSubmitExecute(args as any, ctx),
|
|
73
|
-
},
|
|
34
|
+
/** 工具名 → 内核执行器;名称与描述取自共享规格表(src/adapters/tool-specs.ts)。 */
|
|
35
|
+
const TOOL_EXECUTORS: Record<string, (args: any, ctx: ToolContext) => Promise<string>> = {
|
|
36
|
+
opx_orch_init: (args, ctx) => initExecute(args, ctx),
|
|
37
|
+
opx_orch_set_worktree: (args, ctx) => setWorktreeExecute(args, ctx),
|
|
38
|
+
opx_status: (args, ctx) => statusExecute({ change_id: args.change_id }, ctx),
|
|
39
|
+
opx_orch_complete_task_group: (args, ctx) => completeTaskGroupExecute(args, ctx),
|
|
40
|
+
opx_orch_set_unattended: (args, ctx) => setUnattendedExecute(args, ctx),
|
|
41
|
+
opx_agent_submit: (args, ctx) => agentSubmitExecute(args, ctx),
|
|
74
42
|
}
|
|
75
43
|
|
|
44
|
+
/** 规格表与执行器的合并视图,保持 buildMcpServer 的注册遍历不变。 */
|
|
45
|
+
const TOOL_SPECS: Record<string, ToolSpec> = Object.fromEntries(
|
|
46
|
+
OPX_TOOL_SPECS.map((spec) => [spec.name, { description: spec.description, schema: spec.schema, execute: TOOL_EXECUTORS[spec.name]! }]),
|
|
47
|
+
)
|
|
48
|
+
|
|
76
49
|
/** 附加 _agent 身份参数到 P1 schema(仅 MCP 暴露层)。 */
|
|
77
50
|
function withAgentArg(schema: JSONSchema): JSONSchema {
|
|
78
51
|
return {
|