@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 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`):定位 harness、渲染并复制 7 个 preset 与 `.omd-vendor/` 行模块、把行模块的 `@deepseek-ai/*` 导入改写为 harness 树的 `file://` URL、首次生成 `omd-matrix.json`。全程幂等、hash 保护、不覆盖本地修改。
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)固定模型路由。仅限 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 变量均保持用户选择,本行在作用域 ctx 上记录 `omdModeOverride`(provider/model),omd-task 行据此把 deep tier 路由到用户选择的模型。
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 让路并记录 `ctx.omdModeOverride`),名为 `deep` 的 tier 改用用户选择的模型(provider/model 整体替换),其余 tier 保持矩阵配置。
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> [--harness <路径>] [--dry-run]
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 + postbuild)
68
- npm test # vitest,39 个测试
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-scope/dsh-tools/dsh-subagent 的轻量替身),不依赖真实 harness。
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 通过 `ctx.omdModeOverride` 同步)。
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 harness node_modules is located from
11
- * this module's own resolution path first (the profile exposes the harness
12
- * tree via its flat module fallback), then through the CLI's detection chain.
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, resolveHarnessFromSelf, resolveHarness } from "./sync.js";
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 harness node_modules is located from
12
- * this module's own resolution path first (the profile exposes the harness
13
- * tree via its flat module fallback), then through the CLI's detection chain.
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
- const harness = resolveHarnessFromSelf() ?? resolveHarness({ harness: undefined, dryRun: false, verbose: false });
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, resolveHarness, loadMatrix, saveMatrix, dshHome, MATRIX_PATH, } from "./sync.js";
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, harnessNodeModules) {
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
- if (harnessNodeModules === undefined) {
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 === "--harness")
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, resolveHarness(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, harnessNodeModules);
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
- if (scopeOf(ctx) === undefined) {
85
- throw new Error("omd-mode: refusing to mount outside a scoped context (mode '" + config.mode + "'). " +
86
- "Mount this row inside an agent preset; a global mount would pin the model for every agent in the process.");
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
- if (scopeOf(ctx) === undefined) {
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
- if (scopeOf(ctx) === undefined) {
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
- if (scopeOf(ctx) === undefined) {
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 (\`omd-dsh sync\`) and the bundle boot row (\`dsh plugin add\`
5
- * + restart): locate the DSH harness node_modules, render each preset's
6
- * omd-mode / omd-task rows from the user's model matrix, copy the presets and
7
- * vendored row modules into .agent-presets, and rewrite the vendored modules'
8
- * bare @deepseek-ai/* imports to absolute file:// URLs into the harness tree so
9
- * the rows share ONE module instance with the harness (scope symbols etc.).
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, harnessNodeModules: string, log?: (msg: string) => void): Promise<void>;
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, realpathSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
1
+ import { promises as fs, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
- import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
4
  import { homedir } from "node:os";
5
- import { execFileSync } from "node:child_process";
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, harnessNodeModules, log = console.log) {
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
- let sourceText = await fs.readFile(sourceFile, "utf8");
477
- sourceText = rewriteImports(sourceText, harnessNodeModules);
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, harnessNodeModules, files: nextVendorFiles }, null, 2) + "\n", "utf8");
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 node_modules=" + harnessNodeModules);
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
- if (scopeOf(ctx) === undefined) {
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);