@carljia/omd-dsh 0.1.7 → 0.1.8
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/README.md +14 -11
- package/lib/boot.d.ts +4 -4
- package/lib/boot.js +6 -11
- package/lib/cli.js +9 -21
- package/lib/index.js +4 -5
- package/lib/mode.js +1 -4
- package/lib/plan.js +1 -4
- package/lib/startwork.js +1 -4
- package/lib/sync.d.ts +13 -22
- package/lib/sync.js +9 -213
- package/lib/task.js +1 -4
- package/lib/vendor/omd-mode-switch.mjs +967 -127
- package/lib/vendor/omd-mode.mjs +889 -174
- package/lib/vendor/omd-plan.mjs +95 -177
- package/lib/vendor/omd-start-work.mjs +96 -132
- package/lib/vendor/omd-task.mjs +20735 -265
- package/lib/vendor/omd-ulw.mjs +44 -49
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@ OMD 理念的 DSH 插件:两个 cordis 行 + 7 个模式 preset + 同步 CLI
|
|
|
4
4
|
|
|
5
5
|
## 分发:bundle + boot 行
|
|
6
6
|
|
|
7
|
-
包声明 `dsh.bundle.patch` → `cordis.patch.yml`,后者注入宿主行 `@carljia/omd-dsh/boot`。该行在 profile 启动时执行与 `omd-dsh sync` 相同的内核(`lib/sync.js
|
|
7
|
+
包声明 `dsh.bundle.patch` → `cordis.patch.yml`,后者注入宿主行 `@carljia/omd-dsh/boot`。该行在 profile 启动时执行与 `omd-dsh sync` 相同的内核(`lib/sync.js`):渲染并复制 7 个 preset 与 `.omd-vendor/` 行模块、首次生成 `omd-matrix.json`。全程幂等、hash 保护、不覆盖本地修改。
|
|
8
|
+
|
|
9
|
+
`.omd-vendor/` 里的行模块是**自包含 bundle**(`scripts/postbuild.mjs` 用 esbuild 把 schemastery / dsh-tools / dsh-llm / dsh-subagent 全部打进模块,唯一的例外是 `shared.js`——omd-mode 与 omd-task 共享的按 agent 键控状态,作为相对兄弟模块保持单一实例)。行模块不含任何 `@deepseek-ai/*` 导入,因此 sync **不需要知道 harness 装在哪里**,也不会出现"导入指向的树与运行进程不一致"导致的挂载失败——无论 DSH 从 npx 缓存、全局 npm 还是 profile bundles 加载 agent 机制都能工作。preset 组合里其余裸包名行(如 `@deepseek-ai/dsh-persona`)由 harness 的 loader 在运行时按宿主 base 解析。
|
|
8
10
|
|
|
9
11
|
## 行:omd-mode
|
|
10
12
|
|
|
11
|
-
按模式(agent preset
|
|
13
|
+
按模式(agent preset)固定模型路由。行模块是自包含 bundle,不再做 scope 守卫(bundle 内自带的 dsh-scope 副本读不到 harness 实例写入的 kScope Symbol,守卫会误报——见 docs/ARCHITECTURE.md);请按 preset 组合挂载,误挂到全局组合会导致进程级钉模型。
|
|
12
14
|
|
|
13
15
|
| 配置字段 | 含义 |
|
|
14
16
|
|---|---|
|
|
@@ -24,7 +26,7 @@ OMD 理念的 DSH 插件:两个 cordis 行 + 7 个模式 preset + 同步 CLI
|
|
|
24
26
|
1. 入口选择 == 矩阵模型 → 钉住矩阵模型(无操作);
|
|
25
27
|
2. 会话尚无任何请求(blank)且入口选择 == 挂载时的部署默认模型 → 视为未选择,钉住矩阵模型;
|
|
26
28
|
3. 会话已跑过请求且刚发生 preset 切换(/mode 或 UI 选择,日志中 agent-preset/selected 在最后一次 request/header 之后)且入口选择 == 切换前的路由 → 新模式认领矩阵模型;
|
|
27
|
-
4. 其余情况 → 入口选择与矩阵模型不同 = 用户显式切换:请求与 persona
|
|
29
|
+
4. 其余情况 → 入口选择与矩阵模型不同 = 用户显式切换:请求与 persona 变量均保持用户选择,本行在共享状态 `shared.js`(按顶层 agent 对象为键的 WeakMap)上记录用户选择,omd-task 行据此把 deep tier 路由到用户选择的模型。
|
|
28
30
|
|
|
29
31
|
模型体验:persona 文本每次请求固定携带实际路由模型的声明;模式模型路由在 agent 生命周期内稳定(KV 前缀稳定)。
|
|
30
32
|
|
|
@@ -43,19 +45,19 @@ tier 差异化子代理委派(等价 OMD 的 task(category=…))。仅限 pr
|
|
|
43
45
|
|
|
44
46
|
模型可见参数:description(展示名)、prompt、tier(枚举见工具描述)、run_in_background(仅 continuable 模式)。工具描述枚举各 tier 的 hint 与模型,模型据此选型。
|
|
45
47
|
|
|
46
|
-
**deep tier 与用户模型覆盖**:用户在 UI 显式切换模型后(omd-mode
|
|
48
|
+
**deep tier 与用户模型覆盖**:用户在 UI 显式切换模型后(omd-mode 让路并在 `shared.js` 里记录用户选择),名为 `deep` 的 tier 改用用户选择的模型(provider/model 整体替换),其余 tier 保持矩阵配置。
|
|
47
49
|
|
|
48
50
|
## CLI:omd-dsh
|
|
49
51
|
|
|
50
52
|
```
|
|
51
|
-
omd-dsh <command> [--
|
|
53
|
+
omd-dsh <command> [--dry-run]
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
- `omd-dsh sync` — 把 presets/ 与 vendored 行模块同步到 `<DSH_HOME>/.agent-presets/`(hash 保护、orphan 报告、非管理目录零操作)。每个 preset 的 omd-mode / omd-task 行由用户矩阵 `<DSH_HOME>/omd-matrix.json`
|
|
56
|
+
- `omd-dsh sync` — 把 presets/ 与 vendored 行模块同步到 `<DSH_HOME>/.agent-presets/`(hash 保护、orphan 报告、非管理目录零操作)。每个 preset 的 omd-mode / omd-task 行由用户矩阵 `<DSH_HOME>/omd-matrix.json` 渲染。不需要任何 harness 路径配置。
|
|
55
57
|
- `omd-dsh setup` — 交互式向导:先读取 DSH 已有模型,再引导逐模式/逐 tier 选择模型,写回 `<DSH_HOME>/omd-matrix.json` 并可选立即同步。
|
|
56
58
|
- `omd-dsh models` — 打印发现的 DSH 模型目录(非交互)。
|
|
57
59
|
|
|
58
|
-
细节见 docs/ARCHITECTURE.md 的「vendored
|
|
60
|
+
细节见 docs/ARCHITECTURE.md 的「vendored 分发与自包含 bundle」。
|
|
59
61
|
|
|
60
62
|
## 集中配置:<DSH_HOME>/omd-matrix.json
|
|
61
63
|
|
|
@@ -64,19 +66,20 @@ omd-dsh <command> [--harness <路径>] [--dry-run]
|
|
|
64
66
|
## 开发
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
|
-
npm install # 触发 build(tsc +
|
|
68
|
-
npm test # vitest
|
|
69
|
+
npm install # 触发 build(tsc + esbuild 打包自包含 bundle)
|
|
70
|
+
npm test # vitest 全量测试
|
|
69
71
|
npm pack # prepack 自动重建,产出 tgz
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
测试桩在 test/stubs/(dsh-
|
|
74
|
+
测试桩在 test/stubs/(dsh-tools/dsh-subagent 的轻量替身),不依赖真实 harness。
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
### 子代理透传语义
|
|
76
78
|
|
|
77
79
|
omd-mode 对 subagentDepth > 0 的子代理一律透传(不覆盖 provider/model/变量):
|
|
78
80
|
omd-task 的 tier 模型通过显式 agentOptions 生效;顶层 agent 才被钉到模式模型
|
|
79
|
-
(除非用户显式切换模型——此时顶层跟随用户选择,deep tier 通过 `
|
|
81
|
+
(除非用户显式切换模型——此时顶层跟随用户选择,deep tier 通过 `shared.js` 里
|
|
82
|
+
按 agent 记录的覆盖值同步)。
|
|
80
83
|
这保证「强模型顶层 + tier 差异化子代理」的优先级正确。
|
|
81
84
|
|
|
82
85
|
### toolFilter 注意
|
package/lib/boot.d.ts
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* omd-dsh bundle boot row: materialize the 7 OMD agent presets into the user
|
|
5
5
|
* preset root (<DSH_HOME>/.agent-presets) when the profile boots, so
|
|
6
6
|
* "dsh plugin add @carljia/omd-dsh" + a restart is the whole install — no
|
|
7
|
-
* manual "omd-dsh sync" needed.
|
|
7
|
+
* manual "omd-dsh sync" and no harness configuration needed.
|
|
8
8
|
*
|
|
9
9
|
* The sync is idempotent (hash-aware, conflict-protected) and reuses the same
|
|
10
|
-
* core as the "omd-dsh sync" CLI. The
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* core as the "omd-dsh sync" CLI. The vendored rows are self-contained
|
|
11
|
+
* bundles (see scripts/postbuild.mjs), so the sync needs no harness tree
|
|
12
|
+
* knowledge and cannot break on harness/profile layout differences.
|
|
13
13
|
*
|
|
14
14
|
* A sync failure is logged, not thrown: a broken omd-dsh should never brick
|
|
15
15
|
* the host's boot.
|
package/lib/boot.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { runSync
|
|
1
|
+
import { runSync } from "./sync.js";
|
|
2
2
|
/**
|
|
3
3
|
* @module @carljia/omd-dsh/boot
|
|
4
4
|
*
|
|
5
5
|
* omd-dsh bundle boot row: materialize the 7 OMD agent presets into the user
|
|
6
6
|
* preset root (<DSH_HOME>/.agent-presets) when the profile boots, so
|
|
7
7
|
* "dsh plugin add @carljia/omd-dsh" + a restart is the whole install — no
|
|
8
|
-
* manual "omd-dsh sync" needed.
|
|
8
|
+
* manual "omd-dsh sync" and no harness configuration needed.
|
|
9
9
|
*
|
|
10
10
|
* The sync is idempotent (hash-aware, conflict-protected) and reuses the same
|
|
11
|
-
* core as the "omd-dsh sync" CLI. The
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* core as the "omd-dsh sync" CLI. The vendored rows are self-contained
|
|
12
|
+
* bundles (see scripts/postbuild.mjs), so the sync needs no harness tree
|
|
13
|
+
* knowledge and cannot break on harness/profile layout differences.
|
|
14
14
|
*
|
|
15
15
|
* A sync failure is logged, not thrown: a broken omd-dsh should never brick
|
|
16
16
|
* the host's boot.
|
|
@@ -32,12 +32,7 @@ function log(ctx, level, message) {
|
|
|
32
32
|
}
|
|
33
33
|
export async function apply(ctx) {
|
|
34
34
|
try {
|
|
35
|
-
|
|
36
|
-
if (harness === undefined) {
|
|
37
|
-
log(ctx, "error", "omd-dsh: cannot locate the DSH harness node_modules — presets were not synced. Run `omd-dsh sync --harness <path>` once to point it at the harness, then restart.");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
await runSync({ harness: undefined, dryRun: false, verbose: false }, harness, (message) => log(ctx, "info", message));
|
|
35
|
+
await runSync({ dryRun: false, verbose: false }, (message) => log(ctx, "info", message));
|
|
41
36
|
}
|
|
42
37
|
catch (error) {
|
|
43
38
|
log(ctx, "error", "omd-dsh: preset sync failed: " + (error instanceof Error ? error.message : String(error)));
|
package/lib/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
-
import { runSync,
|
|
5
|
+
import { runSync, loadMatrix, saveMatrix, dshHome, MATRIX_PATH, } from "./sync.js";
|
|
6
6
|
/**
|
|
7
7
|
* omd-dsh CLI
|
|
8
8
|
*
|
|
@@ -15,7 +15,9 @@ import { runSync, resolveHarness, loadMatrix, saveMatrix, dshHome, MATRIX_PATH,
|
|
|
15
15
|
*
|
|
16
16
|
* The sync core lives in ./sync.js and is also invoked automatically by the
|
|
17
17
|
* bundle boot row (lib/boot.js), so `dsh plugin add @carljia/omd-dsh` + restart
|
|
18
|
-
* installs the presets without this CLI.
|
|
18
|
+
* installs the presets without this CLI. The vendored rows are self-contained
|
|
19
|
+
* bundles: no `--harness` flag exists because the sync needs no harness tree
|
|
20
|
+
* knowledge.
|
|
19
21
|
*/
|
|
20
22
|
function usage() {
|
|
21
23
|
return [
|
|
@@ -25,7 +27,6 @@ function usage() {
|
|
|
25
27
|
" setup interactive: discover DSH models, then guide per-mode/tier model selection",
|
|
26
28
|
" models print the discovered DSH model catalog",
|
|
27
29
|
" options:",
|
|
28
|
-
" --harness <path> node_modules dir of the DSH harness install (saved locally after first use; otherwise auto-detected)",
|
|
29
30
|
" --dry-run (sync) print planned writes without touching the filesystem",
|
|
30
31
|
" --verbose (sync) print per-file detail",
|
|
31
32
|
].join("\n");
|
|
@@ -73,7 +74,7 @@ function splitModel(answer) {
|
|
|
73
74
|
}
|
|
74
75
|
return { provider: "deepseek-official", model: a };
|
|
75
76
|
}
|
|
76
|
-
async function runSetup(flags
|
|
77
|
+
async function runSetup(flags) {
|
|
77
78
|
const matrix = loadMatrix(flags);
|
|
78
79
|
const { models, currentDefault } = discoverModels();
|
|
79
80
|
console.log("omd-dsh setup: 发现 DSH 已有模型:");
|
|
@@ -112,12 +113,7 @@ async function runSetup(flags, harnessNodeModules) {
|
|
|
112
113
|
const syncNow = await ask("是否立即同步到 " + join(dshHome(), ".agent-presets") + "? [Y/n] ");
|
|
113
114
|
rl.close();
|
|
114
115
|
if (syncNow === "" || /^y/i.test(syncNow)) {
|
|
115
|
-
|
|
116
|
-
console.error("omd-dsh setup: 无法定位 DSH harness node_modules,跳过同步。首次请运行 `omd-dsh sync --harness <路径>`(之后会自动缓存,无需重复指定)。");
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
await runSync(flags, harnessNodeModules);
|
|
120
|
-
}
|
|
116
|
+
await runSync(flags);
|
|
121
117
|
}
|
|
122
118
|
else {
|
|
123
119
|
console.log("omd-dsh setup: 已跳过同步;稍后运行 `omd-dsh sync` 生效。");
|
|
@@ -130,9 +126,7 @@ async function main() {
|
|
|
130
126
|
let command = "";
|
|
131
127
|
for (let i = 0; i < argv.length; i++) {
|
|
132
128
|
const arg = argv[i];
|
|
133
|
-
if (arg === "--
|
|
134
|
-
flags.harness = argv[++i];
|
|
135
|
-
else if (arg === "--dry-run")
|
|
129
|
+
if (arg === "--dry-run")
|
|
136
130
|
flags.dryRun = true;
|
|
137
131
|
else if (arg === "--verbose")
|
|
138
132
|
flags.verbose = true;
|
|
@@ -163,16 +157,10 @@ async function main() {
|
|
|
163
157
|
return;
|
|
164
158
|
}
|
|
165
159
|
if (command === "setup") {
|
|
166
|
-
await runSetup(flags
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const harnessNodeModules = resolveHarness(flags);
|
|
170
|
-
if (harnessNodeModules === undefined || !existsSync(join(harnessNodeModules, "@deepseek-ai", "dsh-scope", "package.json"))) {
|
|
171
|
-
console.error("omd-dsh sync: cannot locate the DSH harness node_modules.\n - ensure the dsh executable is on PATH, or\n - pass --harness <path to the harness node_modules directory> (saved for later runs).\n\n" + usage());
|
|
172
|
-
process.exitCode = 2;
|
|
160
|
+
await runSetup(flags);
|
|
173
161
|
return;
|
|
174
162
|
}
|
|
175
|
-
await runSync(flags
|
|
163
|
+
await runSync(flags);
|
|
176
164
|
}
|
|
177
165
|
main().catch((error) => {
|
|
178
166
|
console.error("omd-dsh failed: " + (error instanceof Error ? error.message : String(error)));
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
3
2
|
import { setModeOverride } from "./shared.js";
|
|
4
3
|
/**
|
|
5
4
|
* @module @carljia/omd-dsh
|
|
@@ -81,10 +80,10 @@ function presetSwitchedAfterLastRequest(session) {
|
|
|
81
80
|
return lastSwitch > lastHeader;
|
|
82
81
|
}
|
|
83
82
|
function apply(ctx, config) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
// 无 scope 守卫:本行以自包含 bundle(.omd-vendor/omd-mode.mjs)分发,bundle 内
|
|
84
|
+
// 自带的 dsh-scope 副本永远读不到 harness 实例写入的 kScope Symbol,守卫会误报
|
|
85
|
+
// (曾导致全部 omd preset 挂载失败)。preset 挂载由 harness 的 loader 保证作用域;
|
|
86
|
+
// 若误挂到全局组合,后果(进程级钉模型)立即可见。见 docs/ARCHITECTURE.md。
|
|
88
87
|
const pinned = config.provider !== undefined && config.model !== undefined
|
|
89
88
|
? {
|
|
90
89
|
provider: config.provider,
|
package/lib/mode.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
2
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
3
2
|
/**
|
|
4
3
|
* @module @carljia/omd-dsh/mode
|
|
5
4
|
*
|
|
@@ -127,9 +126,7 @@ async function executeSwitch(ctx, invocation) {
|
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
function apply(ctx) {
|
|
130
|
-
|
|
131
|
-
throw new Error("omd-mode-switch: refusing to mount outside a scoped context; mount this row inside an agent preset");
|
|
132
|
-
}
|
|
129
|
+
// 无 scope 守卫:见 src/index.ts 的说明(自包含 bundle 无法读取 harness 的 kScope)。
|
|
133
130
|
ctx.inject(["commands"], (commandCtx) => {
|
|
134
131
|
commandCtx.commands.register({
|
|
135
132
|
name: "mode",
|
package/lib/plan.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
4
3
|
/**
|
|
5
4
|
* @module @carljia/omd-dsh/plan
|
|
6
5
|
*
|
|
@@ -96,9 +95,7 @@ function planModeActive(events) {
|
|
|
96
95
|
return active;
|
|
97
96
|
}
|
|
98
97
|
function apply(ctx) {
|
|
99
|
-
|
|
100
|
-
throw new Error("omd-plan: refusing to mount outside a scoped context; mount this row inside an agent preset");
|
|
101
|
-
}
|
|
98
|
+
// 无 scope 守卫:见 src/index.ts 的说明(自包含 bundle 无法读取 harness 的 kScope)。
|
|
102
99
|
// Scoped .md-only write guard: the planner may write/edit only markdown
|
|
103
100
|
// files, keeping it read-only for every other path. The plan file itself is
|
|
104
101
|
// written by this row via node:fs (not through the model's write/edit tools),
|
package/lib/startwork.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, join } from "node:path";
|
|
3
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
4
3
|
/**
|
|
5
4
|
* @module @carljia/omd-dsh/startwork
|
|
6
5
|
*
|
|
@@ -129,9 +128,7 @@ async function executeStartWork(ctx, invocation) {
|
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
130
|
function apply(ctx) {
|
|
132
|
-
|
|
133
|
-
throw new Error("omd-start-work: refusing to mount outside a scoped context; mount this row inside an agent preset");
|
|
134
|
-
}
|
|
131
|
+
// 无 scope 守卫:见 src/index.ts 的说明(自包含 bundle 无法读取 harness 的 kScope)。
|
|
135
132
|
ctx.inject(["commands"], (commandCtx) => {
|
|
136
133
|
commandCtx.commands.register({
|
|
137
134
|
name: "start-work",
|
package/lib/sync.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* omd-dsh sync core — materialize the OMD presets into <DSH_HOME>/.agent-presets.
|
|
3
3
|
*
|
|
4
|
-
* Shared by the CLI (
|
|
5
|
-
* + restart):
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Shared by the CLI (`omd-dsh sync`) and the bundle boot row (`dsh plugin add`
|
|
5
|
+
* + restart): render each preset's omd-mode / omd-task rows from the user's
|
|
6
|
+
* model matrix, and copy the presets plus the vendored row modules into
|
|
7
|
+
* .agent-presets.
|
|
8
|
+
*
|
|
9
|
+
* The vendored rows are SELF-CONTAINED bundles (see scripts/postbuild.mjs):
|
|
10
|
+
* every dependency is bundled in and they carry no @deepseek-ai imports, so
|
|
11
|
+
* the sync needs NO harness tree knowledge — the rows work regardless of
|
|
12
|
+
* whether the harness's agent machinery loads from an npx cache, a profile,
|
|
13
|
+
* or a global npm install, and no `--harness` configuration is ever needed.
|
|
14
|
+
* Bare package rows in the preset compositions (e.g. @deepseek-ai/dsh-persona)
|
|
15
|
+
* are resolved by the harness's own loader at runtime against the host base.
|
|
10
16
|
*/
|
|
11
17
|
export type SyncFlags = {
|
|
12
|
-
harness?: string;
|
|
13
18
|
dryRun: boolean;
|
|
14
19
|
verbose: boolean;
|
|
15
20
|
};
|
|
@@ -43,20 +48,6 @@ export declare const VENDOR_SOURCES: string[];
|
|
|
43
48
|
/** User-owned model matrix: lives under DSH_HOME, never inside the package or the repo. */
|
|
44
49
|
export declare const MATRIX_PATH: string;
|
|
45
50
|
export declare function dshHome(): string;
|
|
46
|
-
/**
|
|
47
|
-
* Resolve the DSH harness node_modules:
|
|
48
|
-
* 1. --harness flag (and cache it for later);
|
|
49
|
-
* 2. auto-detect via the dsh executable on PATH;
|
|
50
|
-
* 3. fall back to the locally cached value.
|
|
51
|
-
*/
|
|
52
|
-
export declare function resolveHarness(flags: SyncFlags): string | undefined;
|
|
53
|
-
/**
|
|
54
|
-
* Resolve the harness node_modules from THIS module's own location, walking up
|
|
55
|
-
* the Node resolution path for @deepseek-ai/dsh-scope. This is the reliable
|
|
56
|
-
* anchor when the package runs as a bundle inside a DSH profile: the profile's
|
|
57
|
-
* flat module fallback (or its hoisted node_modules) exposes the harness tree.
|
|
58
|
-
*/
|
|
59
|
-
export declare function resolveHarnessFromSelf(): string | undefined;
|
|
60
51
|
export declare function loadMatrix(flags: SyncFlags, log?: (msg: string) => void): Matrix;
|
|
61
52
|
export declare function saveMatrix(m: Matrix): void;
|
|
62
|
-
export declare function runSync(flags: SyncFlags,
|
|
53
|
+
export declare function runSync(flags: SyncFlags, log?: (msg: string) => void): Promise<void>;
|
package/lib/sync.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { promises as fs, existsSync, mkdirSync,
|
|
1
|
+
import { promises as fs, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
7
6
|
export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
7
|
export const VENDOR_SOURCES = ["omd-mode.mjs", "omd-task.mjs", "omd-ulw.mjs", "omd-plan.mjs", "omd-start-work.mjs", "omd-mode-switch.mjs", "shared.js"];
|
|
9
8
|
/** User-owned model matrix: lives under DSH_HOME, never inside the package or the repo. */
|
|
@@ -16,210 +15,6 @@ const TASK_FENCE = { start: "# [omd-dsh:task:start]", end: "# [omd-dsh:task:end]
|
|
|
16
15
|
const RENAMED_FROM = { "omd-architect": "omd-ultraworker" };
|
|
17
16
|
function sha256(text) { return createHash("sha256").update(text).digest("hex"); }
|
|
18
17
|
export function dshHome() { return process.env.DSH_HOME !== undefined && process.env.DSH_HOME !== "" ? resolve(process.env.DSH_HOME) : join(homedir(), ".dsh"); }
|
|
19
|
-
function findNodeModules(start) {
|
|
20
|
-
let current = resolve(start);
|
|
21
|
-
for (;;) {
|
|
22
|
-
if (basename(current) === "node_modules")
|
|
23
|
-
return current;
|
|
24
|
-
const parent = dirname(current);
|
|
25
|
-
if (parent === current)
|
|
26
|
-
return undefined;
|
|
27
|
-
current = parent;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function harnessCachePath() { return join(dshHome(), "omd-dsh-harness.json"); }
|
|
31
|
-
/** Read the cached harness node_modules, ignoring a stale/missing entry. */
|
|
32
|
-
function readCachedHarness() {
|
|
33
|
-
try {
|
|
34
|
-
const p = harnessCachePath();
|
|
35
|
-
if (!existsSync(p))
|
|
36
|
-
return undefined;
|
|
37
|
-
const parsed = JSON.parse(readFileSync(p, "utf8"));
|
|
38
|
-
const nm = parsed && typeof parsed === "object" ? parsed.harnessNodeModules : undefined;
|
|
39
|
-
if (typeof nm !== "string" || nm === "")
|
|
40
|
-
return undefined;
|
|
41
|
-
if (!existsSync(join(nm, "@deepseek-ai", "dsh-scope", "package.json")))
|
|
42
|
-
return undefined;
|
|
43
|
-
return nm;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Persist the resolved harness node_modules for later runs (best-effort). */
|
|
50
|
-
function writeCachedHarness(harnessNodeModules) {
|
|
51
|
-
try {
|
|
52
|
-
writeFileSync(harnessCachePath(), JSON.stringify({ harnessNodeModules }, null, 2) + "\n", "utf8");
|
|
53
|
-
}
|
|
54
|
-
catch { /* best-effort */ }
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Resolve the DSH harness node_modules:
|
|
58
|
-
* 1. --harness flag (and cache it for later);
|
|
59
|
-
* 2. auto-detect via the dsh executable on PATH;
|
|
60
|
-
* 3. fall back to the locally cached value.
|
|
61
|
-
*/
|
|
62
|
-
export function resolveHarness(flags) {
|
|
63
|
-
let nm;
|
|
64
|
-
if (flags.harness !== undefined) {
|
|
65
|
-
nm = findNodeModules(flags.harness);
|
|
66
|
-
if (nm !== undefined) {
|
|
67
|
-
try {
|
|
68
|
-
nm = realpathSync(nm);
|
|
69
|
-
writeCachedHarness(nm);
|
|
70
|
-
}
|
|
71
|
-
catch { /* keep nm as-is */ }
|
|
72
|
-
}
|
|
73
|
-
return nm;
|
|
74
|
-
}
|
|
75
|
-
nm = locateHarnessViaDsh() ?? locateHarnessViaNpxCache() ?? readCachedHarness();
|
|
76
|
-
if (nm !== undefined) {
|
|
77
|
-
try {
|
|
78
|
-
nm = realpathSync(nm);
|
|
79
|
-
}
|
|
80
|
-
catch { /* keep */ }
|
|
81
|
-
}
|
|
82
|
-
return nm;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Resolve the harness node_modules from THIS module's own location, walking up
|
|
86
|
-
* the Node resolution path for @deepseek-ai/dsh-scope. This is the reliable
|
|
87
|
-
* anchor when the package runs as a bundle inside a DSH profile: the profile's
|
|
88
|
-
* flat module fallback (or its hoisted node_modules) exposes the harness tree.
|
|
89
|
-
*/
|
|
90
|
-
export function resolveHarnessFromSelf() {
|
|
91
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
92
|
-
for (;;) {
|
|
93
|
-
const nm = join(dir, "node_modules");
|
|
94
|
-
const scopeDir = join(nm, "@deepseek-ai", "dsh-scope");
|
|
95
|
-
if (existsSync(join(scopeDir, "package.json"))) {
|
|
96
|
-
try {
|
|
97
|
-
const real = realpathSync(scopeDir);
|
|
98
|
-
return findNodeModules(real);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return nm;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const parent = dirname(dir);
|
|
105
|
-
if (parent === dir)
|
|
106
|
-
return undefined;
|
|
107
|
-
dir = parent;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function locateHarnessViaDsh() {
|
|
111
|
-
const candidates = [];
|
|
112
|
-
try {
|
|
113
|
-
const probe = process.platform === "win32" ? "where.exe" : "which";
|
|
114
|
-
const out = execFileSync(probe, ["dsh"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
115
|
-
for (const line of out.split(/\r?\n/)) {
|
|
116
|
-
const t = line.trim();
|
|
117
|
-
if (t !== "")
|
|
118
|
-
candidates.push(t);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
catch { /* dsh not on PATH */ }
|
|
122
|
-
for (const candidate of candidates) {
|
|
123
|
-
let real = candidate;
|
|
124
|
-
try {
|
|
125
|
-
real = realpathSync(candidate);
|
|
126
|
-
}
|
|
127
|
-
catch { /* keep */ }
|
|
128
|
-
const nm = findNodeModules(real);
|
|
129
|
-
if (nm !== undefined && existsSync(join(nm, "@deepseek-ai", "dsh-scope", "package.json")))
|
|
130
|
-
return nm;
|
|
131
|
-
}
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
/** Candidate npx cache roots where a non-global DSH install may live. */
|
|
135
|
-
function npxCacheRoots() {
|
|
136
|
-
const roots = [];
|
|
137
|
-
if (process.platform === "win32") {
|
|
138
|
-
const localAppData = process.env.LOCALAPPDATA;
|
|
139
|
-
if (localAppData)
|
|
140
|
-
roots.push(join(localAppData, "npm-cache", "_npx"));
|
|
141
|
-
const appData = process.env.APPDATA;
|
|
142
|
-
if (appData)
|
|
143
|
-
roots.push(join(appData, "npm-cache", "_npx"));
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
roots.push(join(homedir(), ".npm", "_npx"));
|
|
147
|
-
}
|
|
148
|
-
return roots;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Best-effort scan of the npx cache for a DSH install whose node_modules
|
|
152
|
-
* carries @deepseek-ai/dsh-scope. Picks the most recently touched one.
|
|
153
|
-
*/
|
|
154
|
-
function locateHarnessViaNpxCache() {
|
|
155
|
-
const matches = [];
|
|
156
|
-
for (const root of npxCacheRoots()) {
|
|
157
|
-
let entries;
|
|
158
|
-
try {
|
|
159
|
-
entries = readdirSync(root);
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
for (const entry of entries) {
|
|
165
|
-
const nm = join(root, entry, "node_modules");
|
|
166
|
-
if (!existsSync(join(nm, "@deepseek-ai", "dsh-scope", "package.json")))
|
|
167
|
-
continue;
|
|
168
|
-
let mtime = 0;
|
|
169
|
-
try {
|
|
170
|
-
mtime = statSync(join(root, entry)).mtimeMs;
|
|
171
|
-
}
|
|
172
|
-
catch { /* keep 0 */ }
|
|
173
|
-
matches.push({ nm, mtime });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
matches.sort((a, b) => b.mtime - a.mtime);
|
|
177
|
-
return matches.length > 0 ? matches[0].nm : undefined;
|
|
178
|
-
}
|
|
179
|
-
function resolveHarnessModule(harnessNodeModules, specifier) {
|
|
180
|
-
const segments = specifier.split("/");
|
|
181
|
-
const scope = segments[0].startsWith("@") ? segments[0] + "/" + segments[1] : segments[0];
|
|
182
|
-
const subpath = scope === specifier ? "" : specifier.slice(scope.length + 1);
|
|
183
|
-
const pkgDir = join(harnessNodeModules, ...scope.split("/"));
|
|
184
|
-
const manifestPath = join(pkgDir, "package.json");
|
|
185
|
-
if (!existsSync(manifestPath))
|
|
186
|
-
throw new Error("omd-dsh: cannot resolve \"" + specifier + "\" -- no package.json at " + manifestPath);
|
|
187
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
188
|
-
let entry;
|
|
189
|
-
const exportsMap = manifest.exports;
|
|
190
|
-
if (subpath === "" && exportsMap !== undefined && exportsMap["."] !== undefined) {
|
|
191
|
-
const dot = exportsMap["."];
|
|
192
|
-
if (typeof dot === "string")
|
|
193
|
-
entry = dot;
|
|
194
|
-
else if (typeof dot === "object" && dot !== null) {
|
|
195
|
-
entry = dot.node ?? dot.import ?? dot.default;
|
|
196
|
-
if (typeof entry === "object" && entry !== null)
|
|
197
|
-
entry = entry.node ?? entry.import ?? entry.default;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (entry === undefined && subpath === "")
|
|
201
|
-
entry = manifest.module ?? manifest.main;
|
|
202
|
-
if (entry === undefined)
|
|
203
|
-
entry = subpath === "" ? "index.js" : subpath;
|
|
204
|
-
else if (subpath !== "")
|
|
205
|
-
entry = join(entry, subpath);
|
|
206
|
-
let resolvedPath = resolve(pkgDir, entry);
|
|
207
|
-
try {
|
|
208
|
-
resolvedPath = realpathSync(resolvedPath);
|
|
209
|
-
}
|
|
210
|
-
catch { /* keep */ }
|
|
211
|
-
if (!existsSync(resolvedPath))
|
|
212
|
-
throw new Error("omd-dsh: resolved entry \"" + entry + "\" for \"" + specifier + "\" does not exist at " + resolvedPath);
|
|
213
|
-
return pathToFileURL(resolvedPath).href;
|
|
214
|
-
}
|
|
215
|
-
function rewriteImports(sourceText, harnessNodeModules) {
|
|
216
|
-
const specifierPattern = /@deepseek-ai\/[A-Za-z0-9@._/-]+/g;
|
|
217
|
-
return sourceText.split(/\r?\n/).map((line) => {
|
|
218
|
-
if (!line.trimStart().startsWith("import"))
|
|
219
|
-
return line;
|
|
220
|
-
return line.replace(specifierPattern, (s) => resolveHarnessModule(harnessNodeModules, s));
|
|
221
|
-
}).join("\n");
|
|
222
|
-
}
|
|
223
18
|
function readMeta(dir) {
|
|
224
19
|
const metaPath = join(dir, ".omd-meta.json");
|
|
225
20
|
if (!existsSync(metaPath))
|
|
@@ -397,7 +192,7 @@ async function locallyModified(dir, meta) {
|
|
|
397
192
|
}
|
|
398
193
|
return undefined;
|
|
399
194
|
}
|
|
400
|
-
export async function runSync(flags,
|
|
195
|
+
export async function runSync(flags, log = console.log) {
|
|
401
196
|
const matrix = loadMatrix(flags, log);
|
|
402
197
|
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
403
198
|
const sourceVersion = manifest.version;
|
|
@@ -473,8 +268,9 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
473
268
|
if (!existsSync(sourceFile)) {
|
|
474
269
|
throw new Error("omd-dsh sync: missing vendored source " + sourceFile + " -- run \`npm run build\` first");
|
|
475
270
|
}
|
|
476
|
-
|
|
477
|
-
|
|
271
|
+
// Self-contained bundles: copied verbatim, no import rewriting, no
|
|
272
|
+
// harness tree knowledge (see the module doc comment).
|
|
273
|
+
const sourceText = await fs.readFile(sourceFile, "utf8");
|
|
478
274
|
const sourceHash = sha256(sourceText);
|
|
479
275
|
const destFile = join(vendorTargetDir, vendorName);
|
|
480
276
|
let action = "synced";
|
|
@@ -507,7 +303,7 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
507
303
|
}
|
|
508
304
|
if (!flags.dryRun && VENDOR_SOURCES.length > 0) {
|
|
509
305
|
await fs.mkdir(vendorTargetDir, { recursive: true });
|
|
510
|
-
await fs.writeFile(join(vendorTargetDir, ".omd-meta.json"), JSON.stringify({ source: "omd-dsh", sourceVersion,
|
|
306
|
+
await fs.writeFile(join(vendorTargetDir, ".omd-meta.json"), JSON.stringify({ source: "omd-dsh", sourceVersion, files: nextVendorFiles }, null, 2) + "\n", "utf8");
|
|
511
307
|
}
|
|
512
308
|
}
|
|
513
309
|
if (existsSync(agentPresetsRoot)) {
|
|
@@ -539,7 +335,7 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
539
335
|
}
|
|
540
336
|
log("omd-dsh sync: DSH_HOME=" + dshHome());
|
|
541
337
|
log("omd-dsh sync: matrix=" + MATRIX_PATH + " (customize the model matrix any time with \`omd-dsh setup\`)");
|
|
542
|
-
log("omd-dsh sync: harness
|
|
338
|
+
log("omd-dsh sync: vendored rows are self-contained bundles (no harness tree dependency)");
|
|
543
339
|
log("omd-dsh sync: source version=" + sourceVersion + (flags.dryRun ? " (dry-run)" : ""));
|
|
544
340
|
for (const key of ["synced", "updated", "skipped", "conflicts", "orphan", "removed"])
|
|
545
341
|
for (const line of report[key])
|
package/lib/task.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
3
|
import { assertSubagentMaxDepth } from "@deepseek-ai/dsh-subagent";
|
|
4
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
5
4
|
import { modeOverrideFor } from "./shared.js";
|
|
6
5
|
/**
|
|
7
6
|
* @module @carljia/omd-dsh/task
|
|
@@ -136,9 +135,7 @@ function resolveTier(config, requested) {
|
|
|
136
135
|
throw new Error("omd_task: choose a tier for this task -- valid tiers: " + tierNames.join(", "));
|
|
137
136
|
}
|
|
138
137
|
function apply(ctx, config) {
|
|
139
|
-
|
|
140
|
-
throw new Error("omd-task: refusing to mount outside a scoped context; mount this row inside an agent preset");
|
|
141
|
-
}
|
|
138
|
+
// 无 scope 守卫:见 src/index.ts 的说明(自包含 bundle 无法读取 harness 的 kScope)。
|
|
142
139
|
const invalid = configError(config);
|
|
143
140
|
if (invalid !== undefined)
|
|
144
141
|
throw new Error(invalid);
|