dsh-gitbash-shell 0.31.0 → 0.32.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/CHANGELOG.md +92 -0
- package/dsh.plugin.json +3 -3
- package/package.json +3 -4
- package/src/index.js +32 -585
- package/src/schemastery.js +178 -0
- package/src/shell.js +29 -10
- package/assets/code-gitbash/agent.cordis.ptc.ps.yml +0 -282
- package/assets/code-gitbash/agent.cordis.ptc.yml +0 -276
- package/assets/code-gitbash/agent.cordis.yml +0 -268
- package/assets/code-gitbash/preset.yml +0 -3
- package/assets/cordis-gitbash/agent.cordis.ptc.ps.yml +0 -283
- package/assets/cordis-gitbash/agent.cordis.ptc.yml +0 -267
- package/assets/cordis-gitbash/agent.cordis.yml +0 -267
- package/assets/cordis-gitbash/preset.yml +0 -3
- package/assets/minimal-gitbash/agent.cordis.ps.yml +0 -79
- package/assets/minimal-gitbash/agent.cordis.yml +0 -74
- package/assets/minimal-gitbash/preset.yml +0 -3
- package/assets/standard-gitbash/agent.cordis.ptc.ps.yml +0 -262
- package/assets/standard-gitbash/agent.cordis.ptc.yml +0 -256
- package/assets/standard-gitbash/agent.cordis.yml +0 -256
- package/assets/standard-gitbash/preset.yml +0 -3
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-gitbash-shell/schemastery — resolve the ONE schemastery copy whose
|
|
3
|
+
* semantics match the executing host, instead of whichever copy Node finds
|
|
4
|
+
* first.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS MODULE EXISTS (issue #12). `@deepseek-ai/schemastery` is a peer, so
|
|
7
|
+
* it is resolved by walking UP from this plugin's own file. In a hoisted
|
|
8
|
+
* profile that walk can land on an OLDER copy another dependency hoisted to the
|
|
9
|
+
* profile root — measured on the reporter's machine: 3.18.2, which has no
|
|
10
|
+
* `volatile()` — while the host itself runs the copy installed beside its own
|
|
11
|
+
* shell packages (3.18.4). The executor's base class comes from the HOST's copy
|
|
12
|
+
* of `@deepseek-ai/dsh-bash-sandbox`: on dsh 0.1.7+ it declares its limits with
|
|
13
|
+
* `.volatile()` and reads every one of them through `.get()`
|
|
14
|
+
* (`packages/shell/bash-local/src/index.ts:100-107`, `assertServiceableBashConfig`).
|
|
15
|
+
* A subclass whose Config was built from a `volatile`-less schemastery
|
|
16
|
+
* therefore yields PLAIN values and the first shell call dies with
|
|
17
|
+
* `TypeError: config.timeoutMs.get is not a function`, taking the whole
|
|
18
|
+
* `ctx.shell` down with it. Issue #6 was the same crash from the other
|
|
19
|
+
* direction (an unconditional plain Config); v0.24.4 fixed that with a
|
|
20
|
+
* per-field `schema.volatile` probe, which only helps when the copy we happen
|
|
21
|
+
* to resolve HAS `volatile()`.
|
|
22
|
+
*
|
|
23
|
+
* The choice is therefore driven by the HOST'S BEHAVIOUR, never by our own
|
|
24
|
+
* resolution order. Asking the base class what it resolves `timeoutMs` to is a
|
|
25
|
+
* direct read of "does this host call `.get()`?" — true on 0.1.7+, false on
|
|
26
|
+
* <= 0.1.6 where plain values are the correct answer. Only when the host wants
|
|
27
|
+
* Volatile refs do we insist on a copy that provides them, and only then do we
|
|
28
|
+
* try the parent package's own resolution domain first. An unreadable parent
|
|
29
|
+
* Config keeps the historical behaviour (our own copy, per-field probe), so an
|
|
30
|
+
* unknown host is never made worse.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { createRequire } from 'node:module'
|
|
34
|
+
import { pathToFileURL } from 'node:url'
|
|
35
|
+
|
|
36
|
+
/** The peer whose copy must agree with the host. */
|
|
37
|
+
export const SCHEMASTERY_PEER = '@deepseek-ai/schemastery'
|
|
38
|
+
|
|
39
|
+
/** The package whose resolution domain is authoritative — the executor we extend. */
|
|
40
|
+
export const SCHEMASTERY_PARENT = '@deepseek-ai/dsh-bash-sandbox'
|
|
41
|
+
|
|
42
|
+
const TAG = '[gitbash-shell]'
|
|
43
|
+
|
|
44
|
+
/** Node's own answer for `specifier` as seen from `base`, or `''` when it fails. */
|
|
45
|
+
export function resolveFrom(base, specifier) {
|
|
46
|
+
try {
|
|
47
|
+
return createRequire(base).resolve(specifier)
|
|
48
|
+
} catch {
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether one loaded module looks like a usable schemastery. */
|
|
54
|
+
export function isSchemastery(z) {
|
|
55
|
+
return !!z && typeof z.object === 'function'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Whether one loaded schemastery can produce Volatile refs. */
|
|
59
|
+
export function hasVolatile(z) {
|
|
60
|
+
if (!isSchemastery(z)) return false
|
|
61
|
+
try {
|
|
62
|
+
return typeof z.string().volatile === 'function'
|
|
63
|
+
} catch {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Ask the executing host whether it reads its shell limits through `.get()`.
|
|
70
|
+
*
|
|
71
|
+
* `SandboxBashExecutor.Config({})` is the host's OWN schema, built from the
|
|
72
|
+
* host's own schemastery: it resolves `timeoutMs` to a `Volatile` ref exactly
|
|
73
|
+
* when the host declares volatile fields. Behaviour, not internals — no private
|
|
74
|
+
* schema shape is read.
|
|
75
|
+
*
|
|
76
|
+
* @param ParentConfig - the base class's `static Config`, if it has one.
|
|
77
|
+
* @returns `true`/`false` when the host answered, `undefined` when it could not.
|
|
78
|
+
*/
|
|
79
|
+
export function hostWantsVolatileRefs(ParentConfig) {
|
|
80
|
+
if (typeof ParentConfig !== 'function') return undefined
|
|
81
|
+
try {
|
|
82
|
+
const resolved = ParentConfig({})
|
|
83
|
+
if (!resolved || typeof resolved !== 'object') return undefined
|
|
84
|
+
return typeof resolved.timeoutMs?.get === 'function'
|
|
85
|
+
} catch {
|
|
86
|
+
return undefined
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pick one already-loaded copy. Pure, so the matrix is testable without a
|
|
92
|
+
* filesystem: the caller supplies the candidates and their resolution order.
|
|
93
|
+
*
|
|
94
|
+
* @param options - the host's answer, our own entry path, and the loaded copies in priority order.
|
|
95
|
+
* @returns the chosen copy (or undefined) and why, for the log line.
|
|
96
|
+
*/
|
|
97
|
+
export function chooseCopy({ needsVolatile, ownEntry, copies }) {
|
|
98
|
+
const own = copies.find((copy) => copy.entry === ownEntry)
|
|
99
|
+
if (copies.length === 0) return { chosen: undefined, reason: 'unresolved' }
|
|
100
|
+
if (needsVolatile === false) {
|
|
101
|
+
// <= 0.1.6 reads plain values: the historical answer is the correct one.
|
|
102
|
+
return { chosen: own ?? copies[0], reason: 'host-plain' }
|
|
103
|
+
}
|
|
104
|
+
if (needsVolatile === true) {
|
|
105
|
+
// Our own copy is the historical answer: keep it whenever it can serve, and
|
|
106
|
+
// redirect ONLY when it cannot (that redirection is the whole point of #12).
|
|
107
|
+
if (own !== undefined && hasVolatile(own.z)) return { chosen: own, reason: 'host-volatile' }
|
|
108
|
+
const capable = copies.find((copy) => hasVolatile(copy.z))
|
|
109
|
+
return capable
|
|
110
|
+
? { chosen: capable, reason: 'host-volatile-redirected' }
|
|
111
|
+
: { chosen: own ?? copies[0], reason: 'no-volatile-anywhere' }
|
|
112
|
+
}
|
|
113
|
+
// The host could not answer: keep the pre-#12 behaviour (our own copy) rather
|
|
114
|
+
// than guessing a semantics change on its behalf.
|
|
115
|
+
return { chosen: own ?? copies[0], reason: 'host-unknown' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Load one module by absolute entry path. */
|
|
119
|
+
async function loadAt(entry) {
|
|
120
|
+
const mod = await import(pathToFileURL(entry).href)
|
|
121
|
+
return mod?.default ?? mod
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** One process-wide answer: the module graph does not change under us. */
|
|
125
|
+
let cached = null
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve, load, and cache the schemastery copy that matches the executing host.
|
|
129
|
+
*
|
|
130
|
+
* @param options - `parentConfig` is the executor base class's `static Config`.
|
|
131
|
+
* @returns the chosen module (or undefined), where it came from, the host's answer, and the reason.
|
|
132
|
+
*/
|
|
133
|
+
export async function loadSchemastery({ parentConfig } = {}) {
|
|
134
|
+
if (cached !== null) return cached
|
|
135
|
+
const needsVolatile = hostWantsVolatileRefs(parentConfig)
|
|
136
|
+
const ownEntry = resolveFrom(import.meta.url, SCHEMASTERY_PEER)
|
|
137
|
+
|
|
138
|
+
const wanted = []
|
|
139
|
+
const parentEntry = resolveFrom(import.meta.url, SCHEMASTERY_PARENT)
|
|
140
|
+
if (parentEntry !== '') {
|
|
141
|
+
const beside = resolveFrom(parentEntry, SCHEMASTERY_PEER)
|
|
142
|
+
if (beside !== '' && beside !== ownEntry) wanted.push({ where: 'the shell packages', entry: beside })
|
|
143
|
+
}
|
|
144
|
+
if (ownEntry !== '') wanted.push({ where: 'this plugin', entry: ownEntry })
|
|
145
|
+
|
|
146
|
+
const copies = []
|
|
147
|
+
for (const candidate of wanted) {
|
|
148
|
+
try {
|
|
149
|
+
const z = await loadAt(candidate.entry)
|
|
150
|
+
if (isSchemastery(z)) copies.push({ where: candidate.where, entry: candidate.entry, z })
|
|
151
|
+
} catch { /* try the next domain */ }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const { chosen, reason } = chooseCopy({ needsVolatile, ownEntry, copies })
|
|
155
|
+
const result = { z: chosen?.z, source: chosen?.where ?? '', entry: chosen?.entry ?? '', needsVolatile, reason }
|
|
156
|
+
cached = result
|
|
157
|
+
|
|
158
|
+
if (result.z === undefined) {
|
|
159
|
+
console.warn(`${TAG} ${SCHEMASTERY_PEER} is not resolvable from this plugin or from ${SCHEMASTERY_PARENT}; the row Config surface is absent`)
|
|
160
|
+
} else if (reason === 'no-volatile-anywhere') {
|
|
161
|
+
// The one case the plugin cannot paper over: the host calls `.get()` on
|
|
162
|
+
// plain values, so every shell call would fail. Say so where the user will
|
|
163
|
+
// see it, instead of leaving a mystery TypeError.
|
|
164
|
+
console.warn(
|
|
165
|
+
`${TAG} this host reads its shell limits through .get() but NO resolvable ${SCHEMASTERY_PEER} provides volatile()`
|
|
166
|
+
+ ` (tried: ${wanted.map((w) => `${w.where} -> ${w.entry}`).join('; ') || 'nothing resolved'}).`
|
|
167
|
+
+ ' Shell calls will fail with "config.timeoutMs.get is not a function"; install a dsh whose schemastery matches its own shell packages.',
|
|
168
|
+
)
|
|
169
|
+
} else if (chosen.where === 'the shell packages') {
|
|
170
|
+
console.log(`${TAG} ${SCHEMASTERY_PEER} taken from the shell packages instead of this plugin's own resolution (${reason}): ${chosen.entry}`)
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Test seam: forget the cached answer. */
|
|
176
|
+
export function resetSchemasteryCache() {
|
|
177
|
+
cached = null
|
|
178
|
+
}
|
package/src/shell.js
CHANGED
|
@@ -43,7 +43,7 @@ import { DEFAULT_GIT_BASH, bashResolutionReport, effectiveConfiguredBashPath, re
|
|
|
43
43
|
|
|
44
44
|
/** Re-exported for callers that historically read it here (the value now lives in bash-path.js). */
|
|
45
45
|
export { DEFAULT_GIT_BASH }
|
|
46
|
-
import
|
|
46
|
+
import { loadSchemastery } from './schemastery.js'
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
/** Log prefix, matching src/index.js. */
|
|
@@ -77,7 +77,10 @@ const SETTINGS_NAMESPACE = 'gitbash-shell'
|
|
|
77
77
|
* ones. Published schemastery 3.18.2 has no `volatile()`, which is exactly why
|
|
78
78
|
* the probe (not an unconditional call) is required.
|
|
79
79
|
*/
|
|
80
|
-
function live(schema) {
|
|
80
|
+
function live(schema, needsVolatile) {
|
|
81
|
+
// A <=0.1.6 host reads plain values: never hand it a ref it will not `.get()`
|
|
82
|
+
// (issue #12 — the host's own answer wins over what our copy happens to offer).
|
|
83
|
+
if (needsVolatile === false) return schema
|
|
81
84
|
return typeof schema.volatile === 'function' ? schema.volatile() : schema
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -86,16 +89,19 @@ function live(schema) {
|
|
|
86
89
|
* the smoke test can drive BOTH eras with a recording stand-in instead of
|
|
87
90
|
* depending on which schemastery happens to be installed here.
|
|
88
91
|
* @param z schemastery module (real one, or the test's recorder).
|
|
92
|
+
* @param needsVolatile - the host's OWN answer from src/schemastery.js: `true`
|
|
93
|
+
* forces Volatile refs, `false` forces plain values, `undefined` keeps the
|
|
94
|
+
* per-field probe (the historical behaviour, kept for an unreadable host).
|
|
89
95
|
* @returns the object schema for the `gitbash-executor` row.
|
|
90
96
|
*/
|
|
91
|
-
export function gitBashShellConfig(z) {
|
|
97
|
+
export function gitBashShellConfig(z, needsVolatile) {
|
|
92
98
|
return z.object({
|
|
93
|
-
cwd: live(z.string()),
|
|
94
|
-
timeoutMs: live(z.number().default(120000)),
|
|
95
|
-
maxTimeoutMs: live(z.number().default(600000)),
|
|
96
|
-
maxOutputBytes: live(z.number().default(64000)),
|
|
97
|
-
maxSpillBytes: live(z.number().default(64 * 1024 * 1024)),
|
|
98
|
-
graceMs: live(z.number().default(3000)),
|
|
99
|
+
cwd: live(z.string(), needsVolatile),
|
|
100
|
+
timeoutMs: live(z.number().default(120000), needsVolatile),
|
|
101
|
+
maxTimeoutMs: live(z.number().default(600000), needsVolatile),
|
|
102
|
+
maxOutputBytes: live(z.number().default(64000), needsVolatile),
|
|
103
|
+
maxSpillBytes: live(z.number().default(64 * 1024 * 1024), needsVolatile),
|
|
104
|
+
graceMs: live(z.number().default(3000), needsVolatile),
|
|
99
105
|
// Our own knob: the base class neither declares nor `.get()`s it, and
|
|
100
106
|
// `get bashPath()` below reads it as a plain value, so it stays plain.
|
|
101
107
|
//
|
|
@@ -107,8 +113,21 @@ export function gitBashShellConfig(z) {
|
|
|
107
113
|
})
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
/**
|
|
117
|
+
* The schemastery copy that matches the executing host (see src/schemastery.js).
|
|
118
|
+
* Resolving our own way can land on an OLDER copy another dependency hoisted to
|
|
119
|
+
* the profile root — one without `volatile()` — and the executor then declares
|
|
120
|
+
* plain limits for a base class that calls `.get()` on them: every shell call
|
|
121
|
+
* dies with `config.timeoutMs.get is not a function` (issue #12).
|
|
122
|
+
*
|
|
123
|
+
* Deliberately ONE line, and the ONLY top-level await in this module: the smoke
|
|
124
|
+
* harness evaluates this body inside `new Function`, where an await cannot
|
|
125
|
+
* appear, so it swaps exactly this expression for a stub.
|
|
126
|
+
*/
|
|
127
|
+
const schema = await loadSchemastery({ parentConfig: SandboxBashExecutor.Config })
|
|
128
|
+
|
|
110
129
|
/** Resolved configuration: the local executor's knobs, plus the Git Bash path. */
|
|
111
|
-
export const Config = gitBashShellConfig(z)
|
|
130
|
+
export const Config = schema.z ? gitBashShellConfig(schema.z, schema.needsVolatile) : undefined
|
|
112
131
|
|
|
113
132
|
/** Git Bash executor — mirrors the shipped bash/pwsh sandbox executors. */
|
|
114
133
|
export class GitBashSandboxExecutor extends SandboxBashExecutor {
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
# ERA (persona-split): dsh >= 0.1.3-alpha.2 text — the persona row uses the
|
|
2
|
-
# `prefix:`/`suffix:` keys (40792330c0 split dsh-persona single text key).
|
|
3
|
-
# The pre-split texts live in the sibling files named in the banner below; the
|
|
4
|
-
# plugin probes the roster AND the persona form at materialization and writes
|
|
5
|
-
# the picked text as the preset own agent.cordis.yml.
|
|
6
|
-
# ERA: dsh >= 0.1.2 text (built-in preset id `ptc`, presented with mode: ptc).
|
|
7
|
-
# The dsh <= 0.1.1 era text (built-in id `code`) lives in agent.cordis.yml; the
|
|
8
|
-
# plugin probes the roster at materialization and writes the picked text as the
|
|
9
|
-
# preset's own agent.cordis.yml.
|
|
10
|
-
# The `ptc` agent preset: the standard coding agent, presented as PTC mode.
|
|
11
|
-
#
|
|
12
|
-
# Most of `standard` is here unchanged. The deliberate exception is the
|
|
13
|
-
# general-purpose `workflow` tool: PTC mode uses `run_code` as its model-authored
|
|
14
|
-
# composition surface. The `tool-presentation` row turns the remaining registry
|
|
15
|
-
# into a generated SDK, so a sequence that would be five round trips becomes one.
|
|
16
|
-
#
|
|
17
|
-
# The registry itself stays on the host plane — the agent loop's scheduler and
|
|
18
|
-
# the API proxy's presenters are its consumers — so what this preset owns is
|
|
19
|
-
# the PRESENTATION of that registry for this agent alone. Native sessions run
|
|
20
|
-
# beside this one in the same process, each seeing its own catalog.
|
|
21
|
-
#
|
|
22
|
-
# This file is an AGENT-PLANE composition. It is mounted under one agent's
|
|
23
|
-
# scope context, so every tool and prompt section it registers belongs to that
|
|
24
|
-
# session alone. The host composition (`base.cordis.yml` + `web.cordis.yml`)
|
|
25
|
-
# keeps everything a preset must not own: the registries themselves, the
|
|
26
|
-
# sandbox and approval stack, persistence, and the model route.
|
|
27
|
-
#
|
|
28
|
-
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
|
29
|
-
# Without one it publishes into the root realm, where it is process-global
|
|
30
|
-
# rather than per-session and the second session mounting this preset collides
|
|
31
|
-
# with the first; `dsh-agent-presets` rejects that at mount. `true` means an
|
|
32
|
-
# entry-local realm — one private instance per mounted session, which is the
|
|
33
|
-
# default this deployment wants. A shared label would instead pool one instance
|
|
34
|
-
# across every session naming it.
|
|
35
|
-
|
|
36
|
-
# ── identity ────────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
# The preset's own persona, shadowing the deployment default for this agent.
|
|
39
|
-
# `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
|
|
40
|
-
- id: persona
|
|
41
|
-
name: '@deepseek-ai/dsh-persona'
|
|
42
|
-
config:
|
|
43
|
-
suffix: Your working directory is {{cwd}}.
|
|
44
|
-
prefix: >-
|
|
45
|
-
You are a coding agent powered by the {{model}} model.
|
|
46
|
-
|
|
47
|
-
- id: agent-instructions
|
|
48
|
-
name: '@deepseek-ai/dsh-agent-instructions'
|
|
49
|
-
config:
|
|
50
|
-
maxBytes: 65536
|
|
51
|
-
|
|
52
|
-
# ── shell ───────────────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
|
|
55
|
-
# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
|
|
56
|
-
# the criterion for host-plane ownership — injection resolves before any session
|
|
57
|
-
# exists, so there is no agent to key by. Behind a preset realm those variables
|
|
58
|
-
# never reached the model's shell at all. Both shell tools consume the host
|
|
59
|
-
# registry from here; the host executor is the Git Bash provider (`dsh-gitbash-shell`)
|
|
60
|
-
# host-plane too.
|
|
61
|
-
- id: tool-bash
|
|
62
|
-
name: '@deepseek-ai/dsh-tool-bash'
|
|
63
|
-
# Git Bash on Windows: the host executor is dsh-gitbash-shell (see $DSH_HOME/cordis.patch.yml).
|
|
64
|
-
disabled: false
|
|
65
|
-
|
|
66
|
-
- id: tool-pwsh
|
|
67
|
-
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
68
|
-
disabled: true
|
|
69
|
-
|
|
70
|
-
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
# Both register into the host `tools` registry and provide nothing, so
|
|
73
|
-
# they need no realm. The `fs` service and its policy stay in the host.
|
|
74
|
-
- id: tool-fs
|
|
75
|
-
name: '@deepseek-ai/dsh-tool-fs'
|
|
76
|
-
|
|
77
|
-
- id: tool-fs-search
|
|
78
|
-
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
79
|
-
config:
|
|
80
|
-
sampleOverCapGlobResults: false
|
|
81
|
-
|
|
82
|
-
# ── background jobs ────────────────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
# Only the model-facing controls. The task REGISTRY stays on the host plane:
|
|
85
|
-
# its producers sit outside any realm this file could put it in — `tool-bash`
|
|
86
|
-
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
|
|
87
|
-
# to every sibling row, so `run_in_background` would answer "background jobs
|
|
88
|
-
# unavailable" while these controls sat in the catalog. The registry is keyed by
|
|
89
|
-
# owning agent anyway, so one host instance serves every session. What a preset
|
|
90
|
-
# chooses is whether its agent can collect and stop background work at all.
|
|
91
|
-
- id: tool-jobs
|
|
92
|
-
name: '@deepseek-ai/dsh-tool-jobs'
|
|
93
|
-
|
|
94
|
-
# ── skills ──────────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
# The skill REGISTRY lives in the host composition and is layered per scope:
|
|
97
|
-
# these rows register into THIS preset's layer of it, so they need no realm.
|
|
98
|
-
# `skill-filesystem` contributes local-root discovery for agents on this preset, and
|
|
99
|
-
# `tool-skill` gives them the catalog and loader; the merged catalog also
|
|
100
|
-
# carries whatever the deployment registered globally (repository plugins).
|
|
101
|
-
- id: skill-filesystem
|
|
102
|
-
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
103
|
-
|
|
104
|
-
- id: tool-skill
|
|
105
|
-
name: '@deepseek-ai/dsh-tool-skill'
|
|
106
|
-
|
|
107
|
-
# ── goals ───────────────────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
# The goal service and session driver stay on the host plane, where the Gateway
|
|
110
|
-
# can resolve them. The human command and model-facing tool register into this
|
|
111
|
-
# preset's scoped layers.
|
|
112
|
-
- id: command-goal
|
|
113
|
-
name: '@deepseek-ai/dsh-command-goal'
|
|
114
|
-
|
|
115
|
-
- id: tool-goal
|
|
116
|
-
name: '@deepseek-ai/dsh-tool-goal'
|
|
117
|
-
|
|
118
|
-
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
119
|
-
|
|
120
|
-
# Plan state is per-agent by nature, so an entry-local realm is not a
|
|
121
|
-
# workaround here — it is the correct lifetime.
|
|
122
|
-
- id: planning
|
|
123
|
-
name: cordis:group
|
|
124
|
-
group: true
|
|
125
|
-
isolate:
|
|
126
|
-
planMode: true
|
|
127
|
-
config:
|
|
128
|
-
- id: plan-mode
|
|
129
|
-
name: '@deepseek-ai/dsh-plan-mode'
|
|
130
|
-
config:
|
|
131
|
-
section: |
|
|
132
|
-
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
133
|
-
|
|
134
|
-
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.
|
|
135
|
-
|
|
136
|
-
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.
|
|
137
|
-
|
|
138
|
-
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
139
|
-
|
|
140
|
-
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.
|
|
141
|
-
|
|
142
|
-
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
143
|
-
|
|
144
|
-
# ── compaction ──────────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
|
|
147
|
-
# share this realm rather than sit outside it.
|
|
148
|
-
#
|
|
149
|
-
# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
|
|
150
|
-
# plane, and the rows here resolve that one instance. It takes no configuration,
|
|
151
|
-
# keys every fold by Session, and owns the context-meter projection units the
|
|
152
|
-
# browser reads for every session — behind a realm those units would come and go
|
|
153
|
-
# with whichever presets happen to be mounted. What a preset chooses is whether
|
|
154
|
-
# its agent compacts at all, which is `compaction-basic` below.
|
|
155
|
-
- id: compaction
|
|
156
|
-
name: cordis:group
|
|
157
|
-
group: true
|
|
158
|
-
isolate:
|
|
159
|
-
compaction: true
|
|
160
|
-
toolResultPruner: true
|
|
161
|
-
config:
|
|
162
|
-
- id: compaction-basic
|
|
163
|
-
name: '@deepseek-ai/dsh-compaction-basic'
|
|
164
|
-
|
|
165
|
-
- id: command-compact
|
|
166
|
-
name: '@deepseek-ai/dsh-command-compact'
|
|
167
|
-
|
|
168
|
-
- id: tool-result-pruner
|
|
169
|
-
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
170
|
-
config:
|
|
171
|
-
thresholdChars: 8192
|
|
172
|
-
headChars: 4096
|
|
173
|
-
tailChars: 1024
|
|
174
|
-
|
|
175
|
-
# ── delegation and workflows ────────────────────────────────────────────────
|
|
176
|
-
|
|
177
|
-
# The `subagents` registry and its spawn/fork backends live in the HOST
|
|
178
|
-
# composition: the registry is a process singleton whose cross-session queries
|
|
179
|
-
# the api-proxy serves to the browser, and a provider name may only be
|
|
180
|
-
# registered once. This preset contributes the delegation TOOLS, which resolve
|
|
181
|
-
# that host registry.
|
|
182
|
-
#
|
|
183
|
-
# `workflows` is different — nothing outside an agent reads it — so every row
|
|
184
|
-
# that reaches it shares one entry-local realm here, and a consumer left
|
|
185
|
-
# outside would resolve a host registry this preset does not populate.
|
|
186
|
-
- id: delegation
|
|
187
|
-
name: cordis:group
|
|
188
|
-
group: true
|
|
189
|
-
isolate:
|
|
190
|
-
workflowEngine: true
|
|
191
|
-
config:
|
|
192
|
-
- id: tool-subagent-control
|
|
193
|
-
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
194
|
-
|
|
195
|
-
- id: tool-subagent-list-agents
|
|
196
|
-
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
197
|
-
|
|
198
|
-
- id: tool-subagent
|
|
199
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
200
|
-
config:
|
|
201
|
-
provider: spawn
|
|
202
|
-
toolName: subagent
|
|
203
|
-
modelSelectionSettings: true
|
|
204
|
-
backgroundMode: continuable
|
|
205
|
-
|
|
206
|
-
# Fork omits model selection so provider/model stay equal to the parent and
|
|
207
|
-
# the inherited history remains eligible for KV Cache reuse. This preset
|
|
208
|
-
# keeps fork continuable; parent and child inherit the same messaging tool,
|
|
209
|
-
# while the parent id and return guidance follow the inherited history.
|
|
210
|
-
- id: tool-subagent-fork
|
|
211
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
212
|
-
config:
|
|
213
|
-
provider: fork
|
|
214
|
-
toolName: subagent_fork
|
|
215
|
-
backgroundMode: continuable
|
|
216
|
-
|
|
217
|
-
# Production dsh does not install these optional providers. Install the
|
|
218
|
-
# matching Bundle in this Profile and restart the Host, then copy this
|
|
219
|
-
# preset and remove `disabled` from the matching tool row. Host availability
|
|
220
|
-
# alone grants no tool.
|
|
221
|
-
- id: tool-subagent-codex
|
|
222
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
223
|
-
disabled: true
|
|
224
|
-
config:
|
|
225
|
-
provider: codex
|
|
226
|
-
toolName: subagent_codex
|
|
227
|
-
backgroundMode: one-shot
|
|
228
|
-
maxDepth: provider-managed
|
|
229
|
-
|
|
230
|
-
- id: tool-subagent-claude-code
|
|
231
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
232
|
-
disabled: true
|
|
233
|
-
config:
|
|
234
|
-
provider: claude-code
|
|
235
|
-
toolName: subagent_claude_code
|
|
236
|
-
backgroundMode: one-shot
|
|
237
|
-
maxDepth: provider-managed
|
|
238
|
-
|
|
239
|
-
- id: workflow-worker-thread
|
|
240
|
-
name: '@deepseek-ai/dsh-workflow-worker-thread'
|
|
241
|
-
config:
|
|
242
|
-
provider: spawn
|
|
243
|
-
|
|
244
|
-
- id: tool-workflow
|
|
245
|
-
name: '@deepseek-ai/dsh-tool-workflow'
|
|
246
|
-
# Keep the engine above for `ralph`, but do not publish a second
|
|
247
|
-
# model-authored orchestration surface beside `run_code` in PTC mode.
|
|
248
|
-
disabled: true
|
|
249
|
-
|
|
250
|
-
- id: tool-ralph
|
|
251
|
-
name: '@deepseek-ai/dsh-tool-ralph'
|
|
252
|
-
config:
|
|
253
|
-
subagentProvider: spawn
|
|
254
|
-
maxRounds: 64
|
|
255
|
-
|
|
256
|
-
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
257
|
-
|
|
258
|
-
- id: tool-ask-user
|
|
259
|
-
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
260
|
-
|
|
261
|
-
- id: tool-todo
|
|
262
|
-
name: '@deepseek-ai/dsh-tool-todo'
|
|
263
|
-
config:
|
|
264
|
-
allowParallelInProgress: true
|
|
265
|
-
|
|
266
|
-
# The `web` service and its search provider stay in the host composition; only
|
|
267
|
-
# the model-facing tool is per-session.
|
|
268
|
-
- id: tool-web
|
|
269
|
-
name: '@deepseek-ai/dsh-tool-web'
|
|
270
|
-
config:
|
|
271
|
-
fetch: true
|
|
272
|
-
searchTimeoutMs: 60000
|
|
273
|
-
|
|
274
|
-
# ── presentation ────────────────────────────────────────────────────────────
|
|
275
|
-
|
|
276
|
-
# PTC mode for this agent alone. The row waits for the host's `codeRuntime`
|
|
277
|
-
# rather than assuming it: a deployment that composes no TypeScript runtime
|
|
278
|
-
# fails this preset at mount, naming this id, instead of at the first request.
|
|
279
|
-
- id: tool-presentation
|
|
280
|
-
name: '@deepseek-ai/dsh-agent-tool-presentation'
|
|
281
|
-
config:
|
|
282
|
-
mode: ptc
|