dsh-minimal-ptc 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stardustlc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,60 @@
1
+ # dsh-minimal-ptc
2
+
3
+ > Minimal prompt x full PTC capabilities — a cleaner coding agent.
4
+ > Installing gives you a new agent mode: **Minimal PTC**, with Git Bash kept alive on Windows so the bash tool matches the RL training distribution.
5
+
6
+ ## Highlights
7
+
8
+ - **RL-aligned**: the whole system prompt is one sentence — `You are a helpful software engineer assistant.` — close to the compact instruction distribution used during RL fine-tuning, without long-prompt format bias or context noise.
9
+ - **Complete toolset**: inherits the full PTC toolset — Code Mode SDK multi-step orchestration, files and search, Shell, Skills, plan mode, goals, subagents, and workflows.
10
+ - **We / Let's reasoning**: the Code Mode SDK packs thinking and doing into one TypeScript program, orchestrating multi-step operations before executing them once. This matches the high-scoring Minimal trajectory in DeepSeek's official Project2 V4.1b runs (99/96), where reasoning is dominated by `we` / `let's` instead of the standard-like `let me` / `I` blocks seen at 91/92.
11
+ - **Git Bash on Windows**: ships a preset-local Git Bash executor (GIT_BASH -> Program Files\Git -> LOCALAPPDATA\Git -> PATH). The bash tool is no longer disabled on Windows; pwsh remains available as a fallback.
12
+
13
+ ## Installation
14
+
15
+ 1. Add this package to a web profile (`package.json`):
16
+
17
+ ```json
18
+ "dependencies": { "dsh-minimal-ptc": "^0.4.0" },
19
+ "dsh": { "profile": { "bundles": [..., "dsh-minimal-ptc"] } }
20
+ ```
21
+
22
+ Local development can use a link: `"dsh-minimal-ptc": "link:E://deepseek//dsh-minimal-ptc"`.
23
+
24
+ 2. Run `pnpm install` in the profile directory.
25
+
26
+ 3. Restart the web profile process. The host row materializes the bundled preset into `$DSH_HOME/.agent-presets/ptc-minimal`.
27
+
28
+ 4. Select **Minimal PTC** when starting a new session.
29
+
30
+ ## Capabilities
31
+
32
+ - Web search is enabled via `tool-web`; arbitrary page fetch is disabled by default (`fetch: false`).
33
+ - Subagents are enabled: `subagent`, `subagent_fork`, `subagent_control`, workflows, and Ralph.
34
+ - `subagent_codex` and `subagent_claude_code` providers are disabled by default; remove their `disabled: true` lines to expose them.
35
+
36
+ ## Windows Git Bash configuration
37
+
38
+ The preset-local Git Bash executor can be overridden in `agent.cordis.yml` under `gitbash-executor`:
39
+
40
+ | Option | Default | Meaning |
41
+ | :-- | :-- | :-- |
42
+ | `shellPath` | auto-detected | Fixed Git Bash path (e.g. `C:\\Program Files\\Git\\bin\\bash.exe`) |
43
+ | `timeoutMs` | 120000 | Per-command timeout (ms) |
44
+ | `maxTimeoutMs` | 600000 | Maximum timeout a request may ask for |
45
+ | `maxOutputBytes` | 64000 | Per-call output cap |
46
+ | `maxSpillBytes` | 67108864 | Output spill-to-disk cap |
47
+ | `graceMs` | 3000 | Grace period after timeout |
48
+
49
+ Detection order: `GIT_BASH` -> `Program Files\\Git` -> `Program Files (x86)\\Git` -> `LOCALAPPDATA\\Programs\\Git` -> PATH.
50
+
51
+ ## Materialization policy
52
+
53
+ - No target directory -> write all preset files and a version marker.
54
+ - Marker version lower than the plugin version -> refresh from the bundled files.
55
+ - Directory exists without a marker (treated as user-created) -> leave it untouched.
56
+ - Upgrading: bump `version` in `package.json` and the `VERSION` constant in `lib/index.js`.
57
+
58
+ ## License
59
+
60
+ MIT
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # dsh-minimal-ptc
2
+
3
+ > 极简提示词 × PTC 全能力 —— 一个更"干净"的编码 Agent。
4
+ > 安装即获得新的 Agent 模式:**极简 PTC 模式**;Windows 自动启用 Git Bash,bash 工具与 RL 训练分布对齐。
5
+
6
+ ## 卖点
7
+
8
+ - **对齐 RL 训练**:系统提示词只有一句 `You are a helpful software engineer assistant.`,
9
+ 贴近 RL 微调时的简洁指令分布,没有长提示词带来的格式偏置与上下文噪音,
10
+ 让模型按训练时的节奏干活。
11
+ - **工具完整**:完整继承 PTC 模式的全部工具 —— Code Mode SDK 多步编排、
12
+ 文件读写与检索、Shell、Skills、计划、目标、子代理、工作流,一个不少。
13
+ - **We / Let's 思维链**:Code Mode SDK 把"想"和"做"装进一个 TypeScript 程序,
14
+ 多步操作先编排、再一次执行。这与 DeepSeek 官方跑分环境(Project2 V4.1b)中
15
+ Minimal 高分轨迹一致:99/96 的两跑以 `we` / `let's` 为主,而不是
16
+ Standard/PTC 常见的 `let me` / `I` 长块。
17
+ - **Windows 保持 bash**:预设内置 Git Bash executor(自动探测 GIT_BASH → Program Files\Git → LOCALAPPDATA\Git → PATH),`bash` 工具不再在 Windows 上被禁用;pwsh 仍保留为备用。
18
+
19
+ ## 安装
20
+
21
+ 1. 把本包加入 web profile(`package.json`):
22
+
23
+ ```json
24
+ "dependencies": { "dsh-minimal-ptc": "^0.4.0" },
25
+ "dsh": { "profile": { "bundles": [..., "dsh-minimal-ptc"] } }
26
+ ```
27
+
28
+ 本地开发也可以用 link:`"dsh-minimal-ptc": "link:E://deepseek//dsh-minimal-ptc"`。
29
+
30
+ 2. 在 profile 目录执行 `pnpm install`。
31
+
32
+ 3. 重启 web profile 进程:宿主行会把内置预设物化到
33
+ `$DSH_HOME/.agent-presets/ptc-minimal`。
34
+
35
+ 4. 新建会话时选择 **极简 PTC 模式**。
36
+
37
+ ## Windows Git Bash 配置
38
+
39
+ 预设内置的 Git Bash executor 可在 `agent.cordis.yml` 的 `gitbash-executor` 行覆盖:
40
+
41
+ | 配置 | 默认 | 说明 |
42
+ | :-- | :-- | :-- |
43
+ | `shellPath` | 自动探测 | 固定 Git Bash 路径(如 `C:\\Program Files\\Git\\bin\\bash.exe`) |
44
+ | `timeoutMs` | 120000 | 单次命令超时(毫秒) |
45
+ | `maxTimeoutMs` | 600000 | 请求可要求的最大超时 |
46
+ | `maxOutputBytes` | 64000 | 单次输出上限 |
47
+ | `maxSpillBytes` | 67108864 | 输出溢出落盘上限 |
48
+ | `graceMs` | 3000 | 超时后的宽限时间 |
49
+
50
+ 自动探测顺序:`GIT_BASH` → `Program Files\\Git` → `Program Files (x86)\\Git` → `LOCALAPPDATA\\Programs\\Git` → PATH。
51
+
52
+ ## 目录结构
53
+
54
+ ```
55
+ dsh-minimal-ptc/
56
+ ├── cordis.patch.yml # 插入宿主行(物化预设)
57
+ ├── lib/index.js # 宿主插件:物化 ptc-minimal 预设到用户预设根目录
58
+ └── presets/ptc-minimal/ # 内置 Agent 模式(极简提示词 + PTC 全能力)
59
+ ├── agent.cordis.yml
60
+ ├── gitbash-executor.mjs # Windows Git Bash 探测与执行器(零依赖)
61
+ └── preset.yml
62
+ ```
63
+
64
+ ## 说明
65
+
66
+ - 物化策略:目标目录不存在 → 写入全部文件并留版本标记;版本标记低于当前
67
+ 版本 → 刷新;目录存在但无标记(用户自建)→ 不覆盖。
68
+ - 升级插件:改 `version` 并同步 `lib/index.js` 里的 `VERSION` 常量即可刷新
69
+ 用户根目录里的预设文件。
70
+
71
+ ## 许可证
72
+
73
+ MIT
@@ -0,0 +1,8 @@
1
+ # dsh-minimal-ptc 组合包补丁:向 profile 插入本插件的宿主行。
2
+ #
3
+ # 宿主行(name: dsh-minimal-ptc)在启动时把内置的 ptc-minimal 预设
4
+ # 物化到用户预设根目录($DSH_HOME/.agent-presets/ptc-minimal),
5
+ # 安装即获得「极简 PTC 模式」Agent 模式。
6
+ - insert:
7
+ - id: ptc-minimal-preset
8
+ name: 'dsh-minimal-ptc'
package/lib/index.js ADDED
@@ -0,0 +1,66 @@
1
+ // dsh-minimal-ptc 宿主行:把内置的 ptc-minimal 预设物化到用户预设根目录。
2
+ //
3
+ // 预设发现是实时重读的,因此安装完成后重启一次 web profile 进程,
4
+ // 选择器里就会出现「极简 PTC 模式」。
5
+ //
6
+ // 物化策略:目标目录不存在 → 写入全部文件并留版本标记;
7
+ // 版本标记低于当前版本 → 用插件自带文件刷新;
8
+ // 目录存在但无标记(视为用户自建)→ 不覆盖。
9
+ //
10
+ // 零运行时依赖:harness home 的解析逻辑内置(DSH_HOME 环境变量优先,
11
+ // 否则 ~/.dsh),与 dsh 官方规则一致。
12
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs'
13
+ import { homedir } from 'node:os'
14
+ import { join, resolve } from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
16
+
17
+ export const name = 'ptc-minimal-preset'
18
+
19
+ const VERSION = '0.4.0'
20
+ const PRESET_ID = 'ptc-minimal'
21
+ const MARKER = '.dsh-ptc-minimal.version'
22
+
23
+ const packageDir = fileURLToPath(new URL('..', import.meta.url))
24
+ const presetSourceDir = join(packageDir, 'presets', PRESET_ID)
25
+
26
+ /** 与 @deepseek-ai/dsh-home-paths 相同的解析规则:DSH_HOME > ~/.dsh。 */
27
+ function dshHomePath(...segments) {
28
+ const env = process.env.DSH_HOME
29
+ const fromEnv = typeof env === 'string' && env.trim().length > 0 ? env : null
30
+ let base = fromEnv ?? join(homedir(), '.dsh')
31
+ if (base === '~') base = homedir()
32
+ if (base.startsWith('~/') || base.startsWith('~\\')) base = join(homedir(), base.slice(2))
33
+ return resolve(base, ...segments)
34
+ }
35
+
36
+ function materialize(logger) {
37
+ const dir = dshHomePath('.agent-presets', PRESET_ID)
38
+ mkdirSync(dir, { recursive: true })
39
+
40
+ let marker = null
41
+ try {
42
+ marker = readFileSync(join(dir, MARKER), 'utf8').trim()
43
+ } catch {
44
+ // 无标记:可能是首次安装,也可能是用户自建目录
45
+ }
46
+
47
+ if (marker === VERSION) return
48
+ if (marker === null && existsSync(join(dir, 'agent.cordis.yml'))) {
49
+ logger?.warn(`dsh-minimal-ptc: ${dir} exists without our marker; leaving it alone`)
50
+ return
51
+ }
52
+
53
+ for (const file of ['agent.cordis.yml', 'preset.yml', 'gitbash-executor.mjs']) {
54
+ writeFileSync(join(dir, file), readFileSync(join(presetSourceDir, file)))
55
+ }
56
+ writeFileSync(join(dir, MARKER), VERSION + '\n')
57
+ logger?.info(`dsh-minimal-ptc: materialized agent preset "${PRESET_ID}" into ${dir}`)
58
+ }
59
+
60
+ export function apply(ctx) {
61
+ try {
62
+ materialize(ctx.logger)
63
+ } catch (error) {
64
+ ctx.logger?.warn(`dsh-minimal-ptc: preset materialization failed: ${String(error)}`)
65
+ }
66
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "dsh-minimal-ptc",
3
+ "version": "0.4.0",
4
+ "description": "Minimal prompt x full PTC tools for DeepSeek Harness - aligned with the Project2 we/let's trajectory (Minimal 99/96).",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "default": "./lib/index.js"
10
+ },
11
+ "./cordis.patch.yml": "./cordis.patch.yml",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "lib",
16
+ "presets",
17
+ "cordis.patch.yml",
18
+ "README.md",
19
+ "README.en.md",
20
+ "LICENSE"
21
+ ],
22
+ "dsh": {
23
+ "bundle": {
24
+ "patch": "./cordis.patch.yml"
25
+ }
26
+ },
27
+ "keywords": [
28
+ "dsh",
29
+ "deepseek-harness",
30
+ "plugin",
31
+ "agent-preset",
32
+ "code-mode",
33
+ "dsh-plugin",
34
+ "deepseek",
35
+ "llm-agent",
36
+ "minimal",
37
+ "ptc",
38
+ "we-lets",
39
+ "chain-of-thought"
40
+ ],
41
+ "license": "MIT",
42
+ "author": "stardustlc",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/STARDUSTLC666/dsh-minimal-ptc.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/STARDUSTLC666/dsh-minimal-ptc/issues"
49
+ },
50
+ "homepage": "https://github.com/STARDUSTLC666/dsh-minimal-ptc#readme",
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "scripts": {
55
+ "test": "node --test"
56
+ },
57
+ "packageManager": "pnpm@11.7.0"
58
+ }
@@ -0,0 +1,246 @@
1
+ # The `ptc-minimal` agent preset: the `code` (PTC 模式) capabilities with the
2
+ # `minimal` (极简模式) prompt.
3
+ #
4
+ # Everything in `code` is here unchanged — full standard capabilities plus the
5
+ # Code Mode tool presentation (`tool-presentation` with `mode: code`), where the
6
+ # model composes a TypeScript program against a generated SDK and `run_code`
7
+ # executes it. What differs is the persona row, taken verbatim from `minimal`:
8
+ # a fixed one-line persona marked `complete: true` with
9
+ # `includeRuntimeContext: false`, so global identity, Web orientation, tool
10
+ # guidance, and later assembly listeners cannot add prompt text, and runtime
11
+ # context snapshots are suppressed. The `agent-instructions` row from `code` is
12
+ # dropped for the same reason — its workspace instruction baselines would add
13
+ # user-role text the minimal prompt policy excludes.
14
+ #
15
+ # This file is an AGENT-PLANE composition. It is mounted under one agent's
16
+ # scope context, so every tool it registers belongs to that session alone.
17
+ # A service row here MUST sit inside a group carrying an `isolate` realm.
18
+
19
+ # ── identity ────────────────────────────────────────────────────────────────
20
+
21
+ # The persona is the complete system prompt (minimal-mode prompt policy).
22
+ - id: persona
23
+ name: '@deepseek-ai/dsh-persona'
24
+ config:
25
+ text: You are a helpful software engineer assistant.
26
+ complete: true
27
+ includeRuntimeContext: false
28
+
29
+ # ── shell ───────────────────────────────────────────────────────────────────
30
+
31
+ # `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
32
+ # publish `DSH_WEB_URL`/`DSH_WEB_MODE`. Both shell tools consume the host
33
+ # registry from here; their executors are host-plane too.
34
+ - id: tool-bash
35
+ name: '@deepseek-ai/dsh-tool-bash'
36
+ disabled: !!js process.platform === 'win32'
37
+
38
+ - id: tool-pwsh
39
+ name: '@deepseek-ai/dsh-tool-pwsh'
40
+ disabled: !!js process.platform !== 'win32'
41
+
42
+ # Windows Git Bash shell stack. The preset-local executor runs every `bash`
43
+ # call through Git for Windows bash, keeping the model-facing tool name and
44
+ # stateless semantics identical to the POSIX `tool-bash` row above. Only
45
+ # mounted on win32; the POSIX host keeps the official bash runner.
46
+ - id: gitbash-shell
47
+ name: cordis:group
48
+ group: true
49
+ disabled: !!js process.platform !== 'win32'
50
+ isolate:
51
+ shell: true
52
+ config:
53
+ - id: gitbash-executor
54
+ name: ./gitbash-executor.mjs
55
+ config:
56
+ # 留空:executor 自动探测 GIT_BASH → 常见安装目录 → PATH → 'bash'
57
+ # 如需固定路径可显式写 shellPath: 'C:\\Program Files\\Git\\bin\\bash.exe'
58
+ timeoutMs: 120000
59
+ maxTimeoutMs: 600000
60
+ maxOutputBytes: 64000
61
+ maxSpillBytes: 67108864
62
+ graceMs: 3000
63
+
64
+ - id: tool-bash-win
65
+ name: '@deepseek-ai/dsh-tool-bash'
66
+ config:
67
+ enableRunInBackground: false
68
+
69
+ # ── filesystem ──────────────────────────────────────────────────────────────
70
+
71
+ # Both register into the host `tools` registry and provide nothing, so
72
+ # they need no realm. The `fs` service and its policy stay in the host.
73
+ - id: tool-fs
74
+ name: '@deepseek-ai/dsh-tool-fs'
75
+
76
+ - id: tool-fs-search
77
+ name: '@deepseek-ai/dsh-tool-fs-search'
78
+ config:
79
+ sampleOverCapGlobResults: false
80
+
81
+ # ── background jobs ────────────────────────────────────────────────────────
82
+
83
+ # Only the model-facing controls. The task REGISTRY stays on the host plane.
84
+ - id: tool-jobs
85
+ name: '@deepseek-ai/dsh-tool-jobs'
86
+
87
+ # ── skills ──────────────────────────────────────────────────────────────────
88
+
89
+ # The skill REGISTRY lives in the host composition and is layered per scope:
90
+ # these rows register into THIS preset's layer of it, so they need no realm.
91
+ - id: skill-filesystem
92
+ name: '@deepseek-ai/dsh-skill-filesystem'
93
+
94
+ - id: tool-skill
95
+ name: '@deepseek-ai/dsh-tool-skill'
96
+
97
+ # ── goals ───────────────────────────────────────────────────────────────────
98
+
99
+ # Only the model-facing tool. The goal SERVICE, its session driver, and the
100
+ # `/goal` command stay on the host plane.
101
+ - id: tool-goal
102
+ name: '@deepseek-ai/dsh-tool-goal'
103
+
104
+ # ── plan mode ───────────────────────────────────────────────────────────────
105
+
106
+ # Plan state is per-agent by nature, so an entry-local realm is not a
107
+ # workaround here — it is the correct lifetime.
108
+ - id: planning
109
+ name: cordis:group
110
+ group: true
111
+ isolate:
112
+ planMode: true
113
+ config:
114
+ - id: plan-mode
115
+ name: '@deepseek-ai/dsh-plan-mode'
116
+ config:
117
+ section: |
118
+ 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.
119
+
120
+ 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.
121
+
122
+ 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.
123
+
124
+ 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.
125
+
126
+ 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.
127
+
128
+ 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.
129
+
130
+ # ── compaction ──────────────────────────────────────────────────────────────
131
+
132
+ # `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner
133
+ # must share this realm rather than sit outside it. `tokenMeter` stays HOST-side.
134
+ - id: compaction
135
+ name: cordis:group
136
+ group: true
137
+ isolate:
138
+ compaction: true
139
+ toolResultPruner: true
140
+ config:
141
+ - id: compaction-basic
142
+ name: '@deepseek-ai/dsh-compaction-basic'
143
+
144
+ - id: command-compact
145
+ name: '@deepseek-ai/dsh-command-compact'
146
+
147
+ - id: tool-result-pruner
148
+ name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
149
+ config:
150
+ thresholdChars: 8192
151
+ headChars: 4096
152
+ tailChars: 1024
153
+
154
+ # ── delegation and workflows ────────────────────────────────────────────────
155
+
156
+ # The `subagents` registry and its spawn/fork backends live in the HOST
157
+ # composition. This preset contributes the delegation TOOLS. `workflows` shares
158
+ # one entry-local realm here, and a consumer left outside would resolve a host
159
+ # registry this preset does not populate.
160
+ - id: delegation
161
+ name: cordis:group
162
+ group: true
163
+ isolate:
164
+ workflowEngine: true
165
+ config:
166
+ - id: tool-subagent-control
167
+ name: '@deepseek-ai/dsh-tool-subagent-control'
168
+
169
+ - id: tool-subagent-list-agents
170
+ name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
171
+
172
+ - id: tool-subagent
173
+ name: '@deepseek-ai/dsh-tool-subagent'
174
+ config:
175
+ provider: spawn
176
+ toolName: subagent
177
+ backgroundMode: continuable
178
+
179
+ - id: tool-subagent-fork
180
+ name: '@deepseek-ai/dsh-tool-subagent'
181
+ config:
182
+ provider: fork
183
+ toolName: subagent_fork
184
+ backgroundMode: continuable
185
+
186
+ # Product providers are host-plane singletons. Remove `disabled` from
187
+ # either ordinary tool row to expose that product to agents on this preset.
188
+ - id: tool-subagent-codex
189
+ name: '@deepseek-ai/dsh-tool-subagent'
190
+ disabled: true
191
+ config:
192
+ provider: codex
193
+ toolName: subagent_codex
194
+ enableRunInBackground: false
195
+ maxDepth: provider-managed
196
+
197
+ - id: tool-subagent-claude-code
198
+ name: '@deepseek-ai/dsh-tool-subagent'
199
+ disabled: true
200
+ config:
201
+ provider: claude-code
202
+ toolName: subagent_claude_code
203
+ enableRunInBackground: false
204
+ maxDepth: provider-managed
205
+
206
+ - id: workflow-worker-thread
207
+ name: '@deepseek-ai/dsh-workflow-worker-thread'
208
+ config:
209
+ provider: spawn
210
+
211
+ - id: tool-workflow
212
+ name: '@deepseek-ai/dsh-tool-workflow'
213
+
214
+ - id: tool-ralph
215
+ name: '@deepseek-ai/dsh-tool-ralph'
216
+ config:
217
+ subagentProvider: spawn
218
+ maxRounds: 64
219
+
220
+ # ── remaining model-facing rows ─────────────────────────────────────────────
221
+
222
+ - id: tool-ask-user
223
+ name: '@deepseek-ai/dsh-tool-ask-user'
224
+
225
+ - id: tool-todo
226
+ name: '@deepseek-ai/dsh-tool-todo'
227
+ config:
228
+ allowParallelInProgress: true
229
+
230
+ # The `web` service and its search provider stay in the host composition; only
231
+ # the model-facing tool is per-session.
232
+ - id: tool-web
233
+ name: '@deepseek-ai/dsh-tool-web'
234
+ config:
235
+ fetch: false
236
+ searchTimeoutMs: 60000
237
+
238
+ # ── presentation ────────────────────────────────────────────────────────────
239
+
240
+ # Code Mode for this agent alone. The row waits for the host's `codeRuntime`
241
+ # rather than assuming it: a deployment that composes no TypeScript runtime
242
+ # fails this preset at mount, naming this id, instead of at the first request.
243
+ - id: tool-presentation
244
+ name: '@deepseek-ai/dsh-agent-tool-presentation'
245
+ config:
246
+ mode: code
@@ -0,0 +1,340 @@
1
+ /**
2
+ * gitbash-executor: Git-for-Windows bash provider for the `minimal-gitbash`
3
+ * agent preset.
4
+ *
5
+ * Why this file exists:
6
+ * - the shipped `minimal` preset's persistent PTY shell cannot start on
7
+ * Windows (`@deepseek-ai/dsh-subprocess-local` refuses win32 terminal
8
+ * inspection), and
9
+ * - `@deepseek-ai/dsh-bash-local` hardcodes `bash` from PATH, which a
10
+ * Windows machine does not provide.
11
+ *
12
+ * This plugin replaces `ctx.shell` inside an entry-local realm (see the
13
+ * preset's `gitbash-shell` group) and runs every command as
14
+ * `"<git bash>" -c <command>` through the host subprocess service — a fresh
15
+ * shell per call, with the same `bash` tool name and `str_replace_editor`
16
+ * surface as the original preset. Only Node built-in modules are imported:
17
+ * preset-local files resolve against the preset directory, so DSH package
18
+ * imports (node_modules) would fail, while `node:fs` is always available.
19
+ *
20
+ * Sandbox note: the MSYS runtime cannot initialize inside the Windows
21
+ * restricted-token sandbox (it cannot create its signal pipes), so commands
22
+ * are gated on the danger-full-access policy. The bash tool's single-call
23
+ * `sandbox_permissions: "danger-full-access"` escalation, or switching the
24
+ * session to full access, satisfies the gate.
25
+ */
26
+
27
+ import { existsSync } from 'node:fs'
28
+
29
+ /** Cordis plugin name used by loader diagnostics. */
30
+ export const name = 'gitbash-executor'
31
+
32
+ /** Required host services; ordering matters because apply() reads them eagerly. */
33
+ export const inject = ['subprocess', 'sandboxPolicy']
34
+
35
+ /** Node's maximum timer delay before setTimeout clamps to 1ms. */
36
+ const MAX_TIMER_DELAY_MS = 2147483647
37
+
38
+ const ENV_OVERRIDES = {
39
+ NO_COLOR: '1',
40
+ TERM: 'dumb',
41
+ PAGER: 'cat',
42
+ GIT_PAGER: 'cat',
43
+ }
44
+
45
+ /**
46
+ * Convert a Git Bash / MSYS drive path such as `/d/foo` into the Windows path
47
+ * `D:\foo` that Node's child_process can use as cwd or executable path.
48
+ * Only the single-letter drive form is converted: `/d`, `/d/`, `/d/foo`
49
+ * (the letter must be immediately followed by `/` or end the string), so
50
+ * MSYS root paths like `/usr/bin` are left untouched instead of being
51
+ * mangled into `U:\sr\bin`. Everything else (UNC, `D:\`, `D:/`) passes
52
+ * through unchanged.
53
+ */
54
+ export function toWindowsPath(value) {
55
+ if (process.platform !== 'win32' || typeof value !== 'string' || value.length === 0) {
56
+ return value
57
+ }
58
+ const match = /^\/\s*([A-Za-z])(?:$|\/(.*))$/.exec(value)
59
+ if (match === null) return value
60
+ const drive = `${match[1].toUpperCase()}:`
61
+ const rest = match[2] ?? ''
62
+ if (rest === '') return `${drive}\\`
63
+ return `${drive}\\${rest.replace(/\//g, '\\')}`
64
+ }
65
+
66
+ /**
67
+ * Candidates for the Git-for-Windows bash executable, in preference order:
68
+ * the GIT_BASH environment variable, the standard install roots, this
69
+ * machine's known install location, then every `bash.exe` found on PATH.
70
+ */
71
+ function shellPathCandidates(env) {
72
+ const candidates = [
73
+ env.GIT_BASH,
74
+ env.ProgramFiles === undefined ? undefined : `${env.ProgramFiles}\\Git\\bin\\bash.exe`,
75
+ env['ProgramFiles(x86)'] === undefined ? undefined : `${env['ProgramFiles(x86)']}\\Git\\bin\\bash.exe`,
76
+ env.LOCALAPPDATA === undefined ? undefined : `${env.LOCALAPPDATA}\\Programs\\Git\\bin\\bash.exe`,
77
+ ]
78
+ if (typeof env.PATH === 'string' && env.PATH.length > 0) {
79
+ for (const dir of env.PATH.split(';')) {
80
+ if (dir.length === 0) continue
81
+ candidates.push(`${dir}\\bash.exe`)
82
+ }
83
+ }
84
+ return candidates
85
+ }
86
+
87
+ /**
88
+ * Resolve the shell executable to spawn. An explicit configuration wins; a
89
+ * POSIX host always gets `bash`. On Windows the first existing candidate is
90
+ * used (GIT_BASH → install roots → PATH), falling back to the bare `bash`
91
+ * name when nothing exists so spawn reports a resolution error.
92
+ */
93
+ export function detectShellPath(explicit, env = process.env) {
94
+ if (process.platform !== 'win32') {
95
+ return typeof explicit === 'string' && explicit.length > 0 ? explicit : 'bash'
96
+ }
97
+ if (typeof explicit === 'string' && explicit.length > 0) return toWindowsPath(explicit)
98
+ for (const candidate of shellPathCandidates(env)) {
99
+ if (typeof candidate !== 'string' || candidate.length === 0) continue
100
+ if (existsSync(candidate)) return toWindowsPath(candidate)
101
+ }
102
+ return 'bash'
103
+ }
104
+
105
+ function positiveNumber(config, label, fallback) {
106
+ const value = config[label] ?? fallback
107
+ if (!Number.isFinite(value) || value <= 0) {
108
+ throw new TypeError(`${name}: ${label} must be a positive finite number`)
109
+ }
110
+ return value
111
+ }
112
+
113
+ export function resolveConfig(config, env = process.env) {
114
+ const source = config ?? {}
115
+ const timeoutMs = positiveNumber(source, 'timeoutMs', 120000)
116
+ const maxTimeoutMs = positiveNumber(source, 'maxTimeoutMs', 600000)
117
+ const graceMs = positiveNumber(source, 'graceMs', 3000)
118
+ if (timeoutMs > MAX_TIMER_DELAY_MS || maxTimeoutMs > MAX_TIMER_DELAY_MS || graceMs > MAX_TIMER_DELAY_MS) {
119
+ throw new TypeError(`${name}: timeoutMs, maxTimeoutMs and graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`)
120
+ }
121
+ return {
122
+ shellPath: detectShellPath(source.shellPath, env),
123
+ cwd: typeof source.cwd === 'string' && source.cwd.length > 0
124
+ ? toWindowsPath(source.cwd)
125
+ : undefined,
126
+ timeoutMs,
127
+ maxTimeoutMs,
128
+ maxOutputBytes: positiveNumber(source, 'maxOutputBytes', 64000),
129
+ maxSpillBytes: positiveNumber(source, 'maxSpillBytes', 64 * 1024 * 1024),
130
+ graceMs,
131
+ }
132
+ }
133
+
134
+ /** Fuse upstream cancellation with an identifiable timeout. */
135
+ function timeoutSignal(upstream, timeoutMs) {
136
+ const timer = new AbortController()
137
+ const id = setTimeout(() => {
138
+ timer.abort(new Error('BASH_TIMEOUT'))
139
+ }, timeoutMs)
140
+ const signal = upstream === undefined
141
+ ? timer.signal
142
+ : AbortSignal.any([upstream, timer.signal])
143
+ return {
144
+ signal,
145
+ timedOut: () => timer.signal.aborted,
146
+ dispose: () => clearTimeout(id),
147
+ }
148
+ }
149
+
150
+ /** Project a settled subprocess collector into the tool result shape. */
151
+ function finalOutput(reader) {
152
+ const read = reader.readFrom(0)
153
+ return {
154
+ text: read.text,
155
+ truncated: read.lossy,
156
+ ...read.spillPath === undefined ? {} : { spillPath: read.spillPath },
157
+ }
158
+ }
159
+
160
+ /** Wrap a spawn failure with the shell path and workdir that caused it. */
161
+ function spawnError(shellPath, workdir, cause) {
162
+ return new Error(`${name}: failed to start ${shellPath} (workdir: ${workdir}): ${cause?.message ?? String(cause)}`, {
163
+ cause,
164
+ })
165
+ }
166
+
167
+ /** Register the git-bash executor as the entry-local `shell` provider. */
168
+ export function apply(ctx, config) {
169
+ const resolved = resolveConfig(config)
170
+ const subprocess = ctx.get('subprocess')
171
+ if (subprocess === undefined) {
172
+ throw new Error(`${name}: ctx.subprocess is unavailable`)
173
+ }
174
+ const sandboxPolicy = ctx.get('sandboxPolicy')
175
+
176
+ const gateMessage = (mode) =>
177
+ `${name}: the Git-for-Windows bash runtime cannot start inside the "${mode ?? 'unknown'}" sandbox `
178
+ + '(the MSYS runtime cannot create its signal pipes under the restricted token). '
179
+ + 'Retry this exact command once with sandbox_permissions: "danger-full-access" plus a justification, '
180
+ + 'or ask the user to switch the session sandbox mode to full access.'
181
+
182
+ const spawnSpec = (spec, argv, stdoutMaxBytes, signal) => ({
183
+ argv,
184
+ cwd: spec.workdir,
185
+ stdio: {
186
+ stdin: spec.stdin !== undefined ? { data: spec.stdin } : 'ignore',
187
+ stdout: { maxBytes: stdoutMaxBytes, spill: { maxBytes: resolved.maxSpillBytes } },
188
+ stderr: { maxBytes: resolved.maxOutputBytes, spill: { maxBytes: resolved.maxSpillBytes } },
189
+ },
190
+ graceMs: resolved.graceMs,
191
+ signal,
192
+ env: {
193
+ ...ENV_OVERRIDES,
194
+ ...spec.env,
195
+ ...spec.dshEnv,
196
+ },
197
+ })
198
+
199
+ const spawnShell = (spec, stdoutMaxBytes, signal) => {
200
+ try {
201
+ return subprocess.spawn(spawnSpec(
202
+ spec,
203
+ [resolved.shellPath, '-c', spec.command],
204
+ stdoutMaxBytes,
205
+ signal,
206
+ ))
207
+ } catch (error) {
208
+ throw spawnError(resolved.shellPath, spec.workdir, error)
209
+ }
210
+ }
211
+
212
+ const executor = {
213
+ /** Advertise confinement so the bash tool offers per-call escalation. */
214
+ get sandboxMode() {
215
+ return sandboxPolicy === undefined ? undefined : sandboxPolicy.defaultMode
216
+ },
217
+
218
+ resolve(request) {
219
+ const timeoutMs = Math.min(request.timeoutMs ?? resolved.timeoutMs, resolved.maxTimeoutMs)
220
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
221
+ throw new Error(`${name}: request.timeoutMs must be a positive finite number`)
222
+ }
223
+ const stdoutMaxBytes = request.stdoutMaxBytes ?? resolved.maxOutputBytes
224
+ if (!Number.isFinite(stdoutMaxBytes) || stdoutMaxBytes <= 0) {
225
+ throw new Error(`${name}: request.stdoutMaxBytes must be a positive finite number`)
226
+ }
227
+ return {
228
+ command: request.command,
229
+ workdir: toWindowsPath(request.workdir ?? resolved.cwd ?? process.cwd()),
230
+ timeoutMs,
231
+ stdoutMaxBytes,
232
+ ...request.signal === undefined ? {} : { signal: request.signal },
233
+ ...request.stdin === undefined ? {} : { stdin: request.stdin },
234
+ ...request.env === undefined ? {} : { env: request.env },
235
+ ...request.dshEnv === undefined ? {} : { dshEnv: request.dshEnv },
236
+ sandboxPolicy: request.sandboxPolicy ?? (sandboxPolicy?.resolve === undefined ? undefined : sandboxPolicy.resolve()),
237
+ }
238
+ },
239
+
240
+ async run(spec) {
241
+ const mode = spec.sandboxPolicy?.mode
242
+ // `undefined` means the deployment carries no sandbox policy at all.
243
+ if (mode !== undefined && mode !== 'danger-full-access') {
244
+ throw new Error(gateMessage(mode))
245
+ }
246
+ const fused = timeoutSignal(spec.signal, spec.timeoutMs)
247
+ try {
248
+ const handle = spawnShell(spec, spec.stdoutMaxBytes, fused.signal)
249
+ let outcome
250
+ try {
251
+ outcome = await handle.done
252
+ } catch (error) {
253
+ throw spawnError(resolved.shellPath, spec.workdir, error)
254
+ }
255
+ const stdout = finalOutput(handle.collected.stdout)
256
+ const stderr = finalOutput(handle.collected.stderr)
257
+ const timedOut = fused.timedOut()
258
+ const aborted = spec.signal !== undefined && spec.signal.aborted && !timedOut
259
+ return {
260
+ ...outcome,
261
+ timedOut,
262
+ aborted,
263
+ timeoutMs: spec.timeoutMs,
264
+ stdout,
265
+ stderr,
266
+ ...mode === undefined ? {} : { sandbox: { mode, denied: false } },
267
+ }
268
+ } finally {
269
+ fused.dispose()
270
+ }
271
+ },
272
+
273
+ start(spec) {
274
+ const mode = spec.sandboxPolicy?.mode
275
+ if (mode !== undefined && mode !== 'danger-full-access') {
276
+ throw new Error(gateMessage(mode))
277
+ }
278
+ const fused = timeoutSignal(spec.signal, spec.timeoutMs)
279
+ let running
280
+ try {
281
+ running = spawnShell(spec, resolved.maxOutputBytes, fused.signal)
282
+ } catch (error) {
283
+ fused.dispose()
284
+ throw error
285
+ }
286
+ const collected = {
287
+ stdout: running.collected.stdout,
288
+ stderr: running.collected.stderr,
289
+ }
290
+ let spawnFailureNote
291
+ const consumeSpawnFailure = () => {
292
+ const note = spawnFailureNote ?? ''
293
+ spawnFailureNote = undefined
294
+ return note
295
+ }
296
+ let stdoutOffset = 0
297
+ let stderrOffset = 0
298
+ const proc = {
299
+ status: 'running',
300
+ exitCode: null,
301
+ signal: null,
302
+ done: running.done.then((outcome) => {
303
+ if (proc.status === 'running') {
304
+ proc.status = fused.timedOut() || spec.signal?.aborted === true || outcome.signal !== null
305
+ ? 'killed'
306
+ : 'completed'
307
+ }
308
+ proc.exitCode = outcome.exitCode
309
+ proc.signal = outcome.signal
310
+ }, (error) => {
311
+ proc.status = 'killed'
312
+ spawnFailureNote = spawnError(resolved.shellPath, spec.workdir, error).message
313
+ }).finally(() => fused.dispose()),
314
+ readOutput: () => {
315
+ const out = collected.stdout.readFrom(stdoutOffset)
316
+ const err = collected.stderr.readFrom(stderrOffset)
317
+ stdoutOffset = out.nextOffset
318
+ stderrOffset = err.nextOffset
319
+ const errText = err.text.length > 0 ? err.text : consumeSpawnFailure()
320
+ const separator = out.text.length > 0 && !out.text.endsWith('\n') ? '\n' : ''
321
+ return {
322
+ delta: out.text + (errText.length > 0 ? `${separator}[stderr]\n${errText}` : ''),
323
+ lossy: out.lossy || err.lossy,
324
+ ...out.spillPath === undefined ? {} : { stdoutSpillPath: out.spillPath },
325
+ ...err.spillPath === undefined ? {} : { stderrSpillPath: err.spillPath },
326
+ }
327
+ },
328
+ kill: () => {
329
+ if (proc.status !== 'running') return false
330
+ proc.status = 'killed'
331
+ running.terminate()
332
+ return true
333
+ },
334
+ }
335
+ return proc
336
+ },
337
+ }
338
+
339
+ ctx.provide('shell', executor)
340
+ }
@@ -0,0 +1,2 @@
1
+ name: 极简 PTC 模式
2
+ description: 提示词与极简模式一致(一句固定人格,无运行时上下文与附加指令),保留 PTC 模式的全部能力:Code Mode SDK 多步工具编排,以及文件、Shell、检索、Skills、计划、目标、子代理和工作流。Windows 下通过预设内置 Git Bash executor 保持 bash 工具可用,与 RL 训练动作分布对齐。