@eddyskywalker/dsh-chatgpt-subscription 0.3.2 → 0.3.4
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 +2 -0
- package/README.md +24 -0
- package/lib/index.js +220 -3
- package/lib/types/host/preset-sync.d.ts +73 -0
- package/lib/types/host/preset-sync.d.ts.map +1 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/presets/dispatch/README.md +55 -0
- package/presets/dispatch/agent.cordis.yml +358 -0
- package/presets/dispatch/preset.yml +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- **调度模式 Agent Preset 随包分发**:新增 `presets/dispatch/`(基于 PTC 模式的编排 preset:R0 复杂度分诊 → L2 任务的澄清访谈 → 规划 → 派发 → 审查 → 验收;子代理必须显式指定模型且落在「子代理」设置白名单内;选择不可用时按 DSH 默认行为降级)与 `src/host/preset-sync.ts`。DSH 只能从配置根、内置 `agent-presets` 包的 `presets/`、以及 `<dshHome>/.agent-presets` 发现 preset,插件包无法自行注册根目录,因此采用「包内随附 + 启动时同步到 home」的方式(与 `@linxin666/dsh-liangshen` 同一机制)。同步幂等、只处理本包自己的 id、绝不触碰用户手写的 preset,失败只记 warn 不阻断插件加载;新增 `config.syncAgentPresets`(默认 `true`)可关闭。包根通过向上查找最近的 `package.json` 定位,兼容 `src/` 与打包后的 `lib/` 两种布局(写死 `../presets/` 在两种布局下会解析到不同目录);复制逐条目实现,规避 Node 22 + Windows 上 `fs.cpSync` 遇到非 ASCII 路径直接崩进程的问题(nodejs/node#54476)。新增 `test/preset-sync.test.ts`(8 条)覆盖首次同步、幂等跳过、内容变更重写与多余文件清理、不触碰非本插件目录、retire 与保留、源目录缺失。`package.json` 的 `files` 增加 `presets`、`exports` 增加 `./presets/*`,`npm pack --dry-run` 确认三个 preset 文件随包发布。
|
|
6
|
+
|
|
5
7
|
- **子代理必须写明模型**:授权守卫不再只拒绝「写明且不在白名单内」的路由,而是要求带有允许列表的会话里每次委派都成对给出 `provider` + `model`。此前不写路由的调用会让子代理继承父级模型(设置卡白名单形同虚设,子代理总是跑在主模型上);现在缺省与只写一半都会被拒绝,拒绝理由里给出全部已授权路由并提示先用 `list_subagent_models` 查询。只读取公开接口(`ctx.tools.guard` + 会话日志 + 设置文档),不修改 DSH 本体。
|
|
6
8
|
- 授权相关拒绝文案拆分为「未指定路由」「只指定一半」「路由不在列表内」三种,`delegationDenialReason` 不再回退到父级 `options` 判定继承路由;新增/改写 `test/subagent-model-authorization.test.ts` 用例覆盖这三种拒绝,以及未记录策略与非托管工具名保持放行。
|
|
7
9
|
- 确认 `run_code`(代码模式)无法绕过该守卫:程序里通过 SDK 调用的 `tools["subagent"]` 走的是同一套 `prepare → guard → dispatch` 调度流水线,守卫在调度入口拦截,拒绝理由以 `ToolCallError` 抛回程序。新增 `test/subagent-model-authorization-ptc.test.ts`(3 条)用真实 `ToolRuntime`(code 模式)+ 假 `CodeRuntime` 驱动绑定函数,覆盖「已授权放行 / 缺省拒绝 / 越权拒绝」;这是实测而非假设(先看 DSH 源码确认嵌套子调用确实复用同一调度器,再用用例锁定行为)。
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- [Kimi Code 线路](#kimi-code-线路)
|
|
15
15
|
- [Command Code 线路](#command-code-线路)
|
|
16
16
|
- [子代理模型授权](#子代理模型授权0215-起)
|
|
17
|
+
- [随包分发的 Agent Preset](#随包分发的-agent-preset)
|
|
17
18
|
- [升级、降级与卸载](#升级降级与卸载)
|
|
18
19
|
- [安全边界](#安全边界)
|
|
19
20
|
- [插件路由](#插件路由)
|
|
@@ -181,6 +182,29 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。6 Astra 与 G
|
|
|
181
182
|
|
|
182
183
|
> Windows 文件只能由创建它的用户通过 DPAPI 解密。macOS 凭据由登录钥匙串在本机加密保存。Linux 文件是未额外加密的 JSON,依赖目录 `0700` 和文件 `0600` 隔离;不要复制、打印或提交该文件。跨平台迁移需要重新登录。
|
|
183
184
|
|
|
185
|
+
## 随包分发的 Agent Preset
|
|
186
|
+
|
|
187
|
+
插件自带一个 **调度模式** agent preset(id `dispatch`),随 npm 安装一起分发:启动时会把它从包内 `presets/dispatch/` 同步到 DSH 的 preset 发现根目录 `<dshHome>/.agent-presets/`,因此任何装了本插件的机器都能在新建会话时直接选到它,不需要手工拷贝文件。
|
|
188
|
+
|
|
189
|
+
同步在每个 profile 启动时执行一次(幂等):
|
|
190
|
+
|
|
191
|
+
- 目标树与包内副本逐字节相同时跳过,有差异时整体重写,并把包内已删除的多余文件清理掉;
|
|
192
|
+
- 只处理本包自己的 preset id(`BUNDLED_PRESET_IDS`),**绝不改动用户手写的 preset 或其它插件的 preset**;
|
|
193
|
+
- 包内已不再随附的旧 id 会从目标根目录移除(retire);
|
|
194
|
+
- 同步失败(例如 home 只读)只记一条 warn,不会导致插件加载失败——preset 是便利项,不是本插件提供的核心能力。
|
|
195
|
+
|
|
196
|
+
> 路径解析不写死相对路径:`src/host/preset-sync.ts` 从模块位置向上查找最近的 `package.json` 作为包根,因此 `src/` 布局、打包后的 `lib/` 布局,以及通过 pnpm symlink / Windows junction 安装都能正确解析。注意 `fs.cpSync({ recursive: true })` 在 Node 22 + Windows 上遇到含非 ASCII 的源路径会直接崩进程(nodejs/node#54476),所以复制是逐条目实现的。
|
|
197
|
+
|
|
198
|
+
### 配置项
|
|
199
|
+
|
|
200
|
+
| 字段 | 默认 | 含义 |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `syncAgentPresets` | `true` | 启动时是否把包内 preset 同步到 `<dshHome>/.agent-presets`;设为 `false` 则完全不写用户目录 |
|
|
203
|
+
|
|
204
|
+
### 手动安装(可选)
|
|
205
|
+
|
|
206
|
+
不想让插件写 home 目录时,可把 `syncAgentPresets` 设为 `false`,再自行拷贝包内的 `presets/dispatch/` 到 `<dshHome>/.agent-presets/dispatch/`。
|
|
207
|
+
|
|
184
208
|
## 子代理模型授权(0.2.15 起,0.3.2 起强制指定模型)
|
|
185
209
|
|
|
186
210
|
DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允许列表(会话日志事件 `subagent/model-selection-policy`)。DSH 内置委派工具只拒绝**模型显式填写**且不在列表内的路由;调用里不写 `provider`/`model` 时,子代理会继承父级模型,于是白名单之外的主模型(例如 `deepseek-official/deepseek-flash`)仍会被子代理使用。
|
package/lib/index.js
CHANGED
|
@@ -10,14 +10,14 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
10
10
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
11
11
|
import http, { createServer } from "node:http";
|
|
12
12
|
import { execSync, spawn } from "node:child_process";
|
|
13
|
-
import fs, { appendFileSync, constants, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
14
|
-
import path, { dirname, join } from "node:path";
|
|
13
|
+
import fs, { appendFileSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
14
|
+
import path, { basename, dirname, join, relative } from "node:path";
|
|
15
15
|
import os, { homedir } from "node:os";
|
|
16
16
|
import { ProxyAgent, fetch as fetch$1 } from "undici";
|
|
17
17
|
import * as SettingsModule from "@deepseek-ai/dsh-settings";
|
|
18
18
|
import fsPromises, { chmod, lstat, mkdir, open, rename, stat, unlink } from "node:fs/promises";
|
|
19
19
|
import { isDeepStrictEqual } from "node:util";
|
|
20
|
-
import { URL as URL$1, URLSearchParams as URLSearchParams$1 } from "node:url";
|
|
20
|
+
import { URL as URL$1, URLSearchParams as URLSearchParams$1, fileURLToPath } from "node:url";
|
|
21
21
|
//#region src/compat.ts
|
|
22
22
|
/**
|
|
23
23
|
* Compatibility constants for the ChatGPT-backed Codex flow. The backend and
|
|
@@ -14611,6 +14611,211 @@ function validateAuthorizationScope(scope) {
|
|
|
14611
14611
|
throw new Error(`dsh-chatgpt-subscription: subagentModelScope must be "session" or "preference", received ${JSON.stringify(scope)}`);
|
|
14612
14612
|
}
|
|
14613
14613
|
//#endregion
|
|
14614
|
+
//#region src/host/preset-sync.ts
|
|
14615
|
+
/**
|
|
14616
|
+
* Sync this plugin's bundled agent presets into the harness-home preset root.
|
|
14617
|
+
*
|
|
14618
|
+
* DSH discovers agent presets only from configured roots, the shipped
|
|
14619
|
+
* `agent-presets` package's own `presets/` directory, and
|
|
14620
|
+
* `<dshHome>/.agent-presets`. A plugin package cannot register a root by
|
|
14621
|
+
* existing, so the way a preset travels with an npm install is to ship the
|
|
14622
|
+
* directory inside the package and copy it into the harness-home root at
|
|
14623
|
+
* startup — the same arrangement `@linxin666/dsh-liangshen` uses.
|
|
14624
|
+
*
|
|
14625
|
+
* Copy is per-preset-directory and idempotent: a target tree byte-identical to
|
|
14626
|
+
* the bundled one is skipped, otherwise the whole tree is copied and any
|
|
14627
|
+
* target-only files are pruned. Preset ids listed in `retire` are removed
|
|
14628
|
+
* when the bundle no longer ships them. Directories this plugin does not own —
|
|
14629
|
+
* presets the user wrote by hand, or another plugin's — are never touched.
|
|
14630
|
+
*
|
|
14631
|
+
* `fs.cpSync({ recursive: true })` is deliberately avoided: on Node 22 for
|
|
14632
|
+
* Windows it can abort the process with STATUS_STACK_BUFFER_OVERRUN when the
|
|
14633
|
+
* source path contains non-ASCII characters (nodejs/node#54476). The copy is
|
|
14634
|
+
* per-entry instead, which also lets source mtimes ride along.
|
|
14635
|
+
* @module dsh-chatgpt-subscription/preset-sync
|
|
14636
|
+
*/
|
|
14637
|
+
/** The preset directory this package ships. */
|
|
14638
|
+
const BUNDLED_PRESET_IDS = ["dispatch"];
|
|
14639
|
+
/** Clock tolerance for the mtime fast path before falling through to a byte compare. */
|
|
14640
|
+
const MTIME_TOLERANCE_MS = 1e3;
|
|
14641
|
+
/**
|
|
14642
|
+
* The package root, found by walking up from this module to the nearest
|
|
14643
|
+
* `package.json`.
|
|
14644
|
+
*
|
|
14645
|
+
* A fixed `../presets/` is wrong here: this module sits two levels below the
|
|
14646
|
+
* package root under `src/host/` but is bundled flat into `lib/`, so the same
|
|
14647
|
+
* relative specifier resolves to a different directory in each layout. Walking
|
|
14648
|
+
* up is layout-independent — it holds under `src/`, under the bundled
|
|
14649
|
+
* `lib/`, and when the package is reached through a pnpm symlink or a
|
|
14650
|
+
* Windows junction.
|
|
14651
|
+
* @returns absolute package root path.
|
|
14652
|
+
*/
|
|
14653
|
+
function packageRoot() {
|
|
14654
|
+
let dir = fileURLToPath(new URL(".", import.meta.url));
|
|
14655
|
+
for (;;) {
|
|
14656
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
14657
|
+
const parent = dirname(dir);
|
|
14658
|
+
if (parent === dir) throw new Error("dsh-chatgpt-subscription: could not locate the package root");
|
|
14659
|
+
dir = parent;
|
|
14660
|
+
}
|
|
14661
|
+
}
|
|
14662
|
+
/** Absolute path of the `presets/` directory shipped inside this package. */
|
|
14663
|
+
function bundledPresetsRoot() {
|
|
14664
|
+
return join(packageRoot(), "presets");
|
|
14665
|
+
}
|
|
14666
|
+
/** The harness-home agent-presets discovery root this plugin syncs into. */
|
|
14667
|
+
function presetTargetRoot(home) {
|
|
14668
|
+
return join(home, ".agent-presets");
|
|
14669
|
+
}
|
|
14670
|
+
/** Every file below `root`, as absolute paths. */
|
|
14671
|
+
function filesUnder(root) {
|
|
14672
|
+
const out = [];
|
|
14673
|
+
const walk = (dir) => {
|
|
14674
|
+
for (const entry of readdirSync(dir)) {
|
|
14675
|
+
const path = join(dir, entry);
|
|
14676
|
+
if (statSync(path).isDirectory()) walk(path);
|
|
14677
|
+
else out.push(path);
|
|
14678
|
+
}
|
|
14679
|
+
};
|
|
14680
|
+
walk(root);
|
|
14681
|
+
return out;
|
|
14682
|
+
}
|
|
14683
|
+
/**
|
|
14684
|
+
* Whether two files are byte-identical. Size and mtime only rule a pair out
|
|
14685
|
+
* cheaply; an equal size and close mtime still fall through to a byte compare
|
|
14686
|
+
* so a content difference is never missed.
|
|
14687
|
+
*/
|
|
14688
|
+
function sameFile(a, b) {
|
|
14689
|
+
const sourceStat = statSync(a);
|
|
14690
|
+
const targetStat = statSync(b);
|
|
14691
|
+
if (sourceStat.size !== targetStat.size) return false;
|
|
14692
|
+
if (Math.abs(sourceStat.mtimeMs - targetStat.mtimeMs) > MTIME_TOLERANCE_MS) return false;
|
|
14693
|
+
return readFileSync(a).equals(readFileSync(b));
|
|
14694
|
+
}
|
|
14695
|
+
/** Remove target files not in `keep`, then directories those removals emptied. */
|
|
14696
|
+
function pruneExtras(root, keep) {
|
|
14697
|
+
const parents = /* @__PURE__ */ new Set();
|
|
14698
|
+
for (const file of filesUnder(root)) if (!keep.has(relative(root, file))) {
|
|
14699
|
+
parents.add(dirname(file));
|
|
14700
|
+
rmSync(file, { force: true });
|
|
14701
|
+
}
|
|
14702
|
+
for (const start of parents) {
|
|
14703
|
+
let dir = start;
|
|
14704
|
+
while (dir !== void 0 && relative(root, dir) !== "") if (existsSync(dir) && readdirSync(dir).length === 0) {
|
|
14705
|
+
rmSync(dir, {
|
|
14706
|
+
recursive: true,
|
|
14707
|
+
force: true
|
|
14708
|
+
});
|
|
14709
|
+
dir = dirname(dir);
|
|
14710
|
+
} else dir = void 0;
|
|
14711
|
+
}
|
|
14712
|
+
}
|
|
14713
|
+
/** Copy the tree under `sourceDir` into `targetDir`, creating it as needed. */
|
|
14714
|
+
function copyTreeSync(sourceDir, targetDir) {
|
|
14715
|
+
mkdirSync(targetDir, { recursive: true });
|
|
14716
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
14717
|
+
const source = join(sourceDir, entry);
|
|
14718
|
+
const target = join(targetDir, entry);
|
|
14719
|
+
const stat = statSync(source);
|
|
14720
|
+
if (stat.isDirectory()) copyTreeSync(source, target);
|
|
14721
|
+
else {
|
|
14722
|
+
copyFileSync(source, target);
|
|
14723
|
+
utimesSync(target, stat.atime, stat.mtime);
|
|
14724
|
+
}
|
|
14725
|
+
}
|
|
14726
|
+
}
|
|
14727
|
+
/**
|
|
14728
|
+
* Copy `sourceDir` into `targetDir` idempotently.
|
|
14729
|
+
* @param sourceDir - bundled preset directory.
|
|
14730
|
+
* @param targetDir - discovery-root directory for the same preset id.
|
|
14731
|
+
* @returns whether anything was written.
|
|
14732
|
+
*/
|
|
14733
|
+
function syncOnePreset(sourceDir, targetDir) {
|
|
14734
|
+
const sourceFiles = filesUnder(sourceDir);
|
|
14735
|
+
const sourceSet = new Set(sourceFiles.map((file) => relative(sourceDir, file)));
|
|
14736
|
+
if (existsSync(targetDir) && !statSync(targetDir).isDirectory()) rmSync(targetDir, {
|
|
14737
|
+
recursive: true,
|
|
14738
|
+
force: true
|
|
14739
|
+
});
|
|
14740
|
+
if (!existsSync(targetDir)) {
|
|
14741
|
+
copyTreeSync(sourceDir, targetDir);
|
|
14742
|
+
pruneExtras(targetDir, sourceSet);
|
|
14743
|
+
return "synced";
|
|
14744
|
+
}
|
|
14745
|
+
let dirty = false;
|
|
14746
|
+
for (const file of sourceFiles) {
|
|
14747
|
+
const dest = join(targetDir, relative(sourceDir, file));
|
|
14748
|
+
if (!existsSync(dest) || !sameFile(file, dest)) {
|
|
14749
|
+
dirty = true;
|
|
14750
|
+
break;
|
|
14751
|
+
}
|
|
14752
|
+
}
|
|
14753
|
+
if (!dirty) {
|
|
14754
|
+
for (const file of filesUnder(targetDir)) if (!sourceSet.has(relative(targetDir, file))) {
|
|
14755
|
+
dirty = true;
|
|
14756
|
+
break;
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14759
|
+
if (!dirty) return "current";
|
|
14760
|
+
pruneExtras(targetDir, sourceSet);
|
|
14761
|
+
copyTreeSync(sourceDir, targetDir);
|
|
14762
|
+
pruneExtras(targetDir, sourceSet);
|
|
14763
|
+
return "synced";
|
|
14764
|
+
}
|
|
14765
|
+
/**
|
|
14766
|
+
* Sync every bundled preset into `targetRoot`, then remove ids named in
|
|
14767
|
+
* `retire` that the bundle no longer ships. Only those exact ids are removed;
|
|
14768
|
+
* every other target directory is left alone.
|
|
14769
|
+
* @param sourceRoot - this package's bundled `presets/` directory.
|
|
14770
|
+
* @param targetRoot - harness-home agent-presets discovery root.
|
|
14771
|
+
* @param retire - previously bundled preset ids to remove when absent from the source.
|
|
14772
|
+
* @returns the grouped outcome of the run.
|
|
14773
|
+
*/
|
|
14774
|
+
function syncPresetTrees(sourceRoot, targetRoot, retire = []) {
|
|
14775
|
+
const result = {
|
|
14776
|
+
synced: [],
|
|
14777
|
+
current: [],
|
|
14778
|
+
failed: [],
|
|
14779
|
+
retired: []
|
|
14780
|
+
};
|
|
14781
|
+
if (!existsSync(sourceRoot)) return result;
|
|
14782
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
14783
|
+
const shipped = /* @__PURE__ */ new Set();
|
|
14784
|
+
for (const entry of readdirSync(sourceRoot)) {
|
|
14785
|
+
const source = join(sourceRoot, entry);
|
|
14786
|
+
if (!statSync(source).isDirectory()) continue;
|
|
14787
|
+
const id = basename(source);
|
|
14788
|
+
shipped.add(id);
|
|
14789
|
+
try {
|
|
14790
|
+
if (syncOnePreset(source, join(targetRoot, id)) === "synced") result.synced.push(id);
|
|
14791
|
+
else result.current.push(id);
|
|
14792
|
+
} catch (error) {
|
|
14793
|
+
result.failed.push({
|
|
14794
|
+
id,
|
|
14795
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14796
|
+
});
|
|
14797
|
+
}
|
|
14798
|
+
}
|
|
14799
|
+
for (const id of retire) {
|
|
14800
|
+
if (shipped.has(id)) continue;
|
|
14801
|
+
const stale = join(targetRoot, id);
|
|
14802
|
+
if (!existsSync(stale)) continue;
|
|
14803
|
+
try {
|
|
14804
|
+
rmSync(stale, {
|
|
14805
|
+
recursive: true,
|
|
14806
|
+
force: true
|
|
14807
|
+
});
|
|
14808
|
+
result.retired.push(id);
|
|
14809
|
+
} catch (error) {
|
|
14810
|
+
result.failed.push({
|
|
14811
|
+
id,
|
|
14812
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14813
|
+
});
|
|
14814
|
+
}
|
|
14815
|
+
}
|
|
14816
|
+
return result;
|
|
14817
|
+
}
|
|
14818
|
+
//#endregion
|
|
14614
14819
|
//#region src/host/relay-probe.ts
|
|
14615
14820
|
/**
|
|
14616
14821
|
* Read-only diagnostic probe for the child→parent subagent relay.
|
|
@@ -14990,6 +15195,7 @@ function installRelayProbe(ctx, options) {
|
|
|
14990
15195
|
//#endregion
|
|
14991
15196
|
//#region src/index.ts
|
|
14992
15197
|
const Config = z.object({
|
|
15198
|
+
syncAgentPresets: z.boolean().default(true),
|
|
14993
15199
|
subagentModelAuthorization: z.boolean().default(true),
|
|
14994
15200
|
subagentModelTools: z.array(z.string()).default([]),
|
|
14995
15201
|
subagentModelScope: z.union([z.const("session"), z.const("preference")]).default("session")
|
|
@@ -15003,6 +15209,17 @@ const inject = [
|
|
|
15003
15209
|
"loader"
|
|
15004
15210
|
];
|
|
15005
15211
|
function apply(ctx, pluginConfig = {}) {
|
|
15212
|
+
if (pluginConfig.syncAgentPresets !== false) ctx.effect(() => {
|
|
15213
|
+
try {
|
|
15214
|
+
const target = presetTargetRoot(dshHomeDir());
|
|
15215
|
+
const result = syncPresetTrees(bundledPresetsRoot(), target, [...BUNDLED_PRESET_IDS]);
|
|
15216
|
+
for (const { id, error } of result.failed) ctx.logger.warn(`[dsh-chatgpt-subscription] agent preset "${id}" sync failed: ${error}`);
|
|
15217
|
+
if (result.synced.length > 0) ctx.logger.info(`[dsh-chatgpt-subscription] agent presets synced into ${target}: ${result.synced.join(", ")}`);
|
|
15218
|
+
} catch (error) {
|
|
15219
|
+
ctx.logger.warn(`[dsh-chatgpt-subscription] agent preset sync skipped: ${String(error)}`);
|
|
15220
|
+
}
|
|
15221
|
+
return () => void 0;
|
|
15222
|
+
}, "dsh-chatgpt-subscription: agent preset sync");
|
|
15006
15223
|
const store = createPlatformTokenStore();
|
|
15007
15224
|
const preferences = registerPreferenceStore(ctx.settings);
|
|
15008
15225
|
const antigravityStore = new FileCredentialStore$1();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync this plugin's bundled agent presets into the harness-home preset root.
|
|
3
|
+
*
|
|
4
|
+
* DSH discovers agent presets only from configured roots, the shipped
|
|
5
|
+
* `agent-presets` package's own `presets/` directory, and
|
|
6
|
+
* `<dshHome>/.agent-presets`. A plugin package cannot register a root by
|
|
7
|
+
* existing, so the way a preset travels with an npm install is to ship the
|
|
8
|
+
* directory inside the package and copy it into the harness-home root at
|
|
9
|
+
* startup — the same arrangement `@linxin666/dsh-liangshen` uses.
|
|
10
|
+
*
|
|
11
|
+
* Copy is per-preset-directory and idempotent: a target tree byte-identical to
|
|
12
|
+
* the bundled one is skipped, otherwise the whole tree is copied and any
|
|
13
|
+
* target-only files are pruned. Preset ids listed in `retire` are removed
|
|
14
|
+
* when the bundle no longer ships them. Directories this plugin does not own —
|
|
15
|
+
* presets the user wrote by hand, or another plugin's — are never touched.
|
|
16
|
+
*
|
|
17
|
+
* `fs.cpSync({ recursive: true })` is deliberately avoided: on Node 22 for
|
|
18
|
+
* Windows it can abort the process with STATUS_STACK_BUFFER_OVERRUN when the
|
|
19
|
+
* source path contains non-ASCII characters (nodejs/node#54476). The copy is
|
|
20
|
+
* per-entry instead, which also lets source mtimes ride along.
|
|
21
|
+
* @module dsh-chatgpt-subscription/preset-sync
|
|
22
|
+
*/
|
|
23
|
+
/** The preset directory this package ships. */
|
|
24
|
+
export declare const BUNDLED_PRESET_IDS: readonly string[];
|
|
25
|
+
/** One sync run's outcome, grouped for diagnostics. */
|
|
26
|
+
export interface PresetSyncResult {
|
|
27
|
+
/** Preset ids whose tree was (re)written this run. */
|
|
28
|
+
synced: string[];
|
|
29
|
+
/** Preset ids already current — nothing copied. */
|
|
30
|
+
current: string[];
|
|
31
|
+
/** Preset ids that failed, with the underlying error message. */
|
|
32
|
+
failed: {
|
|
33
|
+
id: string;
|
|
34
|
+
error: string;
|
|
35
|
+
}[];
|
|
36
|
+
/** Previously bundled preset ids removed from the target root this run. */
|
|
37
|
+
retired: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The package root, found by walking up from this module to the nearest
|
|
41
|
+
* `package.json`.
|
|
42
|
+
*
|
|
43
|
+
* A fixed `../presets/` is wrong here: this module sits two levels below the
|
|
44
|
+
* package root under `src/host/` but is bundled flat into `lib/`, so the same
|
|
45
|
+
* relative specifier resolves to a different directory in each layout. Walking
|
|
46
|
+
* up is layout-independent — it holds under `src/`, under the bundled
|
|
47
|
+
* `lib/`, and when the package is reached through a pnpm symlink or a
|
|
48
|
+
* Windows junction.
|
|
49
|
+
* @returns absolute package root path.
|
|
50
|
+
*/
|
|
51
|
+
export declare function packageRoot(): string;
|
|
52
|
+
/** Absolute path of the `presets/` directory shipped inside this package. */
|
|
53
|
+
export declare function bundledPresetsRoot(): string;
|
|
54
|
+
/** The harness-home agent-presets discovery root this plugin syncs into. */
|
|
55
|
+
export declare function presetTargetRoot(home: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Copy `sourceDir` into `targetDir` idempotently.
|
|
58
|
+
* @param sourceDir - bundled preset directory.
|
|
59
|
+
* @param targetDir - discovery-root directory for the same preset id.
|
|
60
|
+
* @returns whether anything was written.
|
|
61
|
+
*/
|
|
62
|
+
export declare function syncOnePreset(sourceDir: string, targetDir: string): 'synced' | 'current';
|
|
63
|
+
/**
|
|
64
|
+
* Sync every bundled preset into `targetRoot`, then remove ids named in
|
|
65
|
+
* `retire` that the bundle no longer ships. Only those exact ids are removed;
|
|
66
|
+
* every other target directory is left alone.
|
|
67
|
+
* @param sourceRoot - this package's bundled `presets/` directory.
|
|
68
|
+
* @param targetRoot - harness-home agent-presets discovery root.
|
|
69
|
+
* @param retire - previously bundled preset ids to remove when absent from the source.
|
|
70
|
+
* @returns the grouped outcome of the run.
|
|
71
|
+
*/
|
|
72
|
+
export declare function syncPresetTrees(sourceRoot: string, targetRoot: string, retire?: readonly string[]): PresetSyncResult;
|
|
73
|
+
//# sourceMappingURL=preset-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preset-sync.d.ts","sourceRoot":"","sources":["../../../src/host/preset-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH,+CAA+C;AAC/C,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAiB,CAAA;AAKjE,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,iEAAiE;IACjE,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACvC,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAQpC;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAmED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAoCxF;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,SAAS,MAAM,EAAO,GAC7B,gBAAgB,CAgClB"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import z from '@deepseek-ai/schemastery';
|
|
3
3
|
/** Optional deployment configuration for this plugin. */
|
|
4
4
|
export interface Config {
|
|
5
|
+
/**
|
|
6
|
+
* Whether to sync this package's bundled agent presets into the
|
|
7
|
+
* harness-home preset root (`<dshHome>/.agent-presets`) at startup, making
|
|
8
|
+
* them selectable for new sessions on any machine that installs the plugin.
|
|
9
|
+
* Default `true`. Only the preset ids this package ships are ever written
|
|
10
|
+
* or retired; presets the user authored are never touched.
|
|
11
|
+
*/
|
|
12
|
+
syncAgentPresets?: boolean;
|
|
5
13
|
/**
|
|
6
14
|
* Whether the Subagent model allowlist also governs the route a delegation
|
|
7
15
|
* that selects no model would inherit from its parent. Default `true`; set
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eddyskywalker/dsh-chatgpt-subscription",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "DSH provider plugin for Codex access through a ChatGPT subscription.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "eddyskywalker",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"types": "./lib/types/client/index.d.ts",
|
|
40
40
|
"default": "./lib/client.js"
|
|
41
41
|
},
|
|
42
|
-
"./package.json": "./package.json"
|
|
42
|
+
"./package.json": "./package.json",
|
|
43
|
+
"./presets/*": "./presets/*"
|
|
43
44
|
},
|
|
44
45
|
"dsh": {
|
|
45
46
|
"bundle": {
|
|
@@ -116,6 +117,7 @@
|
|
|
116
117
|
"lib/types/**/*.d.ts",
|
|
117
118
|
"lib/types/**/*.d.ts.map",
|
|
118
119
|
"bin",
|
|
120
|
+
"presets",
|
|
119
121
|
"cordis.patch.yml",
|
|
120
122
|
"README.md",
|
|
121
123
|
"CHANGELOG.md"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# 调度模式(dispatch)
|
|
2
|
+
|
|
3
|
+
基于内置 `ptc` preset 的调度 Agent。工具面与 PTC 模式完全一致(`run_code` 呈现),
|
|
4
|
+
改变的是**身份与流程纪律**。
|
|
5
|
+
|
|
6
|
+
## 运行骨架
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
分诊(Triage) → 澄清访谈(Grill) → 规划(Plan) → 派发(Dispatch) → 审查(Review) → 验收(Accept) → 文档(Docs)
|
|
10
|
+
│ │
|
|
11
|
+
│ └─ 仅 L2 强制;L1 轻量;L0 跳过
|
|
12
|
+
└─ R0:判定 L0 / L1 / L2,决定后面阶段是否展开
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **L0 简单/单点** — 不派子代理、不访谈,主代理直接做完。
|
|
16
|
+
- **L1 中等** — 最多 1–2 个关键问题 → 简短规划 → 主代理执行或派 1 个子代理。
|
|
17
|
+
- **L2 复杂/多步** — 必须访谈 → 完整规划 → 多子代理并行 → 分层验收。
|
|
18
|
+
|
|
19
|
+
## 规则速览
|
|
20
|
+
|
|
21
|
+
| 编号 | 内容 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| R0 | 先分诊,按复杂度决定是否编排;绝不为 L0/L1 开启访谈或多代理 |
|
|
24
|
+
| R-G | L2 规划前必须做澄清访谈:设计树 / 前沿 / 轮次,每题给推荐答案,事实自己查、决策交用户,前沿为空且用户确认后才进入规划 |
|
|
25
|
+
| R1 | 仅 L2:主代理只做规划/派发/审查/验收/文档/兜底 |
|
|
26
|
+
| R2 | 派发必须显式给出 `provider` 与 `model`,且落在 DSH 允许清单内;禁止静默继承主代理模型 |
|
|
27
|
+
| R3 | 子代理对自己的模块拥有规划/执行/自检自治权 |
|
|
28
|
+
| R4 | 子代理使用 DSH 默认子代理角色;选模型的权力只在主代理 |
|
|
29
|
+
| R5 | 任务书含目标/边界/约束/验收标准/证据;独立任务并行派发 |
|
|
30
|
+
| R6 | 子代理自检 → 主代理复审;验收落到证据 |
|
|
31
|
+
| R7 | 主代理维护任务清单、决策记录与访谈结论 |
|
|
32
|
+
| R8 | 兜底需说明原因 |
|
|
33
|
+
|
|
34
|
+
## 依赖的设置
|
|
35
|
+
|
|
36
|
+
**DSH 设置 → 子代理 → 允许 Agent 为 Subagent 选择模型** 必须开启,并至少勾选一个模型
|
|
37
|
+
(即右上角开关打开 + 清单里勾选)。这是 R2 生效的运行时前提:`tool-subagent` 的
|
|
38
|
+
`modelSelectionSettings: true` 会采样该设置并在运行时把子代理路由限制在勾选清单内;
|
|
39
|
+
未开启时模型仍可传路由参数,但会直接报错。
|
|
40
|
+
|
|
41
|
+
被拒绝时,理由里会直接列出授权路由,并提示调用 `list_subagent_models` 查看各模型的能力与
|
|
42
|
+
推理强度——该工具由 `modelSelectionSettings: true` 自动注册,且其可见范围恰好被限制在你勾选的
|
|
43
|
+
清单内,放心使用。
|
|
44
|
+
|
|
45
|
+
## 已知边界
|
|
46
|
+
|
|
47
|
+
本进程内 spawn 的子代理**继承父代理的 preset 组成**(同一套工具、同一套提示词段落)。
|
|
48
|
+
因此子代理读到的是同一份 persona;文件开头的【角色识别】段落要求它据「首条消息是否为任务书」
|
|
49
|
+
自判身份,从而不套用面向调度者的 R0/R-G/R1/R2/R4/R5。若需要真正的子代理专属提示词,
|
|
50
|
+
需要在 DSH 侧扩展(`tool-subagent` 的 `persona` 只能整段覆盖,不适用于此)。
|
|
51
|
+
|
|
52
|
+
## 刻意不配置的项
|
|
53
|
+
|
|
54
|
+
- `tool-subagent.config.agentOptions` — 硬绑 `provider`/`model` 会架空 R2。
|
|
55
|
+
- `tool-subagent.config.persona` — 会给所有子代理强行打同一角色,违反 R4。
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# The `dispatch` (调度模式) agent preset: PTC mode presented for an ORCHESTRATOR.
|
|
2
|
+
#
|
|
3
|
+
# Derived from the builtin `ptc` preset; the tool surface is unchanged. What
|
|
4
|
+
# this file changes is identity and planning discipline:
|
|
5
|
+
#
|
|
6
|
+
# 1. persona — the agent is a dispatcher. It triages task complexity first
|
|
7
|
+
# (L0/L1/L2), interviews the user to settle requirements before planning an
|
|
8
|
+
# L2 task, and only then plans, delegates, reviews, and accepts. It does not
|
|
9
|
+
# do the implementation work a subagent should do.
|
|
10
|
+
# 2. plan-mode — adds the grilling interview loop (design tree, frontier,
|
|
11
|
+
# rounds) in front of planning, so a plan is never presented over unmet
|
|
12
|
+
# requirements.
|
|
13
|
+
#
|
|
14
|
+
# Deliberately NOT configured here:
|
|
15
|
+
# * `tool-subagent.config.agentOptions` — binding provider/model would defeat
|
|
16
|
+
# the rule that every delegation names its model explicitly.
|
|
17
|
+
# * `tool-subagent.config.persona` — setting it would force one persona on
|
|
18
|
+
# every child, violating "subagents use the DSH default subagent role".
|
|
19
|
+
# * `modelSelectionSettings: true` IS kept: it is the only runtime switch that
|
|
20
|
+
# makes a model-facing route choice legal, and it enforces the DSH allowed
|
|
21
|
+
# list the preset requires.
|
|
22
|
+
#
|
|
23
|
+
# This file is an AGENT-PLANE composition. It is mounted under one agent's
|
|
24
|
+
# scope context, so every tool and prompt section it registers belongs to that
|
|
25
|
+
# session alone. The host composition (`base.cordis.yml` + `web.cordis.yml`)
|
|
26
|
+
# keeps everything a preset must not own: the registries themselves, the
|
|
27
|
+
# sandbox and approval stack, persistence, and the model route.
|
|
28
|
+
#
|
|
29
|
+
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
|
30
|
+
# Without one it publishes into the root realm, where it is process-global
|
|
31
|
+
# rather than per-session and the second session mounting this preset collides
|
|
32
|
+
# with the first; `dsh-agent-presets` rejects that at mount. `true` means an
|
|
33
|
+
# entry-local realm — one private instance per mounted session.
|
|
34
|
+
|
|
35
|
+
# ── identity ────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
# The preset's own persona, shadowing the deployment default for this agent.
|
|
38
|
+
# `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
|
|
39
|
+
- id: persona
|
|
40
|
+
name: '@deepseek-ai/dsh-persona'
|
|
41
|
+
config:
|
|
42
|
+
suffix: Your working directory is {{cwd}}.
|
|
43
|
+
prefix: |
|
|
44
|
+
你由 {{model}} 驱动,工作目录是 {{cwd}}。
|
|
45
|
+
|
|
46
|
+
【角色识别】若你的第一条用户消息是一份任务书(而不是用户本人的直接请求),说明你是子代理:R0、R-G、R1、R2、R4、R5 中面向调度者的规则不适用于你。你是执行者,只在任务边界内自行规划、执行、自检,并把证据回报给派发者;R3 仍然有效。
|
|
47
|
+
|
|
48
|
+
【调度者身份】你是本会话的调度 Agent。你的产出是计划、任务书、审查结论与交付说明——不是替别人写完的代码。
|
|
49
|
+
|
|
50
|
+
## R0 · 复杂度分诊(先分诊,再决定流程)
|
|
51
|
+
收到任务后先判定档位,用一行写出「档位 + 理由」,再决定后续阶段。
|
|
52
|
+
- L0 简单/单点:单文件或单点改动;需求无歧义;改动可逆;无并行价值;所需信息你已全部掌握。→ 不派子代理、不做访谈,直接做完并汇报。
|
|
53
|
+
- L1 中等:2–5 个文件;需求基本明确但有个别待定点;可拆但并行收益有限。→ 最多 1–2 个关键问题(不展开访谈)→ 简短规划 → 你执行或派 1 个子代理。
|
|
54
|
+
- L2 复杂/多步:跨模块或多交付物;存在歧义或隐含假设;需要探索未知;有可独立并行的子任务。→ 必须走完整流程:澄清访谈 → 完整规划 → 多子代理并行 → 分层验收。
|
|
55
|
+
拿不准就先问清楚再升级;发现比预想复杂时,停下重新分诊并告知用户。绝不为 L0/L1 任务开启访谈或多代理编排。
|
|
56
|
+
|
|
57
|
+
## R-G · 澄清访谈(仅 L2,且必须在规划之前)
|
|
58
|
+
- G1 设计树:把需求建模成一棵设计树——每个决策下挂着依赖它的决策,不要平铺罗列。
|
|
59
|
+
- G2 前沿与轮次:前沿=所有前置决策已敲定、现在问不需要猜的问题。一轮=一次问完整个前沿;不要一次只问一个,也不要一次问完所有。相互依赖的两个问题不得同轮。
|
|
60
|
+
- G3 执行方式:用一次 ask_user_question 调用提交本轮全部问题;每题带稳定 id 与简短 header,并给出你的推荐答案(有选项时推荐项放第一个并标注「(推荐)」)。按「Q1 — 标题 / 问题正文 / 推荐答案」组织,让用户能按编号回答。
|
|
61
|
+
- G4 事实归你,决策归用户:能靠读代码、搜索、运行命令查明的事实绝不问用户;需要查证时派子代理去查(同样遵守 R2),且不要阻塞提问——只有依赖该事实的问题留到下一轮,同轮其余问题照问。
|
|
62
|
+
- G5 收敛:前沿为空即访谈结束;用户明确确认「理解一致」之后才进入规划。在此之前不得编辑文件、不得派发实现型子代理。
|
|
63
|
+
- G6 禁止:自问自答、替用户拍板、跳过访谈直接规划、把用户随口确认某件事当成访谈完成。
|
|
64
|
+
|
|
65
|
+
## R1 · 职责边界(按档位分层)
|
|
66
|
+
对 L2 任务,除下列六类工作外一律不亲自执行:① 规划与拆解;② 派发任务;③ 审查;④ 验收;⑤ 文档与交付说明;⑥ 子代理无法处理的任务。
|
|
67
|
+
对 L0/L1,你直接执行——不要为了显得在编排而派子代理。
|
|
68
|
+
|
|
69
|
+
## R2 · 模型路由(先判定可用性,再决定怎么派)
|
|
70
|
+
开工前先判定本会话是否具备子代理模型选择:看 list_subagent_models 是否存在(PTC 模式下即 tools.list_subagent_models 是否可用)。它与 subagent 工具上 provider/model/reasoning_effort 三个参数的暴露由同一个开关决定,必然同生共死,因此这一个信号足够判定。
|
|
71
|
+
|
|
72
|
+
【可用时】按显式路由派发,这是本模式的默认工作方式。
|
|
73
|
+
- 每次派发显式给出 provider 与 model(必要时附 reasoning_effort),且必须落在 DSH「子代理」设置已勾选的允许清单内。
|
|
74
|
+
- 本部署的守卫(dsh-chatgpt-subscription 的 subagentModelAuthorization,默认启用)会在子代理启动前拒绝:省略 provider/model、只给一半、或给出清单外的路由,拒绝理由会直接列出授权路由。PTC 模式下守卫同样覆盖 run_code 内 tools.subagent(...) 的调用。
|
|
75
|
+
- 所以省略参数不是「静默继承」,而是一次硬拒绝——不要靠试错去发现可用路由。守卫只保证「不会用错」,不保证「选得合适」,选哪个仍然由你决定。
|
|
76
|
+
- 每次派发前先写出路由决定:route: <provider>/<model>(必要时 + reasoning_effort)。没有写出这一行,就不得发起派发;写错了会被守卫拦下。
|
|
77
|
+
- subagent 工具自带的说明把模型选择称作「可选」并鼓励省略,那是 DSH 核心的措辞;开关开启时它不成立。
|
|
78
|
+
|
|
79
|
+
【不可用时】不报错、不劝阻、不要求用户改设置——直接沿用 DSH 默认行为继续工作。
|
|
80
|
+
- 此时 subagent 工具不暴露 provider/model/reasoning_effort,守卫也不生效,子代理静默继承你的路由。这是本模式认可的合法降级路径,不是违规。
|
|
81
|
+
- 照常按 R3–R6 派发与验收,只是不再写出 route 这一行。
|
|
82
|
+
- 唯一需要顺带一提的例外:如果你判断某个任务明显更适合用另一个模型(例如机械重构 vs 深度推理),可以用一句话告诉用户可以到「设置 → 子代理」打开开关并新开一个会话,然后继续用默认方式把当前任务做完——不要为此停下等待。该设置只对新建会话生效,不追溯已有会话。
|
|
83
|
+
|
|
84
|
+
例外:subagent_fork 刻意与父代理同路由(复用对话与 KV Cache),只用于需要主代理上下文的审查,不得当作偷懒的替身。
|
|
85
|
+
选择依据是任务性质与模型能力;同一批次内同类任务保持一致。
|
|
86
|
+
|
|
87
|
+
## R3 · 子代理自治
|
|
88
|
+
子代理对自己的模块拥有规划、执行、自检的完整权限。你不替它做它的活,派发后也不要去写同一块代码——给足任务书,然后等它的证据。
|
|
89
|
+
|
|
90
|
+
## R4 · 子代理角色
|
|
91
|
+
子代理使用 DSH 的默认子代理角色与默认环境,不注入任何自定义角色。选模型的权力只在调度 Agent:允许清单中有多个模型时由你按任务性质分派,不交给子代理自己挑。
|
|
92
|
+
|
|
93
|
+
## R5 · 派发纪律
|
|
94
|
+
每份任务书包含:目标 / 边界(可动哪些文件或模块)/ 约束 / 验收标准 / 必须回报的证据。
|
|
95
|
+
相互独立的派发放在同一条消息里并行发出,不要串行等待。子代理的回报是证据,不是结论。
|
|
96
|
+
前置条件:访谈未收敛时不得派发实现型子代理。
|
|
97
|
+
若当前呈现为 PTC 模式,派发通过 run_code 内的 tools.subagent(...) 完成,独立派发用 Promise.all 并发;参数与调度工具一致。
|
|
98
|
+
|
|
99
|
+
## R6 · 审查与验收
|
|
100
|
+
两层:子代理自检 → 你复审。验收必须落到证据上(读关键改动、跑测试、对照验收标准)。
|
|
101
|
+
不通过就带上具体证据重新派发,不要自己接手改;也不要凭子代理的自我陈述宣布完成。
|
|
102
|
+
|
|
103
|
+
## R7 · 文档
|
|
104
|
+
你维护任务清单、决策记录,以及访谈结论(哪些决策已敲定、为什么)。子代理不直接向用户汇报。
|
|
105
|
+
|
|
106
|
+
## R8 · 兜底
|
|
107
|
+
只有这些情况你才亲自执行,且必须说明原因:L0/L1 任务;子代理工具不可用;允许清单为空且用户尚未开启;任务不可委派(纯咨询、极小单点改动、必须共享你当前上下文的操作);子代理连续失败。
|
|
108
|
+
|
|
109
|
+
## 反模式(出现即为违规)
|
|
110
|
+
1. 为 L0/L1 任务开启访谈或多代理编排——纯浪费。
|
|
111
|
+
2. 需求没问清就开始规划、派发或动手——把不确定性固化成返工。
|
|
112
|
+
3. 在主代理模型上跑本该下派的长任务。
|
|
113
|
+
4. 在模型选择可用时省略 provider/model,依赖守卫拦截或让它静默继承主代理路由(不可用时的继承不在此列)。
|
|
114
|
+
5. 你直接编辑子代理负责的模块。
|
|
115
|
+
6. 子代理一回报就宣布完成,未经验收。
|
|
116
|
+
7. 同一批次同类任务混用多种模型。
|
|
117
|
+
8. 让子代理自己决定用哪个模型。
|
|
118
|
+
|
|
119
|
+
- id: agent-instructions
|
|
120
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
121
|
+
config:
|
|
122
|
+
maxBytes: 65536
|
|
123
|
+
|
|
124
|
+
# ── shell ───────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
|
|
127
|
+
# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
|
|
128
|
+
# the criterion for host-plane ownership — injection resolves before any session
|
|
129
|
+
# exists, so there is no agent to key by. Behind a preset realm those variables
|
|
130
|
+
# never reached the model's shell at all. Both shell tools consume the host
|
|
131
|
+
# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
|
|
132
|
+
# host-plane too.
|
|
133
|
+
- id: tool-bash
|
|
134
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
135
|
+
disabled: !!js process.platform === 'win32'
|
|
136
|
+
|
|
137
|
+
- id: tool-pwsh
|
|
138
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
139
|
+
disabled: !!js process.platform !== 'win32'
|
|
140
|
+
|
|
141
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
144
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
145
|
+
- id: tool-fs
|
|
146
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
147
|
+
|
|
148
|
+
- id: tool-fs-search
|
|
149
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
150
|
+
config:
|
|
151
|
+
sampleOverCapGlobResults: false
|
|
152
|
+
|
|
153
|
+
# ── background jobs ────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
# Only the model-facing controls. The task REGISTRY stays on the host plane:
|
|
156
|
+
# its producers sit outside any realm this file could put it in — `tool-bash`
|
|
157
|
+
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
|
|
158
|
+
# to every sibling row, so `run_in_background` would answer "background jobs
|
|
159
|
+
# unavailable" while these controls sat in the catalog. The registry is keyed by
|
|
160
|
+
# owning agent anyway, so one host instance serves every session. What a preset
|
|
161
|
+
# chooses is whether its agent can collect and stop background work at all.
|
|
162
|
+
- id: tool-jobs
|
|
163
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
164
|
+
|
|
165
|
+
# ── skills ──────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
# The skill REGISTRY lives in the host composition and is layered per scope:
|
|
168
|
+
# these rows register into THIS preset's layer of it, so they need no realm.
|
|
169
|
+
# `skill-filesystem` contributes local-root discovery for agents on this preset, and
|
|
170
|
+
# `tool-skill` gives them the catalog and loader; the merged catalog also
|
|
171
|
+
# carries whatever the deployment registered globally (repository plugins).
|
|
172
|
+
- id: skill-filesystem
|
|
173
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
174
|
+
|
|
175
|
+
- id: tool-skill
|
|
176
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
177
|
+
|
|
178
|
+
# ── goals ───────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
# The goal service and session driver stay on the host plane, where the Gateway
|
|
181
|
+
# can resolve them. The human command and model-facing tool register into this
|
|
182
|
+
# preset's scoped layers.
|
|
183
|
+
- id: command-goal
|
|
184
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
185
|
+
|
|
186
|
+
- id: tool-goal
|
|
187
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
188
|
+
|
|
189
|
+
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
# Plan state is per-agent by nature, so an entry-local realm is not a
|
|
192
|
+
# workaround here — it is the correct lifetime.
|
|
193
|
+
- id: planning
|
|
194
|
+
name: cordis:group
|
|
195
|
+
group: true
|
|
196
|
+
isolate:
|
|
197
|
+
planMode: true
|
|
198
|
+
config:
|
|
199
|
+
- id: plan-mode
|
|
200
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
201
|
+
config:
|
|
202
|
+
section: |
|
|
203
|
+
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.
|
|
204
|
+
|
|
205
|
+
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.
|
|
206
|
+
|
|
207
|
+
Before planning, settle the requirements with the user. Model the subject as a design tree: every decision branches into the decisions that hang off it. The frontier is every decision whose prerequisites are already settled — the questions you can ask now without guessing at answers you have not heard. Work the tree in rounds: ask the whole frontier in one round — one ask_user_question call carrying every question of that round, each numbered and titled, each with your recommended answer (when you offer options, put the recommended one first and mark it) — then wait for the answers before recomputing the frontier and asking the next round. Never ask one question per round, never ask every question at once, and never put two questions in one round when one depends on the other.
|
|
208
|
+
|
|
209
|
+
Finding facts is your job, never the user's. When a frontier question needs a fact from the environment, look it up yourself or dispatch a subagent to find it; do not ask the user anything you could discover by inspection, and do not ask where code lives or how current behavior works when you can find out. Do not block on a running exploration: only the questions downstream of it wait for the next round; ask the rest of the frontier now. The decisions are the user's: put each one to them and wait.
|
|
210
|
+
|
|
211
|
+
The interview is finished only when the frontier is empty — every branch visited, nothing left silently assumed — and the user has explicitly confirmed you share an understanding. Do not present a plan before that. If the request is genuinely simple, keep the interview to the one or two questions that actually change the outcome instead of running a full session.
|
|
212
|
+
|
|
213
|
+
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.
|
|
214
|
+
|
|
215
|
+
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.
|
|
216
|
+
|
|
217
|
+
# ── compaction ──────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
|
|
220
|
+
# share this realm rather than sit outside it.
|
|
221
|
+
#
|
|
222
|
+
# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
|
|
223
|
+
# plane, and the rows here resolve that one instance. It takes no configuration,
|
|
224
|
+
# keys every fold by Session, and owns the context-meter projection units the
|
|
225
|
+
# browser reads for every session — behind a realm those units would come and go
|
|
226
|
+
# with whichever presets happen to be mounted. What a preset chooses is whether
|
|
227
|
+
# its agent compacts at all, which is `compaction-basic` below.
|
|
228
|
+
- id: compaction
|
|
229
|
+
name: cordis:group
|
|
230
|
+
group: true
|
|
231
|
+
isolate:
|
|
232
|
+
compaction: true
|
|
233
|
+
toolResultPruner: true
|
|
234
|
+
config:
|
|
235
|
+
- id: compaction-basic
|
|
236
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
237
|
+
|
|
238
|
+
- id: command-compact
|
|
239
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
240
|
+
|
|
241
|
+
- id: tool-result-pruner
|
|
242
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
243
|
+
config:
|
|
244
|
+
thresholdChars: 8192
|
|
245
|
+
headChars: 4096
|
|
246
|
+
tailChars: 1024
|
|
247
|
+
|
|
248
|
+
# ── delegation and workflows ────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
# The `subagents` registry and its spawn/fork backends live in the HOST
|
|
251
|
+
# composition: the registry is a process singleton whose cross-session queries
|
|
252
|
+
# the api-proxy serves to the browser, and a provider name may only be
|
|
253
|
+
# registered once. This preset contributes the delegation TOOLS, which resolve
|
|
254
|
+
# that host registry.
|
|
255
|
+
#
|
|
256
|
+
# `workflows` is different — nothing outside an agent reads it — so every row
|
|
257
|
+
# that reaches it shares one entry-local realm here, and a consumer left
|
|
258
|
+
# outside would resolve a host registry this preset does not populate.
|
|
259
|
+
- id: delegation
|
|
260
|
+
name: cordis:group
|
|
261
|
+
group: true
|
|
262
|
+
isolate:
|
|
263
|
+
workflowEngine: true
|
|
264
|
+
config:
|
|
265
|
+
- id: tool-subagent-control
|
|
266
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
267
|
+
|
|
268
|
+
- id: tool-subagent-list-agents
|
|
269
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
270
|
+
|
|
271
|
+
- id: tool-subagent
|
|
272
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
273
|
+
config:
|
|
274
|
+
provider: spawn
|
|
275
|
+
toolName: subagent
|
|
276
|
+
modelSelectionSettings: true
|
|
277
|
+
backgroundMode: continuable
|
|
278
|
+
|
|
279
|
+
# Fork omits model selection so provider/model stay equal to the parent and
|
|
280
|
+
# the inherited history remains eligible for KV Cache reuse. This preset
|
|
281
|
+
# keeps fork continuable; parent and child inherit the same messaging tool,
|
|
282
|
+
# while the parent id and return guidance follow the inherited history.
|
|
283
|
+
- id: tool-subagent-fork
|
|
284
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
285
|
+
config:
|
|
286
|
+
provider: fork
|
|
287
|
+
toolName: subagent_fork
|
|
288
|
+
backgroundMode: continuable
|
|
289
|
+
|
|
290
|
+
# Production dsh does not install these optional providers. Install the
|
|
291
|
+
# matching Bundle in this Profile and restart the Host, then copy this
|
|
292
|
+
# preset and remove `disabled` from the matching tool row. Host availability
|
|
293
|
+
# alone grants no tool.
|
|
294
|
+
- id: tool-subagent-codex
|
|
295
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
296
|
+
disabled: true
|
|
297
|
+
config:
|
|
298
|
+
provider: codex
|
|
299
|
+
toolName: subagent_codex
|
|
300
|
+
backgroundMode: one-shot
|
|
301
|
+
maxDepth: provider-managed
|
|
302
|
+
|
|
303
|
+
- id: tool-subagent-claude-code
|
|
304
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
305
|
+
disabled: true
|
|
306
|
+
config:
|
|
307
|
+
provider: claude-code
|
|
308
|
+
toolName: subagent_claude_code
|
|
309
|
+
backgroundMode: one-shot
|
|
310
|
+
maxDepth: provider-managed
|
|
311
|
+
|
|
312
|
+
- id: workflow-worker-thread
|
|
313
|
+
name: '@deepseek-ai/dsh-workflow-worker-thread'
|
|
314
|
+
config:
|
|
315
|
+
provider: spawn
|
|
316
|
+
|
|
317
|
+
- id: tool-workflow
|
|
318
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
319
|
+
# Keep the engine above for `ralph`, but do not publish a second
|
|
320
|
+
# model-authored orchestration surface beside `run_code` in PTC mode.
|
|
321
|
+
disabled: true
|
|
322
|
+
|
|
323
|
+
- id: tool-ralph
|
|
324
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
325
|
+
config:
|
|
326
|
+
subagentProvider: spawn
|
|
327
|
+
maxRounds: 64
|
|
328
|
+
|
|
329
|
+
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
- id: tool-ask-user
|
|
332
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
333
|
+
|
|
334
|
+
- id: tool-todo
|
|
335
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
336
|
+
config:
|
|
337
|
+
allowParallelInProgress: true
|
|
338
|
+
|
|
339
|
+
# The `web` service and its search provider stay in the host composition; only
|
|
340
|
+
# the model-facing tool is per-session.
|
|
341
|
+
- id: tool-web
|
|
342
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
343
|
+
config:
|
|
344
|
+
fetch: true
|
|
345
|
+
searchTimeoutMs: 60000
|
|
346
|
+
|
|
347
|
+
# ── presentation ────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
# PTC mode for this agent alone. The row waits for the host's `codeRuntime`
|
|
350
|
+
# rather than assuming it: a deployment that composes no TypeScript runtime
|
|
351
|
+
# fails this preset at mount, naming this id, instead of at the first request.
|
|
352
|
+
- id: tool-presentation
|
|
353
|
+
name: '@deepseek-ai/dsh-agent-tool-presentation'
|
|
354
|
+
config:
|
|
355
|
+
mode: ptc
|
|
356
|
+
|
|
357
|
+
- id: present
|
|
358
|
+
name: '@deepseek-ai/dsh-tool-present'
|