dsh-root-planner-preset 1.4.1 → 1.4.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/build-patch.mjs +11 -6
- package/child-tools.mjs +19 -5
- package/cordis.patch.yml +11 -6
- package/package.json +1 -3
- package/verify-patch.mjs +49 -84
- package/worker-mask.js +0 -30
package/build-patch.mjs
CHANGED
|
@@ -236,10 +236,17 @@ const patch = `# Generated by build-patch.mjs from persona.txt. Do not hand-edit
|
|
|
236
236
|
# default persona instead of this preset's.
|
|
237
237
|
#
|
|
238
238
|
# The worker preset carries the same persona (it addresses both roles) and only the tool
|
|
239
|
-
# plugins a worker needs.
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
239
|
+
# plugins a worker needs. What the worker can call is decided HERE, by which rows exist:
|
|
240
|
+
# in 0.2.1 a preset's rows register their tools per agent scope, and tools.restrict()
|
|
241
|
+
# filters GLOBAL tools only — "restrictions intersect; scoped registrations remain
|
|
242
|
+
# visible". A restriction therefore cannot narrow a preset's own tools, which is why an
|
|
243
|
+
# earlier attempt on this branch mounted a mask row and failed with
|
|
244
|
+
# tools.restrict() names unknown global tools "read", "glob", "write", ...
|
|
245
|
+
# known global tools: ops_glob, ops_grep, ops_inspect_local_file, ops_replace,
|
|
246
|
+
# working_directory
|
|
247
|
+
# That short list is the whole set of globally mounted tools on this machine; every other
|
|
248
|
+
# tool a worker sees comes from a row below. So the curated set is the row list itself,
|
|
249
|
+
# and omitting a row really does remove its tools.
|
|
243
250
|
- insert:
|
|
244
251
|
- id: preset-root-planner-worker
|
|
245
252
|
name: '@deepseek-ai/dsh-agent-preset'
|
|
@@ -259,8 +266,6 @@ const patch = `# Generated by build-patch.mjs from persona.txt. Do not hand-edit
|
|
|
259
266
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
260
267
|
config:
|
|
261
268
|
maxBytes: 65536
|
|
262
|
-
- id: worker-mask
|
|
263
|
-
name: 'dsh-root-planner-preset/worker-mask'
|
|
264
269
|
- id: tool-bash
|
|
265
270
|
name: '@deepseek-ai/dsh-tool-bash'
|
|
266
271
|
disabled: !!js process.platform === 'win32'
|
package/child-tools.mjs
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
2
|
+
* The expected worker toolset — the list this bundle wants a subagent to end up with, kept
|
|
3
|
+
* in one place so the intent is stated once.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* ## It is no longer a runtime mask, and cannot be one
|
|
6
|
+
*
|
|
7
|
+
* Until DSH 0.2.0 this list was handed to `dsh-tool-subagent` as `toolFilter.allow`.
|
|
8
|
+
* 0.2.1 removed that field: zod strips it silently and the child then runs the platform's
|
|
9
|
+
* default preset. The replacement the error message names is a preset (`agentPreset`), so
|
|
10
|
+
* what a worker can call is decided by which rows the `root-planner-worker` preset mounts.
|
|
11
|
+
*
|
|
12
|
+
* A `tools.restrict()` stand-in was tried and cannot work either: a preset's rows register
|
|
13
|
+
* their tools in the agent scope, and a restriction filters GLOBAL tools only
|
|
14
|
+
* ("restrictions intersect; scoped registrations remain visible"). On this machine the
|
|
15
|
+
* globally mounted set is just `ops_glob`, `ops_grep`, `ops_inspect_local_file`,
|
|
16
|
+
* `ops_replace` and `working_directory`; everything else a worker sees comes from a row.
|
|
17
|
+
*
|
|
18
|
+
* So this file is the documented expectation, verified against a live session rather than
|
|
19
|
+
* asserted against a snapshot: `verify-patch.mjs` proves the worker preset's row list, and
|
|
20
|
+
* `tools/extract-offered-tools.mjs` reads what a real child was actually offered.
|
|
7
21
|
*
|
|
8
22
|
* ## What a subagent MAY do
|
|
9
23
|
*
|
|
@@ -12,7 +26,7 @@
|
|
|
12
26
|
* project cannot run tests, git, or a verification script, so every executable
|
|
13
27
|
* step bounces back to the root and the parallelism the tree exists for buys
|
|
14
28
|
* nothing. `write`, `edit`, `ops_replace`, `ops_bash` and `pwsh` are all in the
|
|
15
|
-
*
|
|
29
|
+
* list for that reason.
|
|
16
30
|
*
|
|
17
31
|
* ## What is forbidden, and how it is enforced
|
|
18
32
|
*
|
package/cordis.patch.yml
CHANGED
|
@@ -253,10 +253,17 @@
|
|
|
253
253
|
# default persona instead of this preset's.
|
|
254
254
|
#
|
|
255
255
|
# The worker preset carries the same persona (it addresses both roles) and only the tool
|
|
256
|
-
# plugins a worker needs.
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
256
|
+
# plugins a worker needs. What the worker can call is decided HERE, by which rows exist:
|
|
257
|
+
# in 0.2.1 a preset's rows register their tools per agent scope, and tools.restrict()
|
|
258
|
+
# filters GLOBAL tools only — "restrictions intersect; scoped registrations remain
|
|
259
|
+
# visible". A restriction therefore cannot narrow a preset's own tools, which is why an
|
|
260
|
+
# earlier attempt on this branch mounted a mask row and failed with
|
|
261
|
+
# tools.restrict() names unknown global tools "read", "glob", "write", ...
|
|
262
|
+
# known global tools: ops_glob, ops_grep, ops_inspect_local_file, ops_replace,
|
|
263
|
+
# working_directory
|
|
264
|
+
# That short list is the whole set of globally mounted tools on this machine; every other
|
|
265
|
+
# tool a worker sees comes from a row below. So the curated set is the row list itself,
|
|
266
|
+
# and omitting a row really does remove its tools.
|
|
260
267
|
- insert:
|
|
261
268
|
- id: preset-root-planner-worker
|
|
262
269
|
name: '@deepseek-ai/dsh-agent-preset'
|
|
@@ -347,8 +354,6 @@
|
|
|
347
354
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
348
355
|
config:
|
|
349
356
|
maxBytes: 65536
|
|
350
|
-
- id: worker-mask
|
|
351
|
-
name: 'dsh-root-planner-preset/worker-mask'
|
|
352
357
|
- id: tool-bash
|
|
353
358
|
name: '@deepseek-ai/dsh-tool-bash'
|
|
354
359
|
disabled: !!js process.platform === 'win32'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-root-planner-preset",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH agent preset: the root agent plans and decides, subagent workers read, write and run commands inside their own _agents/ workspace, and nothing outlives its task.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./guard.js",
|
|
10
10
|
"./guard": "./guard.js",
|
|
11
|
-
"./worker-mask": "./worker-mask.js",
|
|
12
11
|
"./package.json": "./package.json"
|
|
13
12
|
},
|
|
14
13
|
"files": [
|
|
15
14
|
"guard.js",
|
|
16
|
-
"worker-mask.js",
|
|
17
15
|
"persona.txt",
|
|
18
16
|
"child-tools.mjs",
|
|
19
17
|
"build-patch.mjs",
|
package/verify-patch.mjs
CHANGED
|
@@ -144,20 +144,56 @@ check(existsSync(join(here, 'guard.js')), 'guard.js ships inside the package');
|
|
|
144
144
|
}
|
|
145
145
|
const delegationRows = delegation?.config ?? [];
|
|
146
146
|
|
|
147
|
-
// DSH 0.2.1 removed `toolFilter` from the delegation tool
|
|
148
|
-
// the key and the child then runs the platform's DEFAULT preset
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
147
|
+
// DSH 0.2.1 removed `toolFilter` from the delegation tool, and it does not warn: zod
|
|
148
|
+
// strips the key and the child then runs the platform's DEFAULT preset. The worker's tool
|
|
149
|
+
// set is now decided by which rows the worker preset mounts, because a preset's rows
|
|
150
|
+
// register their tools in the agent scope while `tools.restrict()` filters GLOBAL tools
|
|
151
|
+
// only ("restrictions intersect; scoped registrations remain visible"). A mask row was
|
|
152
|
+
// tried first and failed at activation with:
|
|
153
|
+
// tools.restrict() names unknown global tools "read", "glob", "write", ...
|
|
154
|
+
// known global tools: ops_glob, ops_grep, ops_inspect_local_file, ops_replace,
|
|
155
|
+
// working_directory
|
|
156
|
+
// Those five are the entire global set on this machine; everything else a worker sees
|
|
157
|
+
// comes from a row. So the assertions below are about rows, and they are two-sided:
|
|
158
|
+
// required rows must exist, forbidden rows must not.
|
|
152
159
|
const workerRow = inserted.find((row) => row.id === 'preset-root-planner-worker');
|
|
153
160
|
check(workerRow !== undefined, 'the patch declares a worker preset');
|
|
154
161
|
check(workerRow?.config?.id === 'root-planner-worker', 'the worker preset id is root-planner-worker');
|
|
155
162
|
check(workerRow?.config?.order === 6, 'the worker preset order is 6');
|
|
156
163
|
const workerPlugins = workerRow?.config?.plugins ?? [];
|
|
164
|
+
const workerRowIds = new Set(workerPlugins.map((row) => row.id));
|
|
165
|
+
|
|
166
|
+
/** Rows a worker needs: persona, the working tools, and delegation so the tree recurses. */
|
|
167
|
+
const WORKER_REQUIRED_ROWS = [
|
|
168
|
+
'persona',
|
|
169
|
+
'agent-instructions',
|
|
170
|
+
'tool-pwsh',
|
|
171
|
+
'tool-fs',
|
|
172
|
+
'tool-fs-search',
|
|
173
|
+
'tool-skill',
|
|
174
|
+
'tool-web',
|
|
175
|
+
'tool-todo',
|
|
176
|
+
'tool-ask-user',
|
|
177
|
+
'delegation',
|
|
178
|
+
];
|
|
179
|
+
for (const id of WORKER_REQUIRED_ROWS) {
|
|
180
|
+
check(workerRowIds.has(id), `the worker preset mounts ${id}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Rows a worker must not have. Each one was in the root preset and not here: workflow
|
|
185
|
+
* orchestration, deliverables, job control, plan mode, plugin management. Anything that
|
|
186
|
+
* lets a worker outlive its task or re-plan the tree belongs to the root.
|
|
187
|
+
*/
|
|
188
|
+
const WORKER_FORBIDDEN_ROWS = ['tool-workflow', 'workflow-ptc', 'present', 'tool-jobs', 'tool-plugin-manager', 'planning'];
|
|
189
|
+
for (const id of WORKER_FORBIDDEN_ROWS) {
|
|
190
|
+
check(!workerRowIds.has(id), `the worker preset does not mount ${id}`);
|
|
191
|
+
}
|
|
157
192
|
check(
|
|
158
|
-
workerPlugins.some((row) =>
|
|
159
|
-
'
|
|
193
|
+
!workerPlugins.some((row) => /worker-mask/.test(String(row?.id)) || /worker-mask/.test(String(row?.name))),
|
|
194
|
+
'no mask row is mounted (a restriction cannot narrow a preset\'s own tools)',
|
|
160
195
|
);
|
|
196
|
+
|
|
161
197
|
const workerDelegation = workerPlugins.find((row) => row.id === 'delegation');
|
|
162
198
|
const workerDelegationRows = workerDelegation?.config ?? [];
|
|
163
199
|
|
|
@@ -227,83 +263,12 @@ for (const [label, rows] of [
|
|
|
227
263
|
);
|
|
228
264
|
}
|
|
229
265
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
* Provenance: transcribed on DSH 0.2.1-rc.2. The upgrade from 0.2.0-rc.2 added
|
|
237
|
-
* `working_directory`, the four `schedule_*` tools, `cordis_inspect_list`,
|
|
238
|
-
* `cordis_inspect_query` and `plugin_manager`; all are registered by profile-level tool
|
|
239
|
-
* packages, so they are global. None of them is in the child mask, which is the intended
|
|
240
|
-
* behaviour of an allow list — a new tool is invisible to children until it is named.
|
|
241
|
-
*
|
|
242
|
-
* Regenerate after a DSH upgrade: break the mask on purpose (add a bogus name), let the
|
|
243
|
-
* agent-creation error print the list, and transcribe it — `tools/extract-tool-inventory.mjs`
|
|
244
|
-
* reads exactly that line out of a session log.
|
|
245
|
-
*/
|
|
246
|
-
const OBSERVED_GLOBAL_TOOLS = new Set([
|
|
247
|
-
'ask_user_question',
|
|
248
|
-
'cordis_inspect_list',
|
|
249
|
-
'cordis_inspect_query',
|
|
250
|
-
'create_goal',
|
|
251
|
-
'edit',
|
|
252
|
-
'exit_plan_mode',
|
|
253
|
-
'get_goal',
|
|
254
|
-
'glob',
|
|
255
|
-
'grep',
|
|
256
|
-
'interrupt_agent',
|
|
257
|
-
'job_kill',
|
|
258
|
-
'job_list',
|
|
259
|
-
'job_output',
|
|
260
|
-
'list_agents',
|
|
261
|
-
'ops_glob',
|
|
262
|
-
'ops_grep',
|
|
263
|
-
'ops_inspect_local_file',
|
|
264
|
-
'ops_replace',
|
|
265
|
-
'plugin_manager',
|
|
266
|
-
'present',
|
|
267
|
-
'pwsh',
|
|
268
|
-
'read',
|
|
269
|
-
'read_image',
|
|
270
|
-
'schedule_create',
|
|
271
|
-
'schedule_delete',
|
|
272
|
-
'schedule_list',
|
|
273
|
-
'schedule_update',
|
|
274
|
-
'send_message',
|
|
275
|
-
'skill',
|
|
276
|
-
'subagent_fork',
|
|
277
|
-
'todo_write',
|
|
278
|
-
'update_goal',
|
|
279
|
-
'web_fetch',
|
|
280
|
-
'web_search',
|
|
281
|
-
'workflow',
|
|
282
|
-
'working_directory',
|
|
283
|
-
'write',
|
|
284
|
-
]);
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Tools that are agent-scoped rather than global, so `tools.restrict()` can never name
|
|
288
|
-
* them. `subagent` is the one this bundle ran into: with `modelSelectionSettings: true`
|
|
289
|
-
* it is mounted inside each composed Agent's own scope, and a restriction accepts global
|
|
290
|
-
* names only. The curated set is the source of truth now — the old per-call mask is gone.
|
|
291
|
-
*/
|
|
292
|
-
{
|
|
293
|
-
const unknown = CHILD_TOOLS.filter((name) => !OBSERVED_GLOBAL_TOOLS.has(name));
|
|
294
|
-
const scoped = new Set(SCOPE_LOCAL_TOOLS);
|
|
295
|
-
const unexpected = unknown.filter((name) => !scoped.has(name));
|
|
296
|
-
check(
|
|
297
|
-
unexpected.length === 0,
|
|
298
|
-
`the curated set names only tools the runtime reports as global` +
|
|
299
|
-
(unexpected.length ? ` (not global: ${unexpected.join(', ')})` : ''),
|
|
300
|
-
);
|
|
301
|
-
// `subagent` reaches a worker through the preset's own delegation row rather than the
|
|
302
|
-
// restriction, so it may legitimately sit in the curated set while being scope-local.
|
|
303
|
-
for (const name of unknown) {
|
|
304
|
-
check(scoped.has(name), `the only scope-local name in the curated set is declared as such (${name})`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
266
|
+
// The curated child toolset is not checkable against a static global inventory, and this
|
|
267
|
+
// file must not pretend otherwise. In DSH 0.2.1 a preset row registers its tool in the
|
|
268
|
+
// agent scope, and `tools.restrict()` filters GLOBAL tools only, so the names in
|
|
269
|
+
// `child-tools.mjs` are not global names. What proves the toolset is the row list —
|
|
270
|
+
// asserted above — plus one live run: spawn from a Root Planner session and read the
|
|
271
|
+
// child's `request/header` with `node tools/extract-offered-tools.mjs <session-dir>`.
|
|
307
272
|
|
|
308
273
|
process.stdout.write(failures.length === 0 ? '\nALL CHECKS PASSED\n' : `\n${failures.length} CHECK(S) FAILED\n`);
|
|
309
274
|
process.exit(failures.length === 0 ? 0 : 1);
|
package/worker-mask.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// Restricts a Root Planner worker to the curated child toolset.
|
|
2
|
-
//
|
|
3
|
-
// DSH 0.2.1 removed `toolFilter` from `dsh-tool-subagent` — its error says to "configure
|
|
4
|
-
// persona and tool plugins in an Agent Preset and select it with agentPreset (spawn
|
|
5
|
-
// only)". A preset decides which tool plugins are mounted for that agent, but not which
|
|
6
|
-
// globally mounted tools it may call: several newer tools (tool-cordis, plugin-manager,
|
|
7
|
-
// working-directory, schedule) are registered at the app level and would reach every
|
|
8
|
-
// agent regardless of the preset's rows. `tools.restrict()` is the mechanism that still
|
|
9
|
-
// subtracts them, and this module is that call, mounted as a row inside the worker
|
|
10
|
-
// preset so it applies to the worker's own agent scope.
|
|
11
|
-
//
|
|
12
|
-
// The allow list lives in `child-tools.mjs` — the same file the previous mask used, so
|
|
13
|
-
// the curated set is still declared once.
|
|
14
|
-
|
|
15
|
-
import { CHILD_TOOLS } from './child-tools.mjs'
|
|
16
|
-
|
|
17
|
-
export const name = 'root-planner-worker-mask'
|
|
18
|
-
|
|
19
|
-
export const inject = ['tools']
|
|
20
|
-
|
|
21
|
-
export function apply(ctx) {
|
|
22
|
-
// `tools.restrict()` requires a scoped context: it refuses to run from a global one
|
|
23
|
-
// because that would mask every agent in the process. A preset row is mounted into the
|
|
24
|
-
// agent's own scope, which is exactly the scope this needs.
|
|
25
|
-
ctx.effect(() => {
|
|
26
|
-
const tools = typeof ctx.get === 'function' ? ctx.get('tools') : ctx.tools
|
|
27
|
-
if (tools === undefined) return () => {}
|
|
28
|
-
return tools.restrict({ allow: CHILD_TOOLS })
|
|
29
|
-
})
|
|
30
|
-
}
|