dsh-plugin-prompt-tool 0.7.2 → 0.8.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/README.md +185 -46
- package/engine/THIRD_PARTY_LICENSES +25 -0
- package/engine/actions.mjs +931 -0
- package/engine/anchor-match.mjs +6 -3
- package/engine/character-tools.mjs +11 -0
- package/engine/classify-task.mjs +18 -0
- package/engine/compaction-epoch.mjs +41 -20
- package/engine/compositions/library/agent-instructions.yml +9 -0
- package/engine/compositions/library/command-goal.yml +2 -6
- package/engine/compositions/library/compaction.yml +2 -32
- package/engine/compositions/library/delegation-ptc.yml +58 -0
- package/engine/compositions/library/delegation.yml +5 -38
- package/engine/compositions/library/persistent-shell.yml +4 -13
- package/engine/compositions/library/planning.yml +8 -23
- package/engine/compositions/library/skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/skill-filesystem.yml +2 -8
- package/engine/compositions/library/tool-ask-user.yml +2 -3
- package/engine/compositions/library/tool-bash.yml +4 -12
- package/engine/compositions/library/tool-cordis.yml +7 -0
- package/engine/compositions/library/tool-fs-search.yml +2 -11
- package/engine/compositions/library/tool-fs.yml +2 -5
- package/engine/compositions/library/tool-goal.yml +2 -6
- package/engine/compositions/library/tool-jobs.yml +2 -18
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +8 -0
- package/engine/compositions/library/tool-plugin-manager.yml +8 -0
- package/engine/compositions/library/tool-present.yml +7 -0
- package/engine/compositions/library/tool-presentation.yml +9 -0
- package/engine/compositions/library/tool-pwsh.yml +2 -6
- package/engine/compositions/library/tool-skill.yml +7 -0
- package/engine/compositions/library/tool-todo.yml +2 -4
- package/engine/compositions/library/tool-web.yml +2 -1
- package/engine/compositions/source/local/character-tools.yml +5 -0
- package/engine/compositions/source/local/declared-triggers.yml +12 -0
- package/engine/compositions/source/local/filesystem-editor.yml +30 -0
- package/engine/compositions/source/local/instruction-hint.yml +15 -0
- package/engine/compositions/{library/official-persistent-shell.yml → source/local/persistent-shell-posix.yml} +10 -8
- package/engine/compositions/source/local/prompt-config-engine.yml +3 -9
- package/engine/compositions/source/local/run-code-env.yml +4 -7
- package/engine/compositions/source/local/session-var-tools.yml +5 -0
- package/engine/compositions/source/local/skill-search.yml +3 -9
- package/engine/compositions/source/local/subagent-tool-policy.yml +21 -0
- package/engine/compositions/source/local/tool-bash-disabled.yml +9 -0
- package/engine/compositions/source/local/tool-config-engine.yml +8 -0
- package/engine/compositions/source/local/tool-git-bash.yml +15 -0
- package/engine/compositions/source/local/world-book-tools.yml +5 -0
- package/engine/condition.mjs +83 -0
- package/engine/declared-triggers.mjs +93 -0
- package/engine/executor.mjs +364 -185
- package/engine/fields.mjs +219 -0
- package/engine/fillers.mjs +12 -141
- package/engine/host-package.mjs +51 -0
- package/engine/instruction-hint.mjs +285 -0
- package/engine/interpolate.mjs +157 -29
- package/engine/layers.mjs +495 -75
- package/engine/predicates.mjs +753 -0
- package/engine/prompt-config-engine.mjs +63 -8
- package/engine/run-code-env.mjs +48 -31
- package/engine/schema.mjs +444 -103
- package/engine/sdk-strip.mjs +365 -0
- package/engine/session-var-tools.mjs +11 -0
- package/engine/session-vars.mjs +8 -6
- package/engine/shared.mjs +137 -0
- package/engine/skill-search.mjs +34 -21
- package/engine/st-macros.d.mts +22 -0
- package/engine/st-macros.mjs +208 -0
- package/engine/st-render.mjs +105 -0
- package/engine/st-world-book.d.mts +10 -0
- package/engine/st-world-book.mjs +259 -0
- package/engine/strategies.mjs +31 -20
- package/engine/subagent-tool-policy-core.mjs +400 -0
- package/engine/subagent-tool-policy.mjs +327 -0
- package/engine/tool-config-engine.mjs +207 -145
- package/engine/tool-definition.mjs +143 -0
- package/engine/{custom-bash.mjs → tool-git-bash.mjs} +38 -13
- package/engine/trigger-editor-meta.mjs +48 -0
- package/engine/trigger-spec.mjs +257 -0
- package/engine/trigger.mjs +304 -0
- package/engine/world-book-tools.mjs +11 -0
- package/lib/client.js +16495 -5456
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +628 -382
- package/lib/index.mjs +5391 -2942
- package/lib/paths-CY5psBqE.d.mts +1233 -0
- package/lib/preset-package-BnJqrU4R.mjs +16792 -0
- package/lib/preset-transfer.d.mts +2 -0
- package/lib/preset-transfer.mjs +2 -0
- package/package.json +91 -49
- package/preset/pt-cordis/preset.yml +43 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +29 -0
- package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +501 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +38 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +60 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +13 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +5 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +22 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +3 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +2 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +15 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +8 -0
- package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +7 -0
- package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +84 -0
- package/preset/pt-custom/preset.yml +15 -0
- package/preset/pt-minimal/preset.yml +27 -0
- package/preset/pt-ptc/preset.yml +45 -0
- package/preset/pt-standard/preset.yml +43 -0
- package/templates/14-first-turn-anchor.yml +4 -4
- package/templates/15-guide-auto.yml +3 -3
- package/templates/16-custom-fallback.yml +1 -0
- package/templates/18-placeholder-env-facts.yml +3 -3
- package/templates/20-system-section.yml +3 -2
- package/templates/65-turn-stop.yml +18 -0
- package/templates/66-subagent-start.yml +14 -0
- package/templates/75-subagent-end.yml +15 -0
- package/templates/tools/10-shell-command.yml +1 -1
- package/templates/tools/40-delegate-character.yml +1 -1
- package/templates/tools/50-delegate-worldbook.yml +1 -1
- package/templates/tools/60-delegate-session-var.yml +1 -1
- package/templates/tools/70-delegate-worldbook-upsert.yml +1 -1
- package/engine/anchor-turn.mjs +0 -68
- package/engine/code-presentation.mjs +0 -103
- package/engine/compositions/library/anchor-turn.yml +0 -10
- package/engine/compositions/library/bootstrap-filesystem.yml +0 -19
- package/engine/compositions/library/code-presentation.yml +0 -8
- package/engine/compositions/library/context-gate.yml +0 -39
- package/engine/compositions/library/cot-drip.yml +0 -11
- package/engine/compositions/library/custom-bash.yml +0 -14
- package/engine/compositions/library/deliberation-gate.yml +0 -11
- package/engine/compositions/library/official-agent-instructions.yml +0 -17
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +0 -10
- package/engine/compositions/library/official-tool-bash.yml +0 -6
- package/engine/compositions/library/official-tool-cordis.yml +0 -13
- package/engine/compositions/library/official-tool-presentation.yml +0 -7
- package/engine/compositions/library/official-tool-skill.yml +0 -14
- package/engine/compositions/library/persona.yml +0 -13
- package/engine/compositions/library/prompt-config-engine.yml +0 -16
- package/engine/compositions/library/run-code-env.yml +0 -15
- package/engine/compositions/library/skill-search.yml +0 -14
- package/engine/compositions/library/str-replace-editor.yml +0 -10
- package/engine/compositions/library/tool-bootstrap.yml +0 -14
- package/engine/compositions/library/tool-config-engine.yml +0 -9
- package/engine/compositions/library/tool-filter.yml +0 -8
- package/engine/compositions/source/local/code-presentation.yml +0 -8
- package/engine/compositions/source/local/context-gate.yml +0 -36
- package/engine/compositions/source/local/custom-bash.yml +0 -11
- package/engine/compositions/source/local/str-replace-editor.yml +0 -10
- package/engine/compositions/source/local/tool-bootstrap.yml +0 -11
- package/engine/compositions/source/local/tool-filter.yml +0 -8
- package/engine/context-gate.mjs +0 -305
- package/engine/cot-drip.mjs +0 -137
- package/engine/deliberation-gate.mjs +0 -127
- package/engine/tool-bootstrap.mjs +0 -476
- package/engine/tool-filter.mjs +0 -80
- package/lib/preset-core.d.mts +0 -10
- package/lib/preset-core.mjs +0 -77
- package/lib/prompt-configs-BnQKRHVb.mjs +0 -884
- package/lib/prompt-configs-CN0sJTUL.d.mts +0 -193
- package/preset/anchored/preset.yml +0 -296
- package/preset/creative/preset.yml +0 -67
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +0 -420
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +0 -165
- package/preset/custom/preset.yml +0 -12
- package/preset/liangshen/preset.yml +0 -75
- package/preset/minimal/preset.yml +0 -45
- package/preset/ptc/preset.yml +0 -58
- package/preset/standard/preset.yml +0 -57
- package/skills/manifest.json +0 -7
- package/skills/sandboxmod/SKILL.md +0 -49
- package/skills/web ui/SKILL.md +0 -42
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent-tool-policy — agent-scoped subagent/subagent_fork shadow 工具。
|
|
3
|
+
*
|
|
4
|
+
* 本模块通过运行中 DSH 入口解析同一份 dsh-tools / dsh-scope,避免生成目录
|
|
5
|
+
* 加载第二份 registry 类型。每个 shadow 只安装到当前 preset generation 的
|
|
6
|
+
* descendant Agent;实例权限在 SubagentStartRequest 创建窗口冻结。
|
|
7
|
+
*
|
|
8
|
+
* ── 能力提供者边界(T4)────────────────────────────────────────────────────
|
|
9
|
+
* 本模块是**能力提供者**,不是触发器。分类判据(与组合源 subagent-tool-policy.yml、
|
|
10
|
+
* tool-config-engine.yml 及 test/engine/provider-boundary.test.mjs 同一份措辞):
|
|
11
|
+
* - 会给模型提供可调用能力(注册工具 / 域 / 服务)的 → 能力提供者;
|
|
12
|
+
* - 干预流程(改提示词、改装配、裁决、追加)的 → 声明式触发器。
|
|
13
|
+
* 因此本模块**不接入** engine/trigger.mjs:它不订阅装配 waterfall,也没有 when/do
|
|
14
|
+
* 声明,只在 agent 创建时向该 agent 的 scope 注册两个可调用工具。影子工具解决的是
|
|
15
|
+
* 「子代理能用什么工具」,属于能力面,不是流程干预面。
|
|
16
|
+
*
|
|
17
|
+
* 样板收敛:
|
|
18
|
+
* - 配置声明走 fields.mjs 的 `defineConfig`(未知键在挂载期 fail loud);
|
|
19
|
+
* - 注册走 disposer 契约,但这里保留 `agent.ctx.effect(...)` 而不是 shared.keepDisposer,
|
|
20
|
+
* 依据见 install() 内的注释(整组原子回滚 + 按 agent 定向撤销句柄);
|
|
21
|
+
* - 降级告警统一 `${name}: <what>; <fallback>` 一个前缀格式。
|
|
22
|
+
*
|
|
23
|
+
* 安全边界(T4 明令不得触碰,改动本模块时同样不得放宽):
|
|
24
|
+
* - 扩权审批门 expansionApproval:requested additional_tools 必须先拿到
|
|
25
|
+
* `allowed-once`,无 approval 通道时 fail loud(不静默放行);
|
|
26
|
+
* - fail loud 语义:策略文件存在但内容非法一律抛错;只有 ENOENT(用户关掉能力卡)
|
|
27
|
+
* 才降级为官方委派行为。
|
|
28
|
+
*
|
|
29
|
+
* 登记入口:`engineProvider`(数据导出),供边界守卫消费。提供者登记与声明式触发器
|
|
30
|
+
* 声明(约定为 `engineTriggers`)**互斥**,同一模块不得同时导出两者。
|
|
31
|
+
*/
|
|
32
|
+
import { readFileSync } from 'node:fs'
|
|
33
|
+
import { fileURLToPath } from 'node:url'
|
|
34
|
+
import { parse as parseYaml } from './vendor/yaml/index.js'
|
|
35
|
+
import { importHostPackage } from './host-package.mjs'
|
|
36
|
+
import {
|
|
37
|
+
buildSubagentToolParameters,
|
|
38
|
+
compileSubagentToolPolicy,
|
|
39
|
+
resolveSubagentToolPolicy,
|
|
40
|
+
} from './subagent-tool-policy-core.mjs'
|
|
41
|
+
import { defineConfig, passthrough } from './fields.mjs'
|
|
42
|
+
|
|
43
|
+
export const name = 'subagent-tool-policy'
|
|
44
|
+
export const inject = ['agents', 'subagents', 'tools']
|
|
45
|
+
|
|
46
|
+
const [{ ToolArgsError }, { scopeChainOf, scopeOf }] = await Promise.all([
|
|
47
|
+
importHostPackage('@deepseek-ai/dsh-tools'),
|
|
48
|
+
importHostPackage('@deepseek-ai/dsh-scope'),
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
const BASE_PARAMETERS = {
|
|
52
|
+
description: { type: 'string', description: '被委派任务的简短说明(展示用)' },
|
|
53
|
+
prompt: { type: 'string', description: '子代理用户消息内容' },
|
|
54
|
+
run_in_background: { type: 'boolean', description: '后台可续跑,立即返回 durable 子代理 id' },
|
|
55
|
+
}
|
|
56
|
+
const SPAWN_PARAMETERS = {
|
|
57
|
+
provider: { type: 'string', description: '子代理 LLM provider;与 model 成对提供' },
|
|
58
|
+
model: { type: 'string', description: '子代理模型 id;与 provider 成对提供' },
|
|
59
|
+
reasoning_effort: { type: 'string', description: '子代理推理强度' },
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 两个 shadow 的模型可见工具名:注册点与 engineProvider 登记共用这一份,防清单漂移。 */
|
|
63
|
+
const SHADOW_TOOL_NAMES = { spawn: 'subagent', fork: 'subagent_fork' }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 配置契约:白名单由字段声明派生(此前没有白名单,未知键被静默忽略)。
|
|
67
|
+
* 五个键**全部**用 passthrough:迁移前它们对非字符串/非对象值都是静默取默认或忽略
|
|
68
|
+
*(policyFile / spawnProvider / forkProvider 取默认名,agentOptions 非对象则 undefined,
|
|
69
|
+
* maxDepth 原样透传、由下游按 `'provider-managed'` 或数字自行判定),
|
|
70
|
+
* 换成严格字段类型会引入 B2 未授权的行为变更。归一化结果与迁移前逐字段等价,
|
|
71
|
+
* 唯一新增的是「未知键报错」。
|
|
72
|
+
*/
|
|
73
|
+
export const configContract = defineConfig({
|
|
74
|
+
policyFile: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : '../subagent-tools/policy.yml')),
|
|
75
|
+
spawnProvider: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : 'spawn')),
|
|
76
|
+
forkProvider: passthrough((value) => (typeof value === 'string' && value.length > 0 ? value : 'fork')),
|
|
77
|
+
maxDepth: passthrough((value) => value),
|
|
78
|
+
agentOptions: passthrough((value) => value),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 能力提供者登记(T4 边界守卫的数据源)。
|
|
83
|
+
* `provides.kind === 'fixed'`:工具名在装配期就是确定的(两种委派入口各一个 shadow),
|
|
84
|
+
* 守卫据此断言「提供者登记的工具名 = 实际注册的工具名」,并断言它们不出现在触发器声明里。
|
|
85
|
+
*/
|
|
86
|
+
export const engineProvider = {
|
|
87
|
+
kind: 'provider',
|
|
88
|
+
moduleId: name,
|
|
89
|
+
registers: 'tools',
|
|
90
|
+
provides: { kind: 'fixed', tools: Object.values(SHADOW_TOOL_NAMES) },
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolvePolicyFile(config) {
|
|
94
|
+
const raw = typeof config?.policyFile === 'string' && config.policyFile.length > 0
|
|
95
|
+
? config.policyFile
|
|
96
|
+
: '../subagent-tools/policy.yml'
|
|
97
|
+
return fileURLToPath(new URL(raw, import.meta.url))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function loadCompiledPolicy(config) {
|
|
101
|
+
const raw = readFileSync(resolvePolicyFile(config), 'utf8')
|
|
102
|
+
const parsed = parseYaml(raw, { logLevel: 'silent' })
|
|
103
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
104
|
+
throw new TypeError('policy.yml must be a YAML map')
|
|
105
|
+
}
|
|
106
|
+
return compileSubagentToolPolicy(parsed)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function providerFor(kind, config) {
|
|
110
|
+
if (kind === 'fork') return typeof config?.forkProvider === 'string' && config.forkProvider.length > 0 ? config.forkProvider : 'fork'
|
|
111
|
+
return typeof config?.spawnProvider === 'string' && config.spawnProvider.length > 0 ? config.spawnProvider : 'spawn'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function assertString(value, field, required = false) {
|
|
115
|
+
if (value === undefined && !required) return
|
|
116
|
+
if (typeof value !== 'string' || value.length === 0) throw new ToolArgsError([`${field}: expected non-empty string`])
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validateShadowArgs(args, compiled, kind) {
|
|
120
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args)) throw new ToolArgsError(['arguments: expected object'])
|
|
121
|
+
assertString(args.description, 'description', true)
|
|
122
|
+
assertString(args.prompt, 'prompt', true)
|
|
123
|
+
if (args.run_in_background !== undefined && typeof args.run_in_background !== 'boolean') {
|
|
124
|
+
throw new ToolArgsError(['run_in_background: expected boolean'])
|
|
125
|
+
}
|
|
126
|
+
for (const field of ['tool_profile', 'character_id', 'task_type']) assertString(args[field], field)
|
|
127
|
+
if (kind === 'spawn') {
|
|
128
|
+
for (const field of ['provider', 'model', 'reasoning_effort']) assertString(args[field], field)
|
|
129
|
+
if ((args.provider === undefined) !== (args.model === undefined)) {
|
|
130
|
+
throw new ToolArgsError(['provider and model must be supplied together'])
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const properties = buildSubagentToolParameters(compiled)
|
|
134
|
+
for (const field of ['tool_profile', 'character_id', 'task_type']) {
|
|
135
|
+
if (args[field] !== undefined && !properties[field]?.enum?.includes(args[field])) {
|
|
136
|
+
throw new ToolArgsError([`${field}: value is not model-selectable`])
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const field of ['additional_tools', 'restrict_tools']) {
|
|
140
|
+
if (args[field] !== undefined && (!Array.isArray(args[field]) || args[field].some((item) => typeof item !== 'string' || item.length === 0))) {
|
|
141
|
+
throw new ToolArgsError([`${field}: expected an array of non-empty strings`])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (args.additional_tools !== undefined) {
|
|
145
|
+
if (properties.additional_tools === undefined) throw new ToolArgsError(['additional_tools: expansion is disabled'])
|
|
146
|
+
if (args.additional_tools.length > compiled.expansion.maxAdditionalTools) {
|
|
147
|
+
throw new ToolArgsError([`additional_tools: exceeds maxAdditionalTools (${compiled.expansion.maxAdditionalTools})`])
|
|
148
|
+
}
|
|
149
|
+
const invalid = args.additional_tools.filter((tool) => !properties.additional_tools.items.enum.includes(tool))
|
|
150
|
+
if (invalid.length > 0) throw new ToolArgsError([`additional_tools: unauthorized tools ${invalid.join(', ')}`])
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function expansionApproval(ctx, run, toolName, resolved) {
|
|
155
|
+
const approval = ctx.get('approval')
|
|
156
|
+
if (approval === undefined || typeof approval.request !== 'function') {
|
|
157
|
+
throw new Error(`tool ${toolName} requests additional_tools, but no approval channel is available`)
|
|
158
|
+
}
|
|
159
|
+
const outcome = await approval.request({
|
|
160
|
+
agent: run.agent,
|
|
161
|
+
toolName,
|
|
162
|
+
callId: run.callId,
|
|
163
|
+
reason: `parent=${run.agent.id}; profile=${resolved.profileId}; character=${resolved.characterId ?? '-'}; task=${resolved.taskType ?? '-'}; additional=[${resolved.additionalTools.join(', ')}]; effective=[${resolved.effectiveTools.join(', ')}]`,
|
|
164
|
+
signal: run.signal,
|
|
165
|
+
})
|
|
166
|
+
if (outcome !== 'allowed-once') throw new Error(`approval for ${toolName} additional_tools was ${outcome}`)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function configuredAgentOptions(config) {
|
|
170
|
+
const raw = config?.agentOptions
|
|
171
|
+
return raw !== null && typeof raw === 'object' && !Array.isArray(raw) ? { ...raw } : undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function requestedAgentOptions(config, request) {
|
|
175
|
+
const baseline = configuredAgentOptions(config)
|
|
176
|
+
if (request.provider === undefined && request.model === undefined && request.reasoning_effort === undefined) return baseline
|
|
177
|
+
return {
|
|
178
|
+
...(baseline ?? {}),
|
|
179
|
+
...(request.provider !== undefined ? { provider: request.provider, model: request.model } : {}),
|
|
180
|
+
...(request.reasoning_effort !== undefined ? { reasoningEffort: request.reasoning_effort } : {}),
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function preflightRoute(ctx, parent, options, signal) {
|
|
185
|
+
if (options === undefined) return
|
|
186
|
+
const provider = options.provider ?? parent.options?.provider
|
|
187
|
+
const model = options.model ?? parent.options?.model
|
|
188
|
+
if (provider === undefined || model === undefined) throw new Error('cannot select child LLM values without an effective provider and model')
|
|
189
|
+
const llm = ctx.get('llm')
|
|
190
|
+
if (llm === undefined || typeof llm.resolveCallConfig !== 'function') {
|
|
191
|
+
throw new Error('cannot resolve the selected child LLM route because the llm service is unavailable')
|
|
192
|
+
}
|
|
193
|
+
await llm.resolveCallConfig({
|
|
194
|
+
provider,
|
|
195
|
+
model,
|
|
196
|
+
...(options.reasoningEffort === undefined ? {} : { reasoningEffort: options.reasoningEffort }),
|
|
197
|
+
...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
|
|
198
|
+
}, signal)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function settleForeground(run) {
|
|
202
|
+
const [execution] = await Promise.allSettled([run.result])
|
|
203
|
+
const [disposal] = await Promise.allSettled([Promise.resolve().then(() => run.dispose())])
|
|
204
|
+
if (execution.status === 'rejected') throw execution.reason
|
|
205
|
+
if (disposal.status === 'rejected') throw disposal.reason
|
|
206
|
+
const result = execution.value
|
|
207
|
+
if (result.stopReason !== 'completed') {
|
|
208
|
+
throw new Error(`subagent stopped with ${result.stopReason}${result.diagnostic === undefined ? '' : `: ${result.diagnostic}`}`)
|
|
209
|
+
}
|
|
210
|
+
return { kind: 'foreground', runId: run.id, output: result.output }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function availablePresetTools(tools, compositionScope) {
|
|
214
|
+
return tools.schemas(compositionScope).map((schema) => schema.name)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function createShadowTool(ctx, tools, compositionScope, compiled, kind, config) {
|
|
218
|
+
const toolName = SHADOW_TOOL_NAMES[kind]
|
|
219
|
+
const provider = providerFor(kind, config)
|
|
220
|
+
const properties = { ...BASE_PARAMETERS, ...(kind === 'spawn' ? SPAWN_PARAMETERS : {}), ...buildSubagentToolParameters(compiled) }
|
|
221
|
+
return {
|
|
222
|
+
name: toolName,
|
|
223
|
+
description: `创建子代理执行委派任务;实例工具权限由 subagentToolPolicy 解析并在创建时冻结。`,
|
|
224
|
+
parameters: { type: 'object', properties, required: ['description', 'prompt'] },
|
|
225
|
+
output: { schema: { type: 'object', additionalProperties: true }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }] },
|
|
226
|
+
execute: async (args, run) => {
|
|
227
|
+
validateShadowArgs(args, compiled, kind)
|
|
228
|
+
if (run.agent === undefined) throw new Error('no active agent for subagent start')
|
|
229
|
+
const resolved = resolveSubagentToolPolicy(compiled, args, availablePresetTools(tools, compositionScope))
|
|
230
|
+
if (resolved.requiresApproval) await expansionApproval(ctx, run, toolName, resolved)
|
|
231
|
+
const childProvider = ctx.subagents.getProvider(provider)
|
|
232
|
+
if (childProvider === undefined) throw new Error(`subagent provider ${provider} is not registered`)
|
|
233
|
+
if (childProvider.capabilities?.toolFilter !== true) {
|
|
234
|
+
throw new Error(`provider ${provider} does not support per-child toolFilter`)
|
|
235
|
+
}
|
|
236
|
+
const maxDepth = config?.maxDepth === 'provider-managed' ? undefined : config?.maxDepth
|
|
237
|
+
if (typeof maxDepth === 'number' && childProvider.capabilities?.depthLimit !== true) {
|
|
238
|
+
throw new Error(`provider ${provider} cannot enforce maxDepth`)
|
|
239
|
+
}
|
|
240
|
+
const agentOptions = kind === 'spawn' ? requestedAgentOptions(config, args) : undefined
|
|
241
|
+
if (agentOptions !== undefined && childProvider.capabilities?.agentOptions !== true) {
|
|
242
|
+
throw new Error(`provider ${provider} does not support child agentOptions`)
|
|
243
|
+
}
|
|
244
|
+
if (kind === 'spawn' && agentOptions !== undefined) {
|
|
245
|
+
await preflightRoute(ctx, run.agent, agentOptions, run.signal)
|
|
246
|
+
if (ctx.subagents.getProvider(provider) !== childProvider) throw new Error(`subagent provider ${provider} changed during route preflight`)
|
|
247
|
+
}
|
|
248
|
+
run.signal.throwIfAborted()
|
|
249
|
+
const request = {
|
|
250
|
+
prompt: [{ type: 'text', text: args.prompt }],
|
|
251
|
+
parent: run.agent,
|
|
252
|
+
...(agentOptions === undefined ? {} : { agentOptions }),
|
|
253
|
+
...(maxDepth === undefined ? {} : { maxDepth }),
|
|
254
|
+
toolFilter: { allow: resolved.effectiveTools },
|
|
255
|
+
}
|
|
256
|
+
const policy = {
|
|
257
|
+
profile: resolved.profileId,
|
|
258
|
+
characterId: resolved.characterId ?? null,
|
|
259
|
+
taskType: resolved.taskType ?? null,
|
|
260
|
+
adoptedSelectors: resolved.adopted,
|
|
261
|
+
ignoredSelectors: resolved.ignored,
|
|
262
|
+
additionalTools: resolved.additionalTools,
|
|
263
|
+
effectiveTools: resolved.effectiveTools,
|
|
264
|
+
}
|
|
265
|
+
if (args.run_in_background !== false) {
|
|
266
|
+
if (childProvider.prepareContinuable === undefined) throw new Error(`provider ${provider} does not support continuable children`)
|
|
267
|
+
const child = await ctx.subagents.startContinuable({ provider, label: args.description, request, signal: run.signal })
|
|
268
|
+
return { kind: 'continuable', subagentId: child.childId, policy }
|
|
269
|
+
}
|
|
270
|
+
const childRun = await ctx.subagents.start(provider, { ...request, label: args.description, signal: run.signal })
|
|
271
|
+
return { ...(await settleForeground(childRun)), policy }
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function apply(ctx, config) {
|
|
277
|
+
// 校验入口:未知键在挂载期报错;五个键的归一化结果与迁移前逐字段一致(见 configContract)。
|
|
278
|
+
const source = configContract.parse(config, name)
|
|
279
|
+
let compiled
|
|
280
|
+
try {
|
|
281
|
+
compiled = loadCompiledPolicy(source)
|
|
282
|
+
} catch (error) {
|
|
283
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
284
|
+
// 策略文件缺失 = 用户在能力卡里把开关关掉了(模块声明保留):降级为官方委派行为,
|
|
285
|
+
// 不注册 shadow 工具,也不让整个预设启动失败。文件在但内容损坏才是真错误——fail loud。
|
|
286
|
+
if (error?.code === 'ENOENT') {
|
|
287
|
+
ctx.logger?.warn(`${name}: policy file missing, subagent tool policy disabled; official delegation behavior kept`)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`${name}: cannot load policy: ${message}`)
|
|
291
|
+
}
|
|
292
|
+
const compositionScope = scopeOf(ctx)
|
|
293
|
+
if (compositionScope === undefined) throw new Error(`${name}: requires a preset scope`)
|
|
294
|
+
const installs = new WeakMap()
|
|
295
|
+
const belongsToComposition = (agent) => scopeChainOf(scopeOf(agent.ctx)).includes(compositionScope)
|
|
296
|
+
const install = (agent) => {
|
|
297
|
+
if (installs.has(agent) || !belongsToComposition(agent)) return
|
|
298
|
+
// 注册走 disposer 契约。这里保留 ctx.effect 而不是 shared.keepDisposer,两条依据:
|
|
299
|
+
// (1) 两个 shadow 必须整组注册:任一个失败时由 Cordis 回滚已注册的那个
|
|
300
|
+
// (keepDisposer 无回滚语义,第二个失败会留下第一个的残留注册);
|
|
301
|
+
// (2) remove(agent) 需要按 agent 定向撤销,而 keepDisposer 返回 void,拿不到句柄。
|
|
302
|
+
const dispose = agent.ctx.effect(() => {
|
|
303
|
+
const disposers = [
|
|
304
|
+
agent.ctx.tools.register(createShadowTool(ctx, agent.ctx.tools, compositionScope, compiled, 'spawn', source)),
|
|
305
|
+
agent.ctx.tools.register(createShadowTool(ctx, agent.ctx.tools, compositionScope, compiled, 'fork', source)),
|
|
306
|
+
]
|
|
307
|
+
return () => { for (const item of disposers) item() }
|
|
308
|
+
}, `${name}: ${agent.id} shadow`)
|
|
309
|
+
installs.set(agent, dispose)
|
|
310
|
+
}
|
|
311
|
+
const remove = (agent) => {
|
|
312
|
+
const dispose = installs.get(agent)
|
|
313
|
+
if (dispose === undefined) return
|
|
314
|
+
installs.delete(agent)
|
|
315
|
+
void Promise.resolve(dispose()).catch((error) => ctx.logger?.warn(`${name}: shadow cleanup failed: ${String(error)}`))
|
|
316
|
+
}
|
|
317
|
+
ctx.on('agent/created', ({ agent }) => install(agent))
|
|
318
|
+
ctx.on('agent/disposed', ({ agent }) => remove(agent))
|
|
319
|
+
const reconcile = () => {
|
|
320
|
+
for (const agent of ctx.agents.list()) {
|
|
321
|
+
if (belongsToComposition(agent)) install(agent)
|
|
322
|
+
else remove(agent)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
ctx.on('tools/change', reconcile)
|
|
326
|
+
reconcile()
|
|
327
|
+
}
|