@menghuan1918/dsh-apollo 0.3.0 → 0.4.1

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.
@@ -1,7 +0,0 @@
1
- {
2
- "name": "dsh-preset-eng-mode-instructions",
3
- "private": true,
4
- "type": "module",
5
- "main": "index.js",
6
- "version": "0.1.0"
7
- }
@@ -1,228 +0,0 @@
1
- /**
2
- * `wait_subagent` tool — explicit wait for continuable children to settle.
3
- *
4
- * Preset-local plugin for the eng preset (resolved relative to this preset's
5
- * directory). Continuable subagents live in the `subagents` registry, not the
6
- * `jobs` registry, so the only settlement signal is the scoped `subagent/end`
7
- * event. The tool takes an ARRAY of durable child ids (`subagent_id`), awaits
8
- * them all concurrently (duplicates deduped, order preserved), and returns one
9
- * SHORT line per id (done + stopReason).
10
- *
11
- * The children's content is none of this tool's business: the subagent manager
12
- * unconditionally delivers a settlement notice to the parent BEFORE
13
- * `subagent/end` fires (dispose order: notifySettlement() →
14
- * observer.settle()), carrying the one-line account and the closing message.
15
- * A busy parent is steered, so the notice sits in the pending next-step queue
16
- * and reaches the model right after this tool result — exactly once, per
17
- * child, by the framework. Earlier designs returned the child's final message
18
- * here AND spliced the duplicate notice (and any mid-run `report`) out of the
19
- * parent inbox; that coupled this plugin to `agent.inbox` internals and to
20
- * the `subagent-report` source kind, which upstream removed in
21
- * 0.1.2-alpha.4 (bidirectional `send_message` replaced `report`). Returning
22
- * "done" lines and leaving delivery to the framework has no such coupling.
23
- * Mid-run messages the child sends (`send_message`, source kind
24
- * `agent-message`) likewise stay in place.
25
- *
26
- * `timeout_ms` is ONE shared window over the whole batch (all timers start at
27
- * the same instant), matching the semantics same-message parallel calls had.
28
- * An id that is not a direct child fails the whole call fast — nobody is
29
- * waited for — keeping the contract the single-id version had. A child
30
- * dispatched moments ago may not have started its first turn yet: its status
31
- * is not `running` and no settlement exists. Returning early with a "not
32
- * running" note there (the pre-grace behavior) ended the turn before the
33
- * child even started and forced a re-wait on the next turn — session audit
34
- * 2f5dd618. Instead, a not-yet-started child gets a startup grace window
35
- * (START_GRACE_MS): the tool keeps waiting while the child spins up,
36
- * settles, the optional timeout elapses, or the grace expires without the
37
- * child ever starting (only then a retryable informational line).
38
- *
39
- * Zero imports on purpose: the module loads from the preset directory, where
40
- * Node's upward node_modules walk never reaches the harness install, so bare
41
- * package imports would fail. Everything needed comes from the context.
42
- */
43
-
44
- export const name = 'tool-wait-subagent'
45
-
46
- export const inject = ['tools', 'subagents', 'agents']
47
-
48
- /** Grace window for a just-dispatched child that has not started running yet. */
49
- const START_GRACE_MS = 30_000
50
-
51
- /** Status polling interval while waiting for a not-yet-started child. */
52
- const POLL_MS = 250
53
-
54
- export function apply(ctx) {
55
- /** childId -> { time, info } — latest settlement observed since mount. */
56
- const settled = new Map()
57
- /** childId -> finish(info) callbacks awaiting settlement. */
58
- const waiters = new Map()
59
-
60
- ctx.on('subagent/end', (info) => {
61
- const id = info && info.id
62
- if (typeof id !== 'string') return
63
- settled.set(id, { time: Date.now(), info })
64
- const list = waiters.get(id)
65
- if (list !== undefined) {
66
- waiters.delete(id)
67
- for (const finish of [...list]) finish({ kind: 'settled', info })
68
- }
69
- })
70
-
71
- ctx.tools.register({
72
- name: 'wait_subagent',
73
- description: 'Wait for one or more background continuable subagents (spawned by subagent or subagent_fork) to finish their current turn. Pass their durable ids as an array in subagent_id — all are awaited concurrently and the result carries one short line per id (done + stop reason); a child\'s closing message arrives as the framework settlement notice immediately after this result; do not expect it here. Blocks until every listed child settles, the optional timeout_ms elapses (one shared window over all ids), or the user interrupts — without timeout_ms, until completion. A just-dispatched child that has not started yet is awaited through a brief startup grace rather than reported as not running. Use it when your next step depends on those results; with independent work remaining, prefer background plus completion notices. Do not pass bash job ids — those use job_output.',
74
- parameters: {
75
- type: 'object',
76
- properties: {
77
- subagent_id: {
78
- type: 'array',
79
- items: { type: 'string' },
80
- minItems: 1,
81
- description: 'One or more durable subagent ids returned by the subagent or subagent_fork tool call; every id is awaited concurrently.',
82
- },
83
- timeout_ms: {
84
- type: 'integer',
85
- description: 'Optional maximum wait in milliseconds applied as one shared window over all ids; omit to wait until every child settles. On expiry the tool returns while some children are still running.',
86
- },
87
- },
88
- required: ['subagent_id'],
89
- },
90
- output: {
91
- schema: { type: 'string' },
92
- render: (_args, value) => [{ type: 'text', text: value }],
93
- },
94
- isConcurrencySafe: () => true,
95
- async execute(args, exec) {
96
- // Dedupe preserving input order; the schema already enforces a
97
- // non-empty array, the guard below only covers malformed direct calls.
98
- const ids = [...new Set(Array.isArray(args.subagent_id) ? args.subagent_id : [])]
99
- if (ids.length === 0) return 'subagent_id must be a non-empty array of subagent ids.'
100
- const parent = exec.agent
101
- if (parent === undefined) throw new Error('wait_subagent requires a calling agent (exec.agent was undefined)')
102
-
103
- // Verify every id is one of this agent's direct children — fail fast,
104
- // waiting for nobody (the contract the single-id version had).
105
- let entries
106
- try {
107
- entries = await ctx.subagents.listChildren(parent.id, exec.signal)
108
- } catch (error) {
109
- return 'listChildren failed: ' + (error && error.message ? error.message : String(error))
110
- }
111
- const known = new Set(entries.map((entry) => entry.id))
112
- const unknown = ids.filter((id) => !known.has(id))
113
- if (unknown.length > 0) {
114
- return `unknown subagent ${unknown.map((id) => `"${id}"`).join(', ')}: not a direct child of this agent (list_agents shows your children; only depth-1 children can be awaited).`
115
- }
116
-
117
- const statusOf = (id) => {
118
- const agent = ctx.agents.get(id)
119
- return agent === undefined ? 'ready' : agent.status === 'running' ? 'running' : 'idle'
120
- }
121
-
122
- /** Settlement line: short done line; content belongs to the framework notice. */
123
- const doneLine = (id, info) => `subagent ${id} done (${info && info.stopReason ? info.stopReason : 'completed'}); its closing message follows as the settlement notice.`
124
-
125
- const startedAt = Date.now()
126
- const capMs = typeof args.timeout_ms === 'number' && args.timeout_ms > 0 ? args.timeout_ms : undefined
127
-
128
- /**
129
- * Wait for one child. Resolves with {kind:'settled',info} |
130
- * {kind:'timedOut'} | {kind:'aborted'} | {kind:'notStarted'}. The
131
- * per-id machinery (settled fast path, waiter registration, timeout
132
- * timer, startup-grace poller) is the single-id logic of old,
133
- * unchanged; Promise.all over ids gives the concurrent batch.
134
- */
135
- const waitOne = (id) => {
136
- // Fast path: the child is not running and a settlement is already on
137
- // record — resolve immediately (the notice was already delivered when
138
- // the child settled, so it is in the conversation history).
139
- if (statusOf(id) !== 'running') {
140
- const hit = settled.get(id)
141
- if (hit !== undefined) return Promise.resolve({ kind: 'settled', info: hit.info })
142
- }
143
-
144
- return new Promise((resolve) => {
145
- let done = false
146
- let timer
147
- let poller
148
- const finish = (value) => {
149
- if (done) return
150
- done = true
151
- if (poller !== undefined) clearInterval(poller)
152
- const list = waiters.get(id)
153
- if (list !== undefined) {
154
- const index = list.indexOf(finish)
155
- if (index >= 0) list.splice(index, 1)
156
- if (list.length === 0) waiters.delete(id)
157
- }
158
- if (timer !== undefined) clearTimeout(timer)
159
- if (exec.signal !== undefined) exec.signal.removeEventListener('abort', onAbort)
160
- resolve(value)
161
- }
162
- const onAbort = () => finish({ kind: 'aborted' })
163
-
164
- const list = waiters.get(id) ?? []
165
- list.push(finish)
166
- waiters.set(id, list)
167
- // All ids share startedAt, so one capMs value is one shared window.
168
- if (capMs !== undefined) timer = setTimeout(() => finish({ kind: 'timedOut' }), capMs)
169
- if (exec.signal !== undefined) {
170
- if (exec.signal.aborted) {
171
- finish({ kind: 'aborted' })
172
- return
173
- }
174
- exec.signal.addEventListener('abort', onAbort, { once: true })
175
- }
176
-
177
- // Startup grace: a freshly dispatched child sits between spawn and
178
- // its first turn (status not `running`, no settlement yet). Keep the
179
- // wait alive while it spins up; once `running`, only `subagent/end`
180
- // (the listener above) resolves us. If the grace expires without the
181
- // child ever starting or settling, fall back to a retryable note
182
- // instead of the old misleading "is not running" early return.
183
- poller = setInterval(() => {
184
- if (done) return
185
- const hit = settled.get(id)
186
- if (hit !== undefined) {
187
- finish({ kind: 'settled', info: hit.info })
188
- return
189
- }
190
- if (statusOf(id) !== 'running' && Date.now() - startedAt >= START_GRACE_MS) {
191
- finish({ kind: 'notStarted' })
192
- }
193
- }, POLL_MS)
194
-
195
- // Re-check: the child may have settled between the fast path above
196
- // and waiter registration. Only a settlement AFTER this wait started
197
- // counts as a live result.
198
- const hit = settled.get(id)
199
- if (hit !== undefined && hit.time >= startedAt) finish({ kind: 'settled', info: hit.info })
200
- })
201
- }
202
-
203
- const outcomes = await Promise.all(ids.map((id) => waitOne(id)))
204
-
205
- // Every waiter shares one abort signal, so an interruption either
206
- // aborts the whole batch or lands after some children already settled.
207
- // Collapse the all-aborted case into one line; mixed cases keep
208
- // per-id lines so the settled ones are not lost.
209
- if (outcomes.length > 0 && outcomes.every((outcome) => outcome.kind === 'aborted')) {
210
- return `wait for ${ids.length} subagent${ids.length === 1 ? '' : 's'} was aborted by user interruption; the children may still be running.`
211
- }
212
-
213
- const lines = outcomes.map((outcome, index) => {
214
- const id = ids[index]
215
- if (outcome.kind === 'aborted') return `wait for subagent ${id} was aborted by user interruption; the child may still be running.`
216
- if (outcome.kind === 'timedOut') return `timed out waiting for subagent ${id}; it is still running. You will be notified when it finishes — you can wait_subagent it again or continue with other work.`
217
- if (outcome.kind === 'notStarted') {
218
- return `subagent ${id} has not started after ${Math.round(START_GRACE_MS / 1000)}s (status: ${statusOf(id)}). It may still be spinning up — call wait_subagent again, or continue with other work; you will be notified when it finishes.`
219
- }
220
- // Real settlement: short done line; the framework's settlement notice
221
- // (already delivered to the parent inbox before `subagent/end`)
222
- // carries the closing message — nothing to consume or deduplicate.
223
- return doneLine(id, outcome.info)
224
- })
225
- return lines.join('\n')
226
- },
227
- })
228
- }
@@ -1,7 +0,0 @@
1
- {
2
- "name": "dsh-preset-eng-wait-subagent",
3
- "private": true,
4
- "type": "module",
5
- "main": "index.js",
6
- "version": "0.1.0"
7
- }
@@ -1,341 +0,0 @@
1
- # dsh-apollo bundle patch: the `eng-noask` (工程模式(无提问)) agent preset —
2
- # a copy of the `eng` plugins block with exactly one capability removed: the
3
- # `tool-ask-user` row is gone (sessions expose no ask_user_question tool),
4
- # plus a no-ask discipline line in the preset-local mode-instructions section
5
- # and the plan-mode section's two ask_user_question mentions rewritten for a
6
- # preset that cannot ask. Everything else matches `eng`;
7
- # tests/upstream-drift.test.mjs fails when this block drifts from `eng`
8
- # beyond the removed row. Keep the two in sync by editing `eng` first, then
9
- # redoing the copy.
10
- #
11
- # Declared as one `@deepseek-ai/dsh-agent-preset` insert row (the DSH 0.2.1
12
- # preset mechanism; the `<dshHome>/.agent-presets` scan root is gone).
13
- # `config.id` MUST stay `eng-noask`: it is the preset identity historical
14
- # sessions resume by. Preset-local plugin modules are referenced by bare
15
- # package subpaths (`@menghuan1918/dsh-apollo/presets/eng-noask/plugins/...`) resolved
16
- # from the profile directory's node_modules, not the preset directory.
17
-
18
- - insert:
19
- - id: preset-eng-noask
20
- name: '@deepseek-ai/dsh-agent-preset'
21
- config:
22
- id: eng-noask
23
- name: 工程模式(无提问)
24
- description: >-
25
- 工程模式的无人值守变体:剔除 ask_user_question 工具,配无提问纪律——全程自主决策不提问,关键假设与替代方案写进最终报告。
26
- order: 11
27
- plugins:
28
- #
29
- # This plugins block is an AGENT-PLANE composition. The roster mounts it ONCE under a
30
- # standing scope; every session naming it joins by scope parentage, so the
31
- # tools and prompt sections registered here cover each joined agent while a
32
- # session's own state stays keyed per Session/Agent inside the plugins. The
33
- # host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
34
- # preset must not own: the registries themselves, the sandbox and approval
35
- # stack, persistence, and the model route.
36
- #
37
- # A service row here MUST sit inside a group carrying an `isolate` realm.
38
- # Without one it publishes into the root realm, where it is process-global —
39
- # another preset publishing the same name collides, and a host reader would
40
- # resolve one preset's instance for every session; `dsh-agent-preset` rejects
41
- # that at mount. `true` means an entry-local realm: this standing mount's own
42
- # private instance, apart from every other preset's. (A shared label does NOT
43
- # pool instances — `provide()` throws on the second registration under the
44
- # same realm symbol; labels join REALMS, and are not what this file needs.)
45
-
46
- # ── identity ────────────────────────────────────────────────────────────────
47
-
48
- # The preset's own persona, shadowing the deployment default for this agent.
49
- # One row on purpose: the rich engineering rules live in the user-global
50
- # instructions (~/.dsh/AGENTS.md) and the preset's `eng:mode-instructions`
51
- # section, not in the persona. `eng:mode-instructions` owns the delegation
52
- # policy; the tiers stay global because they track local route config.
53
- # 0.2.1's upstream `standard` preset carries `prefix` only: the working-
54
- # directory `suffix` line it used to set is gone, and eng follows suit.
55
- - id: persona
56
- name: '@deepseek-ai/dsh-persona'
57
- config:
58
- prefix: >-
59
- You are a coding agent powered by the {{model}} model.
60
-
61
- # Workspace instructions (repository AGENTS.md files) are injected from the
62
- # first request, capped at 64 KiB.
63
- - id: agent-instructions
64
- name: '@deepseek-ai/dsh-agent-instructions'
65
- config:
66
- maxBytes: 65536
67
-
68
- # 0.2.1 standard adds the per-step time context (sampled current time,
69
- # browser time zone and elapsed time) so date/time reasoning does not depend
70
- # on the model's own clock. It consumes host services and provides nothing,
71
- # so it needs no realm.
72
- - id: time-context
73
- name: '@deepseek-ai/dsh-time-context'
74
-
75
- # ── shell ───────────────────────────────────────────────────────────────────
76
-
77
- # `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
78
- # publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
79
- # the criterion for host-plane ownership — injection resolves before any session
80
- # exists, so there is no agent to key by. Behind a preset realm those variables
81
- # never reached the model's shell at all. Both shell tools consume the host
82
- # registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
83
- # host-plane too.
84
- - id: tool-bash
85
- name: '@deepseek-ai/dsh-tool-bash'
86
- disabled: !!js process.platform === 'win32'
87
-
88
- - id: tool-pwsh
89
- name: '@deepseek-ai/dsh-tool-pwsh'
90
- disabled: !!js process.platform !== 'win32'
91
-
92
- # ── filesystem ──────────────────────────────────────────────────────────────
93
-
94
- # Both register into the host `tools` registry and provide nothing, so
95
- # they need no realm. The `fs` service and its policy stay in the host.
96
- - id: tool-fs
97
- name: '@deepseek-ai/dsh-tool-fs'
98
-
99
- - id: tool-fs-search
100
- name: '@deepseek-ai/dsh-tool-fs-search'
101
- config:
102
- sampleOverCapGlobResults: false
103
-
104
- # ── background jobs ────────────────────────────────────────────────────────
105
-
106
- # Only the model-facing controls. The task REGISTRY stays on the host plane:
107
- # its producers sit outside any realm this file could put it in — `tool-bash`
108
- # above resolves it with `ctx.get`, and an entry-local realm here is invisible
109
- # to every sibling row, so `run_in_background` would answer "background jobs
110
- # unavailable" while these controls sat in the catalog. The registry is keyed by
111
- # owning agent anyway, so one host instance serves every session. What a preset
112
- # chooses is whether its agent can collect and stop background work at all.
113
- #
114
- # LOCAL OVERRIDE (2026-09-22): `maxConsecutiveWakes` is 5000 instead of the
115
- # bundled 3. The budget is spent by every completion that opens a turn on an
116
- # idle owner and is restored ONLY by a user-authored message, so a long
117
- # unattended chain of background jobs parks after its third wake — from the
118
- # fourth completion on, the notice degrades to injection and waits for the
119
- # user's next input. This row and its config are copied verbatim from the
120
- # eng patch; keep the two in sync (the upstream-drift test compares
121
- # same-id row configs).
122
- - id: tool-jobs
123
- name: '@deepseek-ai/dsh-tool-jobs'
124
- config:
125
- maxConsecutiveWakes: 5000
126
-
127
- # The automation-task tools (schedule_create/list/update/delete) are
128
- # deliberately omitted from engineering mode: coding/infrastructure work
129
- # does not need scheduled reminders. The host `schedule` service and the
130
- # sidebar Automation page stay available to other presets.
131
-
132
- # ── skills ──────────────────────────────────────────────────────────────────
133
-
134
- # The skill REGISTRY lives in the host composition and is layered per scope:
135
- # these rows register into THIS preset's layer of it, so they need no realm.
136
- # `skill-filesystem` contributes local-root discovery for agents on this preset, and
137
- # `tool-skill` gives them the catalog and loader; the merged catalog also
138
- # carries whatever the deployment registered globally (repository plugins).
139
- - id: skill-filesystem
140
- name: '@deepseek-ai/dsh-skill-filesystem'
141
-
142
- - id: tool-skill
143
- name: '@deepseek-ai/dsh-tool-skill'
144
-
145
- # ── goals ───────────────────────────────────────────────────────────────────
146
-
147
- # Native command-goal/tool-goal rows are deliberately omitted. The goal
148
- # domain is owned by the independent dsh-better-goal plugin row in the
149
- # top-level patch: it registers one `goal` tool and one `/goal` command
150
- # per Agent, hides any legacy goal tools a preset still carries, and has
151
- # its own Plugins row for round limits and question timeouts. The two
152
- # removed rows remain deliberate removals in tests/upstream-drift.test.mjs,
153
- # which asserts they still exist upstream before filtering them out.
154
-
155
- # ── plan mode ───────────────────────────────────────────────────────────────
156
-
157
- # Plan state is per-agent by nature, so an entry-local realm is not a
158
- # workaround here — it is the correct lifetime.
159
- - id: planning
160
- name: cordis:group
161
- group: true
162
- isolate:
163
- planMode: true
164
- config:
165
- - id: plan-mode
166
- name: '@deepseek-ai/dsh-plan-mode'
167
- config:
168
- section: |
169
- You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or 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.
170
-
171
- 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.
172
-
173
- 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.
174
-
175
- Resolve discoverable facts by inspection. This preset exposes no ask-user tool: never wait on the user for a choice — decide autonomously, and record the decision, its key assumptions, and the alternatives you rejected in the plan so review can catch them. Do not ask the user where code lives or how current behavior works when you can find out.
176
-
177
- 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.
178
-
179
- 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. 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.
180
-
181
- # ── compaction ──────────────────────────────────────────────────────────────
182
-
183
- # `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
184
- # share this realm rather than sit outside it.
185
- #
186
- # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
187
- # plane, and the rows here resolve that one instance. It takes no configuration,
188
- # keys every fold by Session, and owns the context-meter projection units the
189
- # browser reads for every session — behind a realm those units would come and go
190
- # with whichever presets happen to be mounted. What a preset chooses is whether
191
- # its agent compacts at all, which is `compaction-basic` below.
192
- - id: compaction
193
- name: cordis:group
194
- group: true
195
- isolate:
196
- compaction: true
197
- toolResultPruner: true
198
- config:
199
- - id: compaction-basic
200
- name: '@deepseek-ai/dsh-compaction-basic'
201
-
202
- - id: command-compact
203
- name: '@deepseek-ai/dsh-command-compact'
204
-
205
- - id: tool-result-pruner
206
- name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
207
- config:
208
- # Deliberately 2x the upstream `standard` preset (8192/4096/1024): eng
209
- # sessions emit large build/test/log tool results, and the extra
210
- # head/tail retention preserves the failure context the agent acts on.
211
- thresholdChars: 16384
212
- headChars: 8192
213
- tailChars: 2048
214
-
215
- # ── delegation and workflows ────────────────────────────────────────────────
216
-
217
- # The `subagents` registry and its spawn/fork backends live in the HOST
218
- # composition: the registry is a process singleton whose cross-session queries
219
- # the api-proxy serves to the browser, and a provider name may only be
220
- # registered once. This preset contributes the delegation TOOLS, which resolve
221
- # that host registry.
222
- #
223
- # `workflows` is different — nothing outside an agent reads it — so every row
224
- # that reaches it shares one entry-local realm here, and a consumer left
225
- # outside would resolve a host registry this preset does not populate.
226
- #
227
- # 0.1.2-alpha.4 note: the one-way child `report` tool is gone — parent/child
228
- # messaging now rides the globally named `send_message` registered by
229
- # `tool-subagent-control` below (children inherit it too), so this preset
230
- # mounts no messaging row of its own.
231
- - id: delegation
232
- name: cordis:group
233
- group: true
234
- isolate:
235
- workflowEngine: true
236
- config:
237
- - id: tool-subagent-control
238
- name: '@deepseek-ai/dsh-tool-subagent-control'
239
-
240
- - id: tool-subagent-list-agents
241
- name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
242
-
243
- - id: tool-subagent
244
- name: '@deepseek-ai/dsh-tool-subagent'
245
- config:
246
- provider: spawn
247
- toolName: subagent
248
- modelSelectionSettings: true
249
-
250
- # Fork omits model selection so provider/model stay equal to the parent and
251
- # the inherited history remains eligible for KV Cache reuse.
252
- - id: tool-subagent-fork
253
- name: '@deepseek-ai/dsh-tool-subagent'
254
- config:
255
- provider: fork
256
- toolName: subagent_fork
257
-
258
- # 0.1.6-alpha.2 renamed `dsh-workflow-worker-thread` to `dsh-workflow-ptc`
259
- # (workflow orchestration in the shared sandboxed Node PTC runtime).
260
- - id: workflow-ptc
261
- name: '@deepseek-ai/dsh-workflow-ptc'
262
- config:
263
- provider: spawn
264
-
265
- - id: tool-workflow
266
- name: '@deepseek-ai/dsh-tool-workflow'
267
-
268
- # ── remaining model-facing rows ─────────────────────────────────────────────
269
-
270
- # No `tool-ask-user` here: this preset runs unattended. The no-ask discipline
271
- # lives in the preset-local mode-instructions section.
272
-
273
- - id: tool-todo
274
- name: '@deepseek-ai/dsh-tool-todo'
275
- config:
276
- allowParallelInProgress: true
277
-
278
- # The `web` service and its search/fetch providers stay in the host
279
- # composition; only the model-facing tool is per-session. `fetch` reads the
280
- # host-mounted SSRF-guarded `web-fetch-http` provider.
281
- - id: tool-web
282
- name: '@deepseek-ai/dsh-tool-web'
283
- config:
284
- fetch: true
285
- searchTimeoutMs: 60000
286
-
287
- # Declares deliverable files to the user (file-card surface). Upstream
288
- # `standard` mounts it; eng is a fork-copy of that block, so this row has to be
289
- # carried by hand — `tests/upstream-drift.test.mjs` fails when a shipped
290
- # preset gains rows that eng never picked up.
291
- - id: present
292
- name: '@deepseek-ai/dsh-tool-present'
293
-
294
- # 0.2.1 ships both plugin surfaces disabled by default; enable either only in a
295
- # copy of this plugins block (edits saved from the Web editor override
296
- # config.plugins by row id from the profile patch).
297
- - id: tool-plugin-manager
298
- name: '@deepseek-ai/dsh-plugin-manager/tools'
299
- disabled: true
300
-
301
- - id: tool-plugin-market
302
- name: '@deepseek-ai/dsh-plugin-market/tools'
303
- disabled: true
304
-
305
- # ── subagent waiting ─────────────────────────────────────────────────────────
306
-
307
- # Preset-local plugin: explicit wait for one continuable child to settle, so
308
- # the agent can block on a specific subagent (pi-style await_task semantics)
309
- # without giving up send_message continuations. The module specifier is a
310
- # bare subpath of this package (@menghuan1918/dsh-apollo/presets/eng-noask/plugins/...):
311
- # config.plugins names resolve from the profile directory's node_modules,
312
- # where this package is installed or linked; it has zero bare imports on
313
- # purpose.
314
- - id: tool-wait-subagent
315
- name: '@menghuan1918/dsh-apollo/presets/eng-noask/plugins/wait-subagent/index.js'
316
-
317
- # ── exit guard ───────────────────────────────────────────────────────────────
318
- #
319
- # Preset-local plugin: pi-atlas 式退出守卫。回合结束(agent/turn-stopping)
320
- # 时若有运行中的直接子代理或后台任务,注入 next-step 提醒(agent.steer)
321
- # 强制回合继续,直到 wait_subagent / job_output / job_kill 处理完毕或用户
322
- # 中断(被中断回合不触发)。开关为正式 Config.exitGuard,默认关闭,
323
- # 每次事件读取当前值,设置保存后即时生效。监听器注册型
324
- # 插件,无服务发布,无需 realm。
325
- - id: eng-exit-guard
326
- name: '@menghuan1918/dsh-apollo/presets/eng-noask/plugins/exit-guard/index.js'
327
-
328
- # ── mode instructions ────────────────────────────────────────────────────────
329
-
330
- # Preset-local prompt section carrying the two eng-only rules: the exit guard
331
- # contract and the delegation/orchestration policy (the latter moved out of the
332
- # user-global ~/.dsh/AGENTS.md on 2026-09-11 — the model tiers stay there
333
- # because they mirror this host's `subagent-model-selection` route list).
334
- # Registered as a system-prompt section (order 1, right after the persona slot)
335
- # so it is scoped to sessions mounted on this preset and nothing else; injected
336
- # from the first request alongside the other sections.
337
- # This plugins block (presets/eng-noask.patch.yml) is the single source of truth;
338
- # the 0.2.1 bundle-patch mechanism loads it straight from this file, with no
339
- # boot-time copy in between.
340
- - id: eng-mode-instructions
341
- name: '@menghuan1918/dsh-apollo/presets/eng-noask/plugins/mode-instructions/index.js'