@carljia/omd-dsh 0.1.8 → 0.1.9
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 +25 -7
- package/cordis.patch.yml +13 -6
- package/lib/client.d.ts +11 -0
- package/lib/client.js +427 -0
- package/lib/host.d.ts +42 -0
- package/lib/host.js +103 -0
- package/lib/omd-matrix-controller.d.ts +108 -0
- package/lib/omd-matrix-controller.js +180 -0
- package/lib/sync.d.ts +31 -4
- package/lib/sync.js +60 -3
- package/package.json +18 -3
package/README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# @carljia/omd-dsh
|
|
2
2
|
|
|
3
|
-
OMD 理念的 DSH
|
|
3
|
+
OMD 理念的 DSH 插件:host 行 + 客户端设置页 + 7 个模式 preset + 同步 CLI。以 `dsh.bundle` 形式分发——`dsh plugin add @carljia/omd-dsh` 安装后重启即自动同步预设,并在设置面板出现「omd模型分配」页;也可全局安装后用 `omd-dsh sync`。总览、模式矩阵与安装见仓库根 [README.md](../../README.md)。
|
|
4
4
|
|
|
5
|
-
## 分发:bundle +
|
|
5
|
+
## 分发:bundle + host 行 + 客户端插件
|
|
6
6
|
|
|
7
|
-
包声明 `dsh.bundle.patch` → `cordis.patch.yml`,后者注入宿主行 `@carljia/omd-dsh/
|
|
7
|
+
包声明 `dsh.bundle.patch` → `cordis.patch.yml`,后者注入宿主行 `@carljia/omd-dsh`(包根,`lib/host.js`)。该行在 profile 启动时执行与 `omd-dsh sync` 相同的内核(`lib/sync.js`)并额外注册 settings 命名空间(见下节「设置页与数据流」)。**注入名必须是包根而不是 `/boot` 子路径**:`dsh-client-modules` 按 loader entry 的 `options.name` == 包名来发现客户端插件(读取 `dsh.client` 与 `exports["./client"]`),浏览器 half(`lib/client.js`,见下)由此被发现。`lib/boot.js` 保留为无 settings 场景的手动回退行,仍可通过 `@carljia/omd-dsh/boot` 挂载。
|
|
8
8
|
|
|
9
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 解析。
|
|
10
10
|
|
|
11
|
+
## 设置页「omd模型分配」与数据流
|
|
12
|
+
|
|
13
|
+
设置 → 左侧导航 →「omd模型分配」(en: OMD model allocation)可视化编辑 7 个模式的模型矩阵(顶层 provider/model + 可选 reasoningEffort;有 tier 的模式逐档展示 provider/model,tier 的 `hint` 只读展示便于理解档位用途)。保存后**新会话**按新矩阵路由模型,运行中的会话保持创建时的组合。
|
|
14
|
+
|
|
15
|
+
数据流(详见 docs/ARCHITECTURE.md「settings 命名空间数据流」):
|
|
16
|
+
|
|
17
|
+
- `settings.yaml` 的命名空间 `omd-model-allocation` 是**在线编辑的权威存储**(宿主行注册,`base` = 随包默认矩阵 `omd-matrix.default.json`,`applies: "live"`)。
|
|
18
|
+
- 启动时宿主行**调和一次**:若 `<DSH_HOME>/omd-matrix.json` 存在且与命名空间解析值不同,把文件内容 `replace` 进命名空间——旧版/CLI 的自定义不丢,CLI 手改在下次重启被采纳。
|
|
19
|
+
- 用命名空间解析出的完整矩阵渲染 presets,并写回 `omd-matrix.json`(**导出镜像 + CLI 兼容面**)。
|
|
20
|
+
- 监听命名空间:UI 每次保存 → 重新渲染 presets + 更新镜像文件。保存走 `settings.replace`,携带 `expectedRevision`,冲突(`settings-rejected`)时拒绝并重载最新值。
|
|
21
|
+
- `omd-dsh setup` / `omd-dsh sync` CLI 保持不变(读写 `omd-matrix.json`);宿主运行中直接改文件不会实时生效,与「运行中会话不变、新会话重新 sync」的既有语义一致。
|
|
22
|
+
- 边界:settings.yaml 中该段损坏 → 宿主回退纯文件 sync(老 boot 行为),不阻断启动;`omd-matrix.json` 缺失/损坏 → 用命名空间(默认矩阵)渲染并重建镜像;远程浏览器(非 loopback)→ 设置页只读/不可用;settings 只读 → 客户端禁用保存、宿主仍按解析值渲染。
|
|
23
|
+
|
|
11
24
|
## 行:omd-mode
|
|
12
25
|
|
|
13
26
|
按模式(agent preset)固定模型路由。行模块是自包含 bundle,不再做 scope 守卫(bundle 内自带的 dsh-scope 副本读不到 harness 实例写入的 kScope Symbol,守卫会误报——见 docs/ARCHITECTURE.md);请按 preset 组合挂载,误挂到全局组合会导致进程级钉模型。
|
|
@@ -59,19 +72,24 @@ omd-dsh <command> [--dry-run]
|
|
|
59
72
|
|
|
60
73
|
细节见 docs/ARCHITECTURE.md 的「vendored 分发与自包含 bundle」。
|
|
61
74
|
|
|
62
|
-
## 集中配置:<DSH_HOME>/omd-matrix.json
|
|
75
|
+
## 集中配置:<DSH_HOME>/omd-matrix.json 与 settings.yaml 命名空间
|
|
76
|
+
|
|
77
|
+
所有模式的模型路由(provider/model/reasoningEffort)与 omd_task 各 tier 的模型集中在用户矩阵里。两种入口,同一份数据:
|
|
78
|
+
|
|
79
|
+
- **在线编辑(推荐)**:设置 →「omd模型分配」。保存写入 `settings.yaml` 的 `omd-model-allocation` 命名空间,宿主立即重渲染 presets 并把矩阵镜像回 `<DSH_HOME>/omd-matrix.json`。
|
|
80
|
+
- **CLI**:`omd-dsh setup`(交互向导)/ 手改 `omd-matrix.json` 后 `omd-dsh sync`。宿主运行中直接改文件不会实时生效;下次重启时宿主把文件导入命名空间(与「新会话重新 sync」语义一致)。
|
|
63
81
|
|
|
64
|
-
|
|
82
|
+
`omd-dsh sync` 首次运行把随包的 deepseek 默认矩阵 `omd-matrix.default.json` 复制为默认配置(或从旧版本包内位置迁移一次)。仓库与 npm 包只携带默认矩阵文件,**不含任何个人模型配置**——个人模型配置只留在本机(`~/.dsh/settings.yaml` 或 `~/.dsh/omd-matrix.json`)。preset 里 `# [omd-dsh:mode:start] / [omd-dsh:mode:end]` 与 `# [omd-dsh:task:start] / [omd-dsh:task:end]` 之间的区域是自动生成的——改模型请走设置页或矩阵后跑 sync,不要手改 fence 之间的内容。
|
|
65
83
|
|
|
66
84
|
## 开发
|
|
67
85
|
|
|
68
86
|
```bash
|
|
69
|
-
npm install # 触发 build(tsc + esbuild 打包自包含 bundle)
|
|
87
|
+
npm install # 触发 build(tsc + esbuild 打包自包含 bundle + 客户端 bundle)
|
|
70
88
|
npm test # vitest 全量测试
|
|
71
89
|
npm pack # prepack 自动重建,产出 tgz
|
|
72
90
|
```
|
|
73
91
|
|
|
74
|
-
|
|
92
|
+
构建产出:`lib/host.js`(宿主行)、`lib/client.js`(客户端 bundle,`window.__ModuleLoader__.load` 信封 + esbuild CJS,仅外部依赖 react)、`lib/vendor/*.mjs`(自包含行 bundle)。测试桩在 test/stubs/(dsh-tools/dsh-subagent 的轻量替身),不依赖真实 harness;`test/sync.test.ts` / `test/host.test.ts` / `test/client-store.test.ts` 覆盖矩阵注入渲染、启动调和判定与客户端保存状态机。
|
|
75
93
|
|
|
76
94
|
|
|
77
95
|
### 子代理透传语义
|
package/cordis.patch.yml
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
# omd-dsh bundle patch: inject the
|
|
2
|
-
# the 7 OMD agent presets into the user preset root (<DSH_HOME>/.agent-presets).
|
|
1
|
+
# omd-dsh bundle patch: inject the host row under the PACKAGE ROOT name.
|
|
3
2
|
#
|
|
4
|
-
# The
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# The row (lib/host.js) registers the `omd-model-allocation` settings
|
|
4
|
+
# namespace (settings.yaml = online-edit source), imports CLI/legacy edits
|
|
5
|
+
# from <DSH_HOME>/omd-matrix.json once at startup, renders the 7 OMD presets
|
|
6
|
+
# from the resolved matrix, mirrors the matrix back to omd-matrix.json, and
|
|
7
|
+
# re-renders + re-mirrors on every UI save.
|
|
8
|
+
#
|
|
9
|
+
# The entry name MUST be the bare package name `@carljia/omd-dsh`, not a
|
|
10
|
+
# subpath: dsh-client-modules discovers client plugins by matching loader
|
|
11
|
+
# entry names against package names (reading dsh.client + exports["./client"]),
|
|
12
|
+
# and the browser half (lib/client.js) provides the "omd模型分配" settings
|
|
13
|
+
# section. Install with:
|
|
7
14
|
# dsh plugin --profile web add @carljia/omd-dsh # then restart dsh.
|
|
8
15
|
- insert:
|
|
9
16
|
- id: omd-dsh
|
|
10
|
-
name: '@carljia/omd-dsh
|
|
17
|
+
name: '@carljia/omd-dsh'
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { OmdMatrixController } from "./omd-matrix-controller.js";
|
|
3
|
+
import type { OmdScope } from "./omd-matrix-controller.js";
|
|
4
|
+
/** Cordis services this browser plugin requires (fiber service waiting). */
|
|
5
|
+
export declare const inject: string[];
|
|
6
|
+
export declare function apply(ctx: any): void;
|
|
7
|
+
export declare function OmdModelAllocationSection(props: {
|
|
8
|
+
scope: OmdScope;
|
|
9
|
+
controller: OmdMatrixController;
|
|
10
|
+
t: (key: string) => string;
|
|
11
|
+
}): React.ReactElement;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@carljia/omd-dsh",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
"use strict";
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
28
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
29
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
30
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
31
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
32
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
33
|
+
mod
|
|
34
|
+
));
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
37
|
+
|
|
38
|
+
// src/client.tsx
|
|
39
|
+
var client_exports = {};
|
|
40
|
+
__export(client_exports, {
|
|
41
|
+
OmdModelAllocationSection: () => OmdModelAllocationSection,
|
|
42
|
+
apply: () => apply,
|
|
43
|
+
inject: () => inject
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(client_exports);
|
|
46
|
+
var React = __toESM(require("react"), 1);
|
|
47
|
+
|
|
48
|
+
// src/omd-matrix-controller.ts
|
|
49
|
+
var OMD_SETTINGS_NS = "omd-model-allocation";
|
|
50
|
+
function cloneMatrix(value) {
|
|
51
|
+
if (value === void 0) return null;
|
|
52
|
+
return JSON.parse(JSON.stringify(value));
|
|
53
|
+
}
|
|
54
|
+
function messageOf(error) {
|
|
55
|
+
return error instanceof Error ? error.message : String(error);
|
|
56
|
+
}
|
|
57
|
+
var OmdMatrixController = class {
|
|
58
|
+
constructor(api, scope, describe) {
|
|
59
|
+
__publicField(this, "api", api);
|
|
60
|
+
__publicField(this, "scope", scope);
|
|
61
|
+
__publicField(this, "describe", describe);
|
|
62
|
+
__publicField(this, "draft");
|
|
63
|
+
__publicField(this, "status", "idle");
|
|
64
|
+
__publicField(this, "error", null);
|
|
65
|
+
__publicField(this, "saved", false);
|
|
66
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
67
|
+
__publicField(this, "unsubscribeScope");
|
|
68
|
+
/**
|
|
69
|
+
* Cached snapshot handed to `useSyncExternalStore`. React requires
|
|
70
|
+
* `getSnapshot()` to return the SAME reference between changes — a fresh
|
|
71
|
+
* object per call throws "The result of getSnapshot should be cached" and
|
|
72
|
+
* crashes the whole settings entry (blank page). Rebuilt only in `emit()`.
|
|
73
|
+
*/
|
|
74
|
+
__publicField(this, "snapshot");
|
|
75
|
+
this.draft = cloneMatrix(scope.getSnapshot().value);
|
|
76
|
+
this.snapshot = { draft: this.draft, status: this.status, error: this.error, saved: this.saved };
|
|
77
|
+
this.unsubscribeScope = scope.subscribe(() => this.onScopeChange());
|
|
78
|
+
}
|
|
79
|
+
/** Release the scope subscription (plugin fiber teardown). */
|
|
80
|
+
dispose() {
|
|
81
|
+
this.unsubscribeScope();
|
|
82
|
+
this.listeners.clear();
|
|
83
|
+
}
|
|
84
|
+
/** Stable snapshot reference (uSES-safe): the SAME object until state changes. */
|
|
85
|
+
getSnapshot() {
|
|
86
|
+
return this.snapshot;
|
|
87
|
+
}
|
|
88
|
+
subscribe(listener) {
|
|
89
|
+
this.listeners.add(listener);
|
|
90
|
+
return () => {
|
|
91
|
+
this.listeners.delete(listener);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
emit() {
|
|
95
|
+
this.snapshot = { draft: this.draft, status: this.status, error: this.error, saved: this.saved };
|
|
96
|
+
for (const listener of [...this.listeners]) listener();
|
|
97
|
+
}
|
|
98
|
+
/** External change (mirror reload after a rejected write, another window):
|
|
99
|
+
* rebuild the draft from the fresh value, unless a save is in flight. */
|
|
100
|
+
onScopeChange() {
|
|
101
|
+
if (this.status === "saving") return;
|
|
102
|
+
this.refreshDraft();
|
|
103
|
+
this.emit();
|
|
104
|
+
}
|
|
105
|
+
refreshDraft() {
|
|
106
|
+
const value = this.scope.getSnapshot().value;
|
|
107
|
+
if (value !== void 0) this.draft = cloneMatrix(value);
|
|
108
|
+
}
|
|
109
|
+
/** Whether the Save / Reset controls may run. */
|
|
110
|
+
get canSave() {
|
|
111
|
+
const snap = this.scope.getSnapshot();
|
|
112
|
+
return snap.status === "ready" && snap.writable && this.status !== "saving" && this.draft !== null;
|
|
113
|
+
}
|
|
114
|
+
/** Whether the namespace accepts writes at all (read-only provider / memory mode). */
|
|
115
|
+
get writable() {
|
|
116
|
+
return this.scope.getSnapshot().writable;
|
|
117
|
+
}
|
|
118
|
+
/** Edit one mode's top-level field; an empty string removes the override
|
|
119
|
+
* (the field re-inherits the composition base / schema default). */
|
|
120
|
+
patchMode(modeId, field, value) {
|
|
121
|
+
const draft = this.draft;
|
|
122
|
+
if (draft === null) return;
|
|
123
|
+
const modes = { ...draft.modes };
|
|
124
|
+
const cfg = { ...modes[modeId] ?? {} };
|
|
125
|
+
if (value === "") delete cfg[field];
|
|
126
|
+
else cfg[field] = value;
|
|
127
|
+
modes[modeId] = cfg;
|
|
128
|
+
this.draft = { ...draft, modes };
|
|
129
|
+
this.saved = false;
|
|
130
|
+
this.emit();
|
|
131
|
+
}
|
|
132
|
+
/** Edit one tier's provider/model; an empty string removes the override. */
|
|
133
|
+
patchTier(modeId, tier, field, value) {
|
|
134
|
+
const draft = this.draft;
|
|
135
|
+
if (draft === null) return;
|
|
136
|
+
const modes = { ...draft.modes };
|
|
137
|
+
const cfg = { ...modes[modeId] ?? {} };
|
|
138
|
+
const tiers = { ...cfg.tiers ?? {} };
|
|
139
|
+
const tierCfg = { ...tiers[tier] ?? {} };
|
|
140
|
+
if (value === "") delete tierCfg[field];
|
|
141
|
+
else tierCfg[field] = value;
|
|
142
|
+
tiers[tier] = tierCfg;
|
|
143
|
+
cfg.tiers = tiers;
|
|
144
|
+
modes[modeId] = cfg;
|
|
145
|
+
this.draft = { ...draft, modes };
|
|
146
|
+
this.saved = false;
|
|
147
|
+
this.emit();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Persist the whole draft matrix through `settings.replace` (wholesale
|
|
151
|
+
* section replacement — the removal/reset path merge cannot express). The
|
|
152
|
+
* revision fence makes a stale editor fail loudly instead of overwriting.
|
|
153
|
+
* @returns once the write settled and the snapshot reflects the outcome.
|
|
154
|
+
*/
|
|
155
|
+
async save() {
|
|
156
|
+
if (!this.canSave || this.draft === null) return;
|
|
157
|
+
const snap = this.scope.getSnapshot();
|
|
158
|
+
this.status = "saving";
|
|
159
|
+
this.error = null;
|
|
160
|
+
this.emit();
|
|
161
|
+
const response = await this.replace({ section: this.draft, revision: snap.revision });
|
|
162
|
+
if (response.ok) this.saved = true;
|
|
163
|
+
else {
|
|
164
|
+
this.error = response.message;
|
|
165
|
+
await this.describe.load();
|
|
166
|
+
}
|
|
167
|
+
this.refreshDraft();
|
|
168
|
+
this.status = "idle";
|
|
169
|
+
this.emit();
|
|
170
|
+
}
|
|
171
|
+
/** Restore the shipped default matrix: replace with an empty section. */
|
|
172
|
+
async reset() {
|
|
173
|
+
if (!this.canSave) return;
|
|
174
|
+
const snap = this.scope.getSnapshot();
|
|
175
|
+
this.status = "saving";
|
|
176
|
+
this.error = null;
|
|
177
|
+
this.emit();
|
|
178
|
+
const response = await this.replace({ section: {}, revision: snap.revision });
|
|
179
|
+
if (response.ok) this.saved = true;
|
|
180
|
+
else this.error = response.message;
|
|
181
|
+
await this.describe.load();
|
|
182
|
+
this.refreshDraft();
|
|
183
|
+
this.status = "idle";
|
|
184
|
+
this.emit();
|
|
185
|
+
}
|
|
186
|
+
async replace(request) {
|
|
187
|
+
try {
|
|
188
|
+
const response = await this.api.settings.replace({
|
|
189
|
+
ns: OMD_SETTINGS_NS,
|
|
190
|
+
section: request.section,
|
|
191
|
+
...request.revision === void 0 ? {} : { expectedRevision: request.revision }
|
|
192
|
+
});
|
|
193
|
+
if (response.result.ok) return { ok: true };
|
|
194
|
+
return { ok: false, message: response.result.error.message };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return { ok: false, message: messageOf(error) };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/client.tsx
|
|
202
|
+
var NS = "settings.omd";
|
|
203
|
+
var MODE_ORDER = ["executor", "ultraworker", "planner", "reviewer", "explorer", "librarian", "chat"];
|
|
204
|
+
var en = {
|
|
205
|
+
nav: "OMD model allocation",
|
|
206
|
+
title: "OMD model allocation",
|
|
207
|
+
intro: "Model routes for the 7 OMD agent presets. Changes apply to sessions you start from now on \u2014 running sessions keep the models they began with.",
|
|
208
|
+
loading: "Loading the model matrix\u2026",
|
|
209
|
+
unavailable: "The model matrix is not available from this browser (settings are process-local for remote connections).",
|
|
210
|
+
readOnly: "The settings document is read-only here; the current matrix is shown for reference.",
|
|
211
|
+
mode: "Mode",
|
|
212
|
+
provider: "Provider",
|
|
213
|
+
model: "Model",
|
|
214
|
+
reasoningEffort: "Reasoning effort",
|
|
215
|
+
reasoningEffortHint: "Optional; leave empty to let the provider decide.",
|
|
216
|
+
tiers: "Tiers",
|
|
217
|
+
tier: "Tier",
|
|
218
|
+
hint: "Purpose",
|
|
219
|
+
save: "Save",
|
|
220
|
+
saving: "Saving\u2026",
|
|
221
|
+
reset: "Restore defaults",
|
|
222
|
+
saved: "Saved \u2014 new sessions will use the new matrix.",
|
|
223
|
+
errorPrefix: "Save failed:",
|
|
224
|
+
renderError: "The model matrix form could not be rendered:",
|
|
225
|
+
conflict: "The matrix changed on the host; the form was reloaded with the latest values.",
|
|
226
|
+
executor: "Executor",
|
|
227
|
+
ultraworker: "Ultraworker",
|
|
228
|
+
planner: "Planner",
|
|
229
|
+
reviewer: "Reviewer",
|
|
230
|
+
explorer: "Explorer",
|
|
231
|
+
librarian: "Librarian",
|
|
232
|
+
chat: "Chat"
|
|
233
|
+
};
|
|
234
|
+
var zh = {
|
|
235
|
+
nav: "omd\u6A21\u578B\u5206\u914D",
|
|
236
|
+
title: "omd \u6A21\u578B\u5206\u914D",
|
|
237
|
+
intro: "7 \u4E2A OMD agent \u9884\u8BBE\u7684\u6A21\u578B\u8DEF\u7531\u3002\u4FDD\u5B58\u540E\u5BF9\u4E4B\u540E\u65B0\u5EFA\u7684\u4F1A\u8BDD\u751F\u6548\u2014\u2014\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u4FDD\u6301\u5B83\u5F00\u59CB\u65F6\u7684\u6A21\u578B\u3002",
|
|
238
|
+
loading: "\u6B63\u5728\u52A0\u8F7D\u6A21\u578B\u77E9\u9635\u2026",
|
|
239
|
+
unavailable: "\u5F53\u524D\u6D4F\u89C8\u5668\u65E0\u6CD5\u8BBF\u95EE\u6A21\u578B\u77E9\u9635\uFF08\u8FDC\u7A0B\u8FDE\u63A5\u65F6\u8BBE\u7F6E\u4EC5\u5728\u672C\u673A\u8FDB\u7A0B\u5185\u6709\u6548\uFF09\u3002",
|
|
240
|
+
readOnly: "\u8BBE\u7F6E\u6587\u6863\u5F53\u524D\u53EA\u8BFB\uFF1B\u4EE5\u4E0B\u5C55\u793A\u73B0\u6709\u77E9\u9635\uFF0C\u4EC5\u4F9B\u53C2\u8003\u3002",
|
|
241
|
+
mode: "\u6A21\u5F0F",
|
|
242
|
+
provider: "Provider",
|
|
243
|
+
model: "Model",
|
|
244
|
+
reasoningEffort: "\u63A8\u7406\u5F3A\u5EA6",
|
|
245
|
+
reasoningEffortHint: "\u53EF\u9009\uFF1B\u7559\u7A7A\u4EA4\u7ED9 provider \u9ED8\u8BA4\u3002",
|
|
246
|
+
tiers: "\u6863\u4F4D\uFF08Tiers\uFF09",
|
|
247
|
+
tier: "\u6863\u4F4D",
|
|
248
|
+
hint: "\u7528\u9014",
|
|
249
|
+
save: "\u4FDD\u5B58",
|
|
250
|
+
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
251
|
+
reset: "\u6062\u590D\u9ED8\u8BA4",
|
|
252
|
+
saved: "\u5DF2\u4FDD\u5B58\u2014\u2014\u65B0\u4F1A\u8BDD\u5C06\u6309\u65B0\u77E9\u9635\u8DEF\u7531\u6A21\u578B\u3002",
|
|
253
|
+
errorPrefix: "\u4FDD\u5B58\u5931\u8D25\uFF1A",
|
|
254
|
+
renderError: "\u6A21\u578B\u77E9\u9635\u8868\u5355\u6E32\u67D3\u5931\u8D25\uFF1A",
|
|
255
|
+
conflict: "\u77E9\u9635\u5DF2\u5728\u5BBF\u4E3B\u4FA7\u53D8\u66F4\uFF0C\u8868\u5355\u5DF2\u6309\u6700\u65B0\u503C\u91CD\u65B0\u52A0\u8F7D\u3002",
|
|
256
|
+
executor: "\u6267\u884C\u8005",
|
|
257
|
+
ultraworker: "\u8D85\u80FD\u5DE5\u4F5C\u8005",
|
|
258
|
+
planner: "\u89C4\u5212\u8005",
|
|
259
|
+
reviewer: "\u8BC4\u5BA1\u8005",
|
|
260
|
+
explorer: "\u63A2\u7D22\u8005",
|
|
261
|
+
librarian: "\u56FE\u4E66\u7BA1\u7406\u5458",
|
|
262
|
+
chat: "\u5BF9\u8BDD"
|
|
263
|
+
};
|
|
264
|
+
var inject = ["slots", "locale", "connection", "settingsScope"];
|
|
265
|
+
function apply(ctx) {
|
|
266
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "omd-dsh: settings section dictionaries");
|
|
267
|
+
const { api } = ctx.get("connection");
|
|
268
|
+
const describe = ctx.settingsScope.describe();
|
|
269
|
+
const scope = ctx.settingsScope.bind({ namespace: OMD_SETTINGS_NS });
|
|
270
|
+
const controller = new OmdMatrixController(api, scope, describe);
|
|
271
|
+
const t = ctx.locale.bind(NS);
|
|
272
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
273
|
+
name: "settings.section",
|
|
274
|
+
id: "omd-model-allocation",
|
|
275
|
+
order: 30,
|
|
276
|
+
// after Models (10) and Agent presets (20)
|
|
277
|
+
label: () => t("nav"),
|
|
278
|
+
locale: NS,
|
|
279
|
+
inject: () => ({ scope, controller, t })
|
|
280
|
+
}, OmdModelAllocationSection));
|
|
281
|
+
}
|
|
282
|
+
var styles = {
|
|
283
|
+
section: { maxWidth: 720, color: "var(--dsw-alias-label-primary)", display: "flex", flexDirection: "column", gap: 12 },
|
|
284
|
+
title: { margin: 0, fontSize: 18, fontWeight: 600 },
|
|
285
|
+
intro: { color: "var(--dsw-alias-label-tertiary)", margin: 0, fontSize: 13, lineHeight: "20px" },
|
|
286
|
+
notice: { color: "var(--dsw-alias-state-warn-label)", margin: 0, fontSize: 12, lineHeight: "18px" },
|
|
287
|
+
savedNotice: { color: "var(--dsw-alias-state-success-primary)", margin: 0, fontSize: 12, lineHeight: "18px" },
|
|
288
|
+
error: { color: "var(--dsw-alias-state-error-primary)", margin: 0, fontSize: 12, lineHeight: "18px" },
|
|
289
|
+
card: { border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-3)", borderRadius: 12, padding: "14px 16px", display: "flex", flexDirection: "column", gap: 10 },
|
|
290
|
+
cardHead: { display: "flex", alignItems: "baseline", gap: 8 },
|
|
291
|
+
cardTitle: { margin: 0, fontSize: 15, fontWeight: 600 },
|
|
292
|
+
cardRoute: { color: "var(--dsw-alias-label-tertiary)", fontSize: 12, lineHeight: "18px" },
|
|
293
|
+
field: { display: "flex", flexDirection: "column", gap: 4 },
|
|
294
|
+
fieldLabel: { color: "var(--dsw-alias-label-secondary)", fontSize: 12, fontWeight: 500, lineHeight: "18px" },
|
|
295
|
+
input: { boxSizing: "border-box", border: "1px solid var(--dsw-alias-border-l2)", width: "100%", height: 32, font: "inherit", background: "var(--dsw-alias-bg-layer-1)", color: "var(--dsw-alias-label-primary)", borderRadius: 8, padding: "0 10px", fontSize: 14, lineHeight: "22px" },
|
|
296
|
+
inputDisabled: { opacity: 0.6, cursor: "default" },
|
|
297
|
+
tierRow: { borderTop: "1px solid var(--dsw-alias-border-l2)", paddingTop: 10, display: "flex", flexDirection: "column", gap: 8 },
|
|
298
|
+
tierHead: { display: "flex", alignItems: "baseline", gap: 8 },
|
|
299
|
+
tierName: { margin: 0, fontSize: 13, fontWeight: 600 },
|
|
300
|
+
tierHint: { color: "var(--dsw-alias-label-tertiary)", fontSize: 12, lineHeight: "18px", flex: 1, overflowWrap: "anywhere" },
|
|
301
|
+
grid2: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: 8 },
|
|
302
|
+
actions: { display: "flex", alignItems: "center", gap: 8, marginTop: 4 },
|
|
303
|
+
primaryButton: { boxSizing: "border-box", height: 36, font: "inherit", cursor: "pointer", border: "none", borderRadius: 18, padding: "0 14px", fontSize: 14, lineHeight: "22px", background: "var(--dsw-alias-button-primary-fill)", color: "var(--dsw-alias-label-primary-foreground)" },
|
|
304
|
+
secondaryButton: { boxSizing: "border-box", height: 36, font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2)", background: "transparent", color: "var(--dsw-alias-label-primary)", borderRadius: 18, padding: "0 14px", fontSize: 14, lineHeight: "22px" },
|
|
305
|
+
buttonDisabled: { opacity: 0.4, cursor: "default" }
|
|
306
|
+
};
|
|
307
|
+
var OmdRenderBoundary = class extends React.Component {
|
|
308
|
+
constructor() {
|
|
309
|
+
super(...arguments);
|
|
310
|
+
__publicField(this, "state", { error: null });
|
|
311
|
+
}
|
|
312
|
+
static getDerivedStateFromError(error) {
|
|
313
|
+
return { error };
|
|
314
|
+
}
|
|
315
|
+
render() {
|
|
316
|
+
if (this.state.error !== null) {
|
|
317
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.section }, /* @__PURE__ */ React.createElement("h2", { style: styles.title }, this.props.t("nav")), /* @__PURE__ */ React.createElement("p", { style: styles.error, role: "alert" }, this.props.t("renderError"), " ", this.state.error instanceof Error ? this.state.error.message : String(this.state.error)));
|
|
318
|
+
}
|
|
319
|
+
return this.props.children ?? null;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
function OmdModelAllocationSection(props) {
|
|
323
|
+
return /* @__PURE__ */ React.createElement(OmdRenderBoundary, { t: props.t }, /* @__PURE__ */ React.createElement(OmdModelAllocationBody, { ...props }));
|
|
324
|
+
}
|
|
325
|
+
function OmdModelAllocationBody(props) {
|
|
326
|
+
const { scope, controller, t } = props;
|
|
327
|
+
const scopeSnapshot = React.useSyncExternalStore(
|
|
328
|
+
React.useCallback((listener) => scope.subscribe(listener), [scope]),
|
|
329
|
+
React.useCallback(() => scope.getSnapshot(), [scope]),
|
|
330
|
+
React.useCallback(() => scope.getSnapshot(), [scope])
|
|
331
|
+
// SSR/test renderers; no-op in the browser
|
|
332
|
+
);
|
|
333
|
+
const ui = React.useSyncExternalStore(
|
|
334
|
+
React.useCallback((listener) => controller.subscribe(listener), [controller]),
|
|
335
|
+
React.useCallback(() => controller.getSnapshot(), [controller]),
|
|
336
|
+
React.useCallback(() => controller.getSnapshot(), [controller])
|
|
337
|
+
);
|
|
338
|
+
if (scopeSnapshot.status === "unavailable") {
|
|
339
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.section }, /* @__PURE__ */ React.createElement("h2", { style: styles.title }, t("nav")), /* @__PURE__ */ React.createElement("p", { style: styles.intro }, t("unavailable")));
|
|
340
|
+
}
|
|
341
|
+
if (scopeSnapshot.status !== "ready" || ui.draft === null) {
|
|
342
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.section }, /* @__PURE__ */ React.createElement("h2", { style: styles.title }, t("nav")), /* @__PURE__ */ React.createElement("p", { style: styles.intro }, t("loading")));
|
|
343
|
+
}
|
|
344
|
+
const matrix = ui.draft;
|
|
345
|
+
const readOnly = !controller.writable;
|
|
346
|
+
return /* @__PURE__ */ React.createElement("div", { style: styles.section }, /* @__PURE__ */ React.createElement("h2", { style: styles.title }, t("nav")), /* @__PURE__ */ React.createElement("p", { style: styles.intro }, t("intro")), readOnly ? /* @__PURE__ */ React.createElement("p", { style: styles.notice }, t("readOnly")) : null, ui.saved ? /* @__PURE__ */ React.createElement("p", { style: styles.savedNotice, role: "status" }, t("saved")) : null, ui.error !== null ? /* @__PURE__ */ React.createElement("p", { style: styles.error, role: "alert" }, t("errorPrefix"), " ", ui.error, ui.error.toLowerCase().includes("revision") || ui.error.toLowerCase().includes("changed") ? ` ${t("conflict")}` : "") : null, MODE_ORDER.map((modeId) => {
|
|
347
|
+
const cfg = matrix.modes[modeId] ?? {};
|
|
348
|
+
const route = cfg.provider !== void 0 || cfg.model !== void 0 ? `${cfg.provider ?? "?"}/${cfg.model ?? "?"}` : "";
|
|
349
|
+
const modeName = t(modeId) ?? modeId;
|
|
350
|
+
return /* @__PURE__ */ React.createElement("div", { key: modeId, style: styles.card }, /* @__PURE__ */ React.createElement("div", { style: styles.cardHead }, /* @__PURE__ */ React.createElement("h3", { style: styles.cardTitle }, modeName), /* @__PURE__ */ React.createElement("span", { style: styles.cardRoute }, modeId, route !== "" ? ` \xB7 ${route}` : "")), /* @__PURE__ */ React.createElement("div", { style: styles.grid2 }, /* @__PURE__ */ React.createElement("label", { style: styles.field }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("provider")), /* @__PURE__ */ React.createElement(
|
|
351
|
+
"input",
|
|
352
|
+
{
|
|
353
|
+
style: { ...styles.input, ...readOnly ? styles.inputDisabled : {} },
|
|
354
|
+
value: cfg.provider ?? "",
|
|
355
|
+
disabled: readOnly || ui.status === "saving",
|
|
356
|
+
spellCheck: false,
|
|
357
|
+
placeholder: "deepseek-official",
|
|
358
|
+
onChange: (event) => controller.patchMode(modeId, "provider", event.target.value)
|
|
359
|
+
}
|
|
360
|
+
)), /* @__PURE__ */ React.createElement("label", { style: styles.field }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("model")), /* @__PURE__ */ React.createElement(
|
|
361
|
+
"input",
|
|
362
|
+
{
|
|
363
|
+
style: { ...styles.input, ...readOnly ? styles.inputDisabled : {} },
|
|
364
|
+
value: cfg.model ?? "",
|
|
365
|
+
disabled: readOnly || ui.status === "saving",
|
|
366
|
+
spellCheck: false,
|
|
367
|
+
placeholder: "deepseek-v4-pro",
|
|
368
|
+
onChange: (event) => controller.patchMode(modeId, "model", event.target.value)
|
|
369
|
+
}
|
|
370
|
+
))), /* @__PURE__ */ React.createElement("label", { style: styles.field }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("reasoningEffort")), /* @__PURE__ */ React.createElement(
|
|
371
|
+
"input",
|
|
372
|
+
{
|
|
373
|
+
style: { ...styles.input, ...readOnly ? styles.inputDisabled : {} },
|
|
374
|
+
value: cfg.reasoningEffort ?? "",
|
|
375
|
+
disabled: readOnly || ui.status === "saving",
|
|
376
|
+
spellCheck: false,
|
|
377
|
+
placeholder: "",
|
|
378
|
+
title: t("reasoningEffortHint"),
|
|
379
|
+
onChange: (event) => controller.patchMode(modeId, "reasoningEffort", event.target.value)
|
|
380
|
+
}
|
|
381
|
+
)), cfg.tiers !== void 0 && Object.keys(cfg.tiers).length > 0 ? /* @__PURE__ */ React.createElement("div", { style: styles.tierRow }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("tiers")), Object.entries(cfg.tiers).map(([tierName, tier]) => /* @__PURE__ */ React.createElement("div", { key: tierName, style: styles.tierRow }, /* @__PURE__ */ React.createElement("div", { style: styles.tierHead }, /* @__PURE__ */ React.createElement("h4", { style: styles.tierName }, tierName), /* @__PURE__ */ React.createElement("span", { style: styles.tierHint }, tier.hint ?? "")), /* @__PURE__ */ React.createElement("div", { style: styles.grid2 }, /* @__PURE__ */ React.createElement("label", { style: styles.field }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("provider")), /* @__PURE__ */ React.createElement(
|
|
382
|
+
"input",
|
|
383
|
+
{
|
|
384
|
+
style: { ...styles.input, ...readOnly ? styles.inputDisabled : {} },
|
|
385
|
+
value: tier.provider ?? "",
|
|
386
|
+
disabled: readOnly || ui.status === "saving",
|
|
387
|
+
spellCheck: false,
|
|
388
|
+
onChange: (event) => controller.patchTier(modeId, tierName, "provider", event.target.value)
|
|
389
|
+
}
|
|
390
|
+
)), /* @__PURE__ */ React.createElement("label", { style: styles.field }, /* @__PURE__ */ React.createElement("span", { style: styles.fieldLabel }, t("model")), /* @__PURE__ */ React.createElement(
|
|
391
|
+
"input",
|
|
392
|
+
{
|
|
393
|
+
style: { ...styles.input, ...readOnly ? styles.inputDisabled : {} },
|
|
394
|
+
value: tier.model ?? "",
|
|
395
|
+
disabled: readOnly || ui.status === "saving",
|
|
396
|
+
spellCheck: false,
|
|
397
|
+
onChange: (event) => controller.patchTier(modeId, tierName, "model", event.target.value)
|
|
398
|
+
}
|
|
399
|
+
)))))) : null);
|
|
400
|
+
}), !readOnly ? /* @__PURE__ */ React.createElement("div", { style: styles.actions }, /* @__PURE__ */ React.createElement(
|
|
401
|
+
"button",
|
|
402
|
+
{
|
|
403
|
+
type: "button",
|
|
404
|
+
style: { ...styles.primaryButton, ...!controller.canSave ? styles.buttonDisabled : {} },
|
|
405
|
+
disabled: !controller.canSave,
|
|
406
|
+
onClick: () => {
|
|
407
|
+
controller.save();
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
ui.status === "saving" ? t("saving") : t("save")
|
|
411
|
+
), /* @__PURE__ */ React.createElement(
|
|
412
|
+
"button",
|
|
413
|
+
{
|
|
414
|
+
type: "button",
|
|
415
|
+
style: { ...styles.secondaryButton, ...!controller.canSave ? styles.buttonDisabled : {} },
|
|
416
|
+
disabled: !controller.canSave,
|
|
417
|
+
onClick: () => {
|
|
418
|
+
controller.reset();
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
t("reset")
|
|
422
|
+
)) : null);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return module.exports;
|
|
426
|
+
}
|
|
427
|
+
});
|
package/lib/host.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Matrix } from "./sync.js";
|
|
2
|
+
/**
|
|
3
|
+
* @module @carljia/omd-dsh
|
|
4
|
+
*
|
|
5
|
+
* omd-dsh host row (package root entry, injected by cordis.patch.yml):
|
|
6
|
+
*
|
|
7
|
+
* 1. registers the `omd-model-allocation` settings namespace (base = the
|
|
8
|
+
* shipped default matrix, `applies: "live"`), making the settings
|
|
9
|
+
* document (<DSH_HOME>/settings.yaml) the authoritative "online edit"
|
|
10
|
+
* store for the model matrix;
|
|
11
|
+
* 2. reconciles once at startup: if <DSH_HOME>/omd-matrix.json exists and
|
|
12
|
+
* differs from the namespace's resolved value, its content is imported
|
|
13
|
+
* into the namespace (`replace`) — CLI edits and pre-settings installs
|
|
14
|
+
* survive the migration, and CLI edits are picked up on the next restart;
|
|
15
|
+
* 3. renders the 7 presets from the namespace's resolved matrix and mirrors
|
|
16
|
+
* that matrix back to omd-matrix.json (the export mirror + CLI face);
|
|
17
|
+
* 4. watches the namespace: every UI save re-renders the presets and
|
|
18
|
+
* re-mirrors the file. New sessions route with the new matrix; running
|
|
19
|
+
* sessions keep the combination they started with.
|
|
20
|
+
*
|
|
21
|
+
* The browser half (lib/client.js) binds the same namespace and renders the
|
|
22
|
+
* "omd模型分配" settings section (see src/client.tsx). Because the client
|
|
23
|
+
* plugin discovery (dsh-client-modules) matches loader entries by package
|
|
24
|
+
* name, the bundle patch injects THIS row under the bare package name
|
|
25
|
+
* `@carljia/omd-dsh` — not a `/boot` subpath.
|
|
26
|
+
*
|
|
27
|
+
* Failure policy: a broken settings document / namespace must never brick the
|
|
28
|
+
* host's boot — any failure here falls back to the plain file sync (the old
|
|
29
|
+
* boot behavior) and is logged.
|
|
30
|
+
*/
|
|
31
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
32
|
+
export declare const name = "omd-dsh";
|
|
33
|
+
/** Requires the settings service (provided by dsh-base → dsh-settings-file in every profile). */
|
|
34
|
+
export declare const inject: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Startup reconciliation decision (pure, unit-tested): the file matrix is
|
|
37
|
+
* imported into the settings namespace exactly when it exists AND differs
|
|
38
|
+
* from the namespace's currently resolved value. Equal values need no write;
|
|
39
|
+
* a missing file leaves the namespace (defaults / user section) authoritative.
|
|
40
|
+
*/
|
|
41
|
+
export declare function shouldImportFile(fileMatrix: Matrix | undefined, resolved: Matrix): boolean;
|
|
42
|
+
export declare function apply(ctx: any): Promise<void>;
|
package/lib/host.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import { readDefaultMatrix, readMatrixFileIfExists, matrixEquals, runSyncWithMatrix, runSync, saveMatrix, MatrixSchema, MATRIX_PATH, } from "./sync.js";
|
|
3
|
+
/**
|
|
4
|
+
* @module @carljia/omd-dsh
|
|
5
|
+
*
|
|
6
|
+
* omd-dsh host row (package root entry, injected by cordis.patch.yml):
|
|
7
|
+
*
|
|
8
|
+
* 1. registers the `omd-model-allocation` settings namespace (base = the
|
|
9
|
+
* shipped default matrix, `applies: "live"`), making the settings
|
|
10
|
+
* document (<DSH_HOME>/settings.yaml) the authoritative "online edit"
|
|
11
|
+
* store for the model matrix;
|
|
12
|
+
* 2. reconciles once at startup: if <DSH_HOME>/omd-matrix.json exists and
|
|
13
|
+
* differs from the namespace's resolved value, its content is imported
|
|
14
|
+
* into the namespace (`replace`) — CLI edits and pre-settings installs
|
|
15
|
+
* survive the migration, and CLI edits are picked up on the next restart;
|
|
16
|
+
* 3. renders the 7 presets from the namespace's resolved matrix and mirrors
|
|
17
|
+
* that matrix back to omd-matrix.json (the export mirror + CLI face);
|
|
18
|
+
* 4. watches the namespace: every UI save re-renders the presets and
|
|
19
|
+
* re-mirrors the file. New sessions route with the new matrix; running
|
|
20
|
+
* sessions keep the combination they started with.
|
|
21
|
+
*
|
|
22
|
+
* The browser half (lib/client.js) binds the same namespace and renders the
|
|
23
|
+
* "omd模型分配" settings section (see src/client.tsx). Because the client
|
|
24
|
+
* plugin discovery (dsh-client-modules) matches loader entries by package
|
|
25
|
+
* name, the bundle patch injects THIS row under the bare package name
|
|
26
|
+
* `@carljia/omd-dsh` — not a `/boot` subpath.
|
|
27
|
+
*
|
|
28
|
+
* Failure policy: a broken settings document / namespace must never brick the
|
|
29
|
+
* host's boot — any failure here falls back to the plain file sync (the old
|
|
30
|
+
* boot behavior) and is logged.
|
|
31
|
+
*/
|
|
32
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
33
|
+
export const name = "omd-dsh";
|
|
34
|
+
/** Requires the settings service (provided by dsh-base → dsh-settings-file in every profile). */
|
|
35
|
+
export const inject = ["settings"];
|
|
36
|
+
const NS = settingsNamespace("omd-model-allocation");
|
|
37
|
+
/**
|
|
38
|
+
* Startup reconciliation decision (pure, unit-tested): the file matrix is
|
|
39
|
+
* imported into the settings namespace exactly when it exists AND differs
|
|
40
|
+
* from the namespace's currently resolved value. Equal values need no write;
|
|
41
|
+
* a missing file leaves the namespace (defaults / user section) authoritative.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldImportFile(fileMatrix, resolved) {
|
|
44
|
+
return fileMatrix !== undefined && !matrixEquals(fileMatrix, resolved);
|
|
45
|
+
}
|
|
46
|
+
function makeLog(ctx) {
|
|
47
|
+
return {
|
|
48
|
+
info: (message) => { try {
|
|
49
|
+
ctx?.logger?.info?.(message);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
console.log(message);
|
|
53
|
+
} },
|
|
54
|
+
error: (message) => { try {
|
|
55
|
+
ctx?.logger?.error?.(message);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.error(message);
|
|
59
|
+
} },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export async function apply(ctx) {
|
|
63
|
+
const log = makeLog(ctx);
|
|
64
|
+
const flags = { dryRun: false, verbose: false };
|
|
65
|
+
try {
|
|
66
|
+
const settings = ctx.settings; // SettingsProvider (dsh-base provides it in every profile)
|
|
67
|
+
const scope = settings.register(NS, MatrixSchema, {
|
|
68
|
+
base: readDefaultMatrix(),
|
|
69
|
+
applies: "live",
|
|
70
|
+
});
|
|
71
|
+
// 启动期调和:文件 → 命名空间(幂等;导入 CLI/旧版自定义,CLI 手改在下次重启被采纳)
|
|
72
|
+
const fileMatrix = readMatrixFileIfExists();
|
|
73
|
+
if (shouldImportFile(fileMatrix, scope.get())) {
|
|
74
|
+
log.info("omd-dsh: importing " + MATRIX_PATH + " into settings namespace " + NS);
|
|
75
|
+
await scope.replace(fileMatrix);
|
|
76
|
+
}
|
|
77
|
+
// 用命名空间解析出的完整矩阵渲染 presets,并把该矩阵写回镜像文件
|
|
78
|
+
const resolved = scope.get();
|
|
79
|
+
saveMatrix(resolved);
|
|
80
|
+
await runSyncWithMatrix(resolved, flags, log.info);
|
|
81
|
+
// 后续 UI 编辑:重渲染 + 更新镜像(watch 仅在解析值真正变化时触发)
|
|
82
|
+
scope.watch((next) => {
|
|
83
|
+
log.info("omd-dsh: matrix changed via settings — re-syncing presets and mirroring omd-matrix.json");
|
|
84
|
+
try {
|
|
85
|
+
saveMatrix(next);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
log.error("omd-dsh: failed to mirror " + MATRIX_PATH + ": " + (error instanceof Error ? error.message : String(error)));
|
|
89
|
+
}
|
|
90
|
+
runSyncWithMatrix(next, flags, log.info).catch((error) => log.error("omd-dsh re-sync failed: " + (error instanceof Error ? error.message : String(error))));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// settings 不可用 / 命名空间已损坏时退回纯文件 sync(保持老 boot 行为)
|
|
95
|
+
log.error("omd-dsh: settings namespace failed, falling back to file sync: " + (error instanceof Error ? error.message : String(error)));
|
|
96
|
+
try {
|
|
97
|
+
await runSync(flags, log.info);
|
|
98
|
+
}
|
|
99
|
+
catch (fallbackError) {
|
|
100
|
+
log.error("omd-dsh: file sync fallback failed: " + (fallbackError instanceof Error ? fallbackError.message : String(fallbackError)));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Matrix } from "./sync.js";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side edit state machine for the omd model matrix — deliberately free
|
|
4
|
+
* of React and DOM so it can be unit-tested against fake wire faces
|
|
5
|
+
* (test/client-store.test.ts).
|
|
6
|
+
*
|
|
7
|
+
* The controller mirrors one settings namespace scope (`ctx.settingsScope`
|
|
8
|
+
* bind) and writes through `api.settings.replace` with the scope's revision
|
|
9
|
+
* as `expectedRevision` — a stale editor is refused rather than silently
|
|
10
|
+
* overwriting a concurrent change (`settings-rejected` on the wire).
|
|
11
|
+
*/
|
|
12
|
+
/** Settings namespace the host row registers (src/host.ts) — must stay in step. */
|
|
13
|
+
export declare const OMD_SETTINGS_NS = "omd-model-allocation";
|
|
14
|
+
/** Client-side sync state of one settings namespace (wire-contract subset). */
|
|
15
|
+
export interface OmdScopeSnapshot {
|
|
16
|
+
status: "loading" | "ready" | "unavailable";
|
|
17
|
+
value: Matrix | undefined;
|
|
18
|
+
revision: number | undefined;
|
|
19
|
+
writable: boolean;
|
|
20
|
+
mode: "host" | "memory";
|
|
21
|
+
}
|
|
22
|
+
/** The `settings.replace` wire face this controller needs. */
|
|
23
|
+
export interface OmdSettingsApi {
|
|
24
|
+
settings: {
|
|
25
|
+
replace(request: {
|
|
26
|
+
ns: string;
|
|
27
|
+
section: object;
|
|
28
|
+
expectedRevision?: number;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
result: {
|
|
31
|
+
ok: true;
|
|
32
|
+
value: unknown;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
result: {
|
|
36
|
+
ok: false;
|
|
37
|
+
error: {
|
|
38
|
+
message: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The shared describe mirror face (used to reload Host state after a rejected write). */
|
|
45
|
+
export interface OmdDescribeFace {
|
|
46
|
+
load(): Promise<unknown>;
|
|
47
|
+
}
|
|
48
|
+
/** The bound scope as the controller consumes it. */
|
|
49
|
+
export interface OmdScope {
|
|
50
|
+
getSnapshot(): OmdScopeSnapshot;
|
|
51
|
+
subscribe(listener: () => void): () => void;
|
|
52
|
+
}
|
|
53
|
+
export interface OmdControllerSnapshot {
|
|
54
|
+
draft: Matrix | null;
|
|
55
|
+
status: "idle" | "saving";
|
|
56
|
+
error: string | null;
|
|
57
|
+
saved: boolean;
|
|
58
|
+
}
|
|
59
|
+
type ModeField = "provider" | "model" | "reasoningEffort";
|
|
60
|
+
export declare class OmdMatrixController {
|
|
61
|
+
private readonly api;
|
|
62
|
+
private readonly scope;
|
|
63
|
+
private readonly describe;
|
|
64
|
+
draft: Matrix | null;
|
|
65
|
+
status: "idle" | "saving";
|
|
66
|
+
error: string | null;
|
|
67
|
+
saved: boolean;
|
|
68
|
+
private readonly listeners;
|
|
69
|
+
private readonly unsubscribeScope;
|
|
70
|
+
/**
|
|
71
|
+
* Cached snapshot handed to `useSyncExternalStore`. React requires
|
|
72
|
+
* `getSnapshot()` to return the SAME reference between changes — a fresh
|
|
73
|
+
* object per call throws "The result of getSnapshot should be cached" and
|
|
74
|
+
* crashes the whole settings entry (blank page). Rebuilt only in `emit()`.
|
|
75
|
+
*/
|
|
76
|
+
private snapshot;
|
|
77
|
+
constructor(api: OmdSettingsApi, scope: OmdScope, describe: OmdDescribeFace);
|
|
78
|
+
/** Release the scope subscription (plugin fiber teardown). */
|
|
79
|
+
dispose(): void;
|
|
80
|
+
/** Stable snapshot reference (uSES-safe): the SAME object until state changes. */
|
|
81
|
+
getSnapshot(): OmdControllerSnapshot;
|
|
82
|
+
subscribe(listener: () => void): () => void;
|
|
83
|
+
private emit;
|
|
84
|
+
/** External change (mirror reload after a rejected write, another window):
|
|
85
|
+
* rebuild the draft from the fresh value, unless a save is in flight. */
|
|
86
|
+
private onScopeChange;
|
|
87
|
+
private refreshDraft;
|
|
88
|
+
/** Whether the Save / Reset controls may run. */
|
|
89
|
+
get canSave(): boolean;
|
|
90
|
+
/** Whether the namespace accepts writes at all (read-only provider / memory mode). */
|
|
91
|
+
get writable(): boolean;
|
|
92
|
+
/** Edit one mode's top-level field; an empty string removes the override
|
|
93
|
+
* (the field re-inherits the composition base / schema default). */
|
|
94
|
+
patchMode(modeId: string, field: ModeField, value: string): void;
|
|
95
|
+
/** Edit one tier's provider/model; an empty string removes the override. */
|
|
96
|
+
patchTier(modeId: string, tier: string, field: "provider" | "model", value: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* Persist the whole draft matrix through `settings.replace` (wholesale
|
|
99
|
+
* section replacement — the removal/reset path merge cannot express). The
|
|
100
|
+
* revision fence makes a stale editor fail loudly instead of overwriting.
|
|
101
|
+
* @returns once the write settled and the snapshot reflects the outcome.
|
|
102
|
+
*/
|
|
103
|
+
save(): Promise<void>;
|
|
104
|
+
/** Restore the shipped default matrix: replace with an empty section. */
|
|
105
|
+
reset(): Promise<void>;
|
|
106
|
+
private replace;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side edit state machine for the omd model matrix — deliberately free
|
|
3
|
+
* of React and DOM so it can be unit-tested against fake wire faces
|
|
4
|
+
* (test/client-store.test.ts).
|
|
5
|
+
*
|
|
6
|
+
* The controller mirrors one settings namespace scope (`ctx.settingsScope`
|
|
7
|
+
* bind) and writes through `api.settings.replace` with the scope's revision
|
|
8
|
+
* as `expectedRevision` — a stale editor is refused rather than silently
|
|
9
|
+
* overwriting a concurrent change (`settings-rejected` on the wire).
|
|
10
|
+
*/
|
|
11
|
+
/** Settings namespace the host row registers (src/host.ts) — must stay in step. */
|
|
12
|
+
export const OMD_SETTINGS_NS = "omd-model-allocation";
|
|
13
|
+
function cloneMatrix(value) {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return null;
|
|
16
|
+
return JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
function messageOf(error) {
|
|
19
|
+
return error instanceof Error ? error.message : String(error);
|
|
20
|
+
}
|
|
21
|
+
export class OmdMatrixController {
|
|
22
|
+
api;
|
|
23
|
+
scope;
|
|
24
|
+
describe;
|
|
25
|
+
draft;
|
|
26
|
+
status = "idle";
|
|
27
|
+
error = null;
|
|
28
|
+
saved = false;
|
|
29
|
+
listeners = new Set();
|
|
30
|
+
unsubscribeScope;
|
|
31
|
+
/**
|
|
32
|
+
* Cached snapshot handed to `useSyncExternalStore`. React requires
|
|
33
|
+
* `getSnapshot()` to return the SAME reference between changes — a fresh
|
|
34
|
+
* object per call throws "The result of getSnapshot should be cached" and
|
|
35
|
+
* crashes the whole settings entry (blank page). Rebuilt only in `emit()`.
|
|
36
|
+
*/
|
|
37
|
+
snapshot;
|
|
38
|
+
constructor(api, scope, describe) {
|
|
39
|
+
this.api = api;
|
|
40
|
+
this.scope = scope;
|
|
41
|
+
this.describe = describe;
|
|
42
|
+
this.draft = cloneMatrix(scope.getSnapshot().value);
|
|
43
|
+
this.snapshot = { draft: this.draft, status: this.status, error: this.error, saved: this.saved };
|
|
44
|
+
this.unsubscribeScope = scope.subscribe(() => this.onScopeChange());
|
|
45
|
+
}
|
|
46
|
+
/** Release the scope subscription (plugin fiber teardown). */
|
|
47
|
+
dispose() {
|
|
48
|
+
this.unsubscribeScope();
|
|
49
|
+
this.listeners.clear();
|
|
50
|
+
}
|
|
51
|
+
/** Stable snapshot reference (uSES-safe): the SAME object until state changes. */
|
|
52
|
+
getSnapshot() {
|
|
53
|
+
return this.snapshot;
|
|
54
|
+
}
|
|
55
|
+
subscribe(listener) {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => { this.listeners.delete(listener); };
|
|
58
|
+
}
|
|
59
|
+
emit() {
|
|
60
|
+
this.snapshot = { draft: this.draft, status: this.status, error: this.error, saved: this.saved };
|
|
61
|
+
for (const listener of [...this.listeners])
|
|
62
|
+
listener();
|
|
63
|
+
}
|
|
64
|
+
/** External change (mirror reload after a rejected write, another window):
|
|
65
|
+
* rebuild the draft from the fresh value, unless a save is in flight. */
|
|
66
|
+
onScopeChange() {
|
|
67
|
+
if (this.status === "saving")
|
|
68
|
+
return;
|
|
69
|
+
this.refreshDraft();
|
|
70
|
+
this.emit();
|
|
71
|
+
}
|
|
72
|
+
refreshDraft() {
|
|
73
|
+
const value = this.scope.getSnapshot().value;
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
this.draft = cloneMatrix(value);
|
|
76
|
+
}
|
|
77
|
+
/** Whether the Save / Reset controls may run. */
|
|
78
|
+
get canSave() {
|
|
79
|
+
const snap = this.scope.getSnapshot();
|
|
80
|
+
return snap.status === "ready" && snap.writable && this.status !== "saving" && this.draft !== null;
|
|
81
|
+
}
|
|
82
|
+
/** Whether the namespace accepts writes at all (read-only provider / memory mode). */
|
|
83
|
+
get writable() {
|
|
84
|
+
return this.scope.getSnapshot().writable;
|
|
85
|
+
}
|
|
86
|
+
/** Edit one mode's top-level field; an empty string removes the override
|
|
87
|
+
* (the field re-inherits the composition base / schema default). */
|
|
88
|
+
patchMode(modeId, field, value) {
|
|
89
|
+
const draft = this.draft;
|
|
90
|
+
if (draft === null)
|
|
91
|
+
return;
|
|
92
|
+
const modes = { ...draft.modes };
|
|
93
|
+
const cfg = { ...(modes[modeId] ?? {}) };
|
|
94
|
+
if (value === "")
|
|
95
|
+
delete cfg[field];
|
|
96
|
+
else
|
|
97
|
+
cfg[field] = value;
|
|
98
|
+
modes[modeId] = cfg;
|
|
99
|
+
this.draft = { ...draft, modes };
|
|
100
|
+
this.saved = false;
|
|
101
|
+
this.emit();
|
|
102
|
+
}
|
|
103
|
+
/** Edit one tier's provider/model; an empty string removes the override. */
|
|
104
|
+
patchTier(modeId, tier, field, value) {
|
|
105
|
+
const draft = this.draft;
|
|
106
|
+
if (draft === null)
|
|
107
|
+
return;
|
|
108
|
+
const modes = { ...draft.modes };
|
|
109
|
+
const cfg = { ...(modes[modeId] ?? {}) };
|
|
110
|
+
const tiers = { ...(cfg.tiers ?? {}) };
|
|
111
|
+
const tierCfg = { ...(tiers[tier] ?? {}) };
|
|
112
|
+
if (value === "")
|
|
113
|
+
delete tierCfg[field];
|
|
114
|
+
else
|
|
115
|
+
tierCfg[field] = value;
|
|
116
|
+
tiers[tier] = tierCfg;
|
|
117
|
+
cfg.tiers = tiers;
|
|
118
|
+
modes[modeId] = cfg;
|
|
119
|
+
this.draft = { ...draft, modes };
|
|
120
|
+
this.saved = false;
|
|
121
|
+
this.emit();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Persist the whole draft matrix through `settings.replace` (wholesale
|
|
125
|
+
* section replacement — the removal/reset path merge cannot express). The
|
|
126
|
+
* revision fence makes a stale editor fail loudly instead of overwriting.
|
|
127
|
+
* @returns once the write settled and the snapshot reflects the outcome.
|
|
128
|
+
*/
|
|
129
|
+
async save() {
|
|
130
|
+
if (!this.canSave || this.draft === null)
|
|
131
|
+
return;
|
|
132
|
+
const snap = this.scope.getSnapshot();
|
|
133
|
+
this.status = "saving";
|
|
134
|
+
this.error = null;
|
|
135
|
+
this.emit();
|
|
136
|
+
const response = await this.replace({ section: this.draft, revision: snap.revision });
|
|
137
|
+
if (response.ok)
|
|
138
|
+
this.saved = true;
|
|
139
|
+
else {
|
|
140
|
+
this.error = response.message;
|
|
141
|
+
await this.describe.load();
|
|
142
|
+
}
|
|
143
|
+
this.refreshDraft();
|
|
144
|
+
this.status = "idle";
|
|
145
|
+
this.emit();
|
|
146
|
+
}
|
|
147
|
+
/** Restore the shipped default matrix: replace with an empty section. */
|
|
148
|
+
async reset() {
|
|
149
|
+
if (!this.canSave)
|
|
150
|
+
return;
|
|
151
|
+
const snap = this.scope.getSnapshot();
|
|
152
|
+
this.status = "saving";
|
|
153
|
+
this.error = null;
|
|
154
|
+
this.emit();
|
|
155
|
+
const response = await this.replace({ section: {}, revision: snap.revision });
|
|
156
|
+
if (response.ok)
|
|
157
|
+
this.saved = true;
|
|
158
|
+
else
|
|
159
|
+
this.error = response.message;
|
|
160
|
+
await this.describe.load();
|
|
161
|
+
this.refreshDraft();
|
|
162
|
+
this.status = "idle";
|
|
163
|
+
this.emit();
|
|
164
|
+
}
|
|
165
|
+
async replace(request) {
|
|
166
|
+
try {
|
|
167
|
+
const response = await this.api.settings.replace({
|
|
168
|
+
ns: OMD_SETTINGS_NS,
|
|
169
|
+
section: request.section,
|
|
170
|
+
...(request.revision === undefined ? {} : { expectedRevision: request.revision }),
|
|
171
|
+
});
|
|
172
|
+
if (response.result.ok)
|
|
173
|
+
return { ok: true };
|
|
174
|
+
return { ok: false, message: response.result.error.message };
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return { ok: false, message: messageOf(error) };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/lib/sync.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
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`)
|
|
5
|
-
* + restart)
|
|
6
|
-
* model matrix, and copy the presets plus
|
|
7
|
-
* .agent-presets.
|
|
4
|
+
* Shared by the CLI (`omd-dsh sync`), the bundle boot row (`dsh plugin add`
|
|
5
|
+
* + restart), and the host settings row (lib/host.js): render each preset's
|
|
6
|
+
* omd-mode / omd-task rows from the model matrix, and copy the presets plus
|
|
7
|
+
* the vendored row modules into .agent-presets.
|
|
8
|
+
*
|
|
9
|
+
* The matrix has two faces: `runSync` reads/writes the CLI face
|
|
10
|
+
* (<DSH_HOME>/omd-matrix.json, the `omd-dsh setup` target), while the host
|
|
11
|
+
* settings row resolves the matrix from the settings namespace
|
|
12
|
+
* (`omd-model-allocation` in settings.yaml) and feeds it to
|
|
13
|
+
* `runSyncWithMatrix` — the file becomes an exported mirror of the namespace
|
|
14
|
+
* plus the CLI compatibility face (see src/host.ts).
|
|
8
15
|
*
|
|
9
16
|
* The vendored rows are SELF-CONTAINED bundles (see scripts/postbuild.mjs):
|
|
10
17
|
* every dependency is bundled in and they carry no @deepseek-ai imports, so
|
|
@@ -48,6 +55,26 @@ export declare const VENDOR_SOURCES: string[];
|
|
|
48
55
|
/** User-owned model matrix: lives under DSH_HOME, never inside the package or the repo. */
|
|
49
56
|
export declare const MATRIX_PATH: string;
|
|
50
57
|
export declare function dshHome(): string;
|
|
58
|
+
export declare const MatrixSchema: any;
|
|
59
|
+
export declare function readDefaultMatrix(): Matrix;
|
|
60
|
+
/**
|
|
61
|
+
* Read the user's matrix file (<DSH_HOME>/omd-matrix.json) without touching
|
|
62
|
+
* the filesystem beyond the read: missing or malformed returns undefined and
|
|
63
|
+
* NEVER auto-generates — the caller decides what to fall back to (settings
|
|
64
|
+
* namespace resolved value / default matrix). This is the host row's
|
|
65
|
+
* "import CLI / legacy edits into the settings namespace" read.
|
|
66
|
+
*/
|
|
67
|
+
export declare function readMatrixFileIfExists(): Matrix | undefined;
|
|
68
|
+
/** Structural equality over the small JSON matrix (stringify equality suffices). */
|
|
69
|
+
export declare function matrixEquals(a: Matrix, b: Matrix): boolean;
|
|
51
70
|
export declare function loadMatrix(flags: SyncFlags, log?: (msg: string) => void): Matrix;
|
|
52
71
|
export declare function saveMatrix(m: Matrix): void;
|
|
72
|
+
/**
|
|
73
|
+
* Render and materialize the presets from an ALREADY-RESOLVED matrix. This is
|
|
74
|
+
* the shared core between the CLI file path (`runSync`) and the settings
|
|
75
|
+
* namespace path (host row): the matrix comes from the caller, everything
|
|
76
|
+
* after `loadMatrix(...)` is here.
|
|
77
|
+
*/
|
|
78
|
+
export declare function runSyncWithMatrix(matrix: Matrix, flags: SyncFlags, log?: (msg: string) => void): Promise<void>;
|
|
79
|
+
/** CLI / manual-fallback path: load the matrix from <DSH_HOME>/omd-matrix.json (generating/migrating it on first run), then render. */
|
|
53
80
|
export declare function runSync(flags: SyncFlags, log?: (msg: string) => void): Promise<void>;
|
package/lib/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import z from "@deepseek-ai/schemastery";
|
|
6
7
|
export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
8
|
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"];
|
|
8
9
|
/** User-owned model matrix: lives under DSH_HOME, never inside the package or the repo. */
|
|
@@ -45,6 +46,32 @@ async function collectSourceFiles(rootDir) {
|
|
|
45
46
|
}
|
|
46
47
|
// ── matrix ──
|
|
47
48
|
const DEFAULT_MATRIX_PATH = join(PACKAGE_ROOT, "omd-matrix.default.json");
|
|
49
|
+
/** Settings-namespace schema for the model matrix (see plan §5). Every field
|
|
50
|
+
* is optional: `base` (the shipped default matrix) fills whatever the user
|
|
51
|
+
* document omits, and extra keys (future fields) pass through untouched. */
|
|
52
|
+
const TierSchema = z.object({
|
|
53
|
+
provider: z.string(),
|
|
54
|
+
model: z.string(),
|
|
55
|
+
hint: z.string(),
|
|
56
|
+
persona: z.string(),
|
|
57
|
+
maxTokens: z.number(),
|
|
58
|
+
toolFilter: z.object({
|
|
59
|
+
allow: z.array(z.string()),
|
|
60
|
+
deny: z.array(z.string()),
|
|
61
|
+
denyShell: z.boolean(),
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const ModeSchema = z.object({
|
|
65
|
+
provider: z.string(),
|
|
66
|
+
model: z.string(),
|
|
67
|
+
reasoningEffort: z.string(),
|
|
68
|
+
tiers: z.dict(TierSchema),
|
|
69
|
+
});
|
|
70
|
+
export const MatrixSchema = z.object({
|
|
71
|
+
version: z.number(),
|
|
72
|
+
defaults: z.object({ provider: z.string() }),
|
|
73
|
+
modes: z.dict(ModeSchema),
|
|
74
|
+
});
|
|
48
75
|
function parseMatrix(text) {
|
|
49
76
|
try {
|
|
50
77
|
const parsed = JSON.parse(text);
|
|
@@ -54,7 +81,7 @@ function parseMatrix(text) {
|
|
|
54
81
|
catch { /* fall through */ }
|
|
55
82
|
return undefined;
|
|
56
83
|
}
|
|
57
|
-
function readDefaultMatrix() {
|
|
84
|
+
export function readDefaultMatrix() {
|
|
58
85
|
if (!existsSync(DEFAULT_MATRIX_PATH))
|
|
59
86
|
throw new Error("omd-dsh: missing default matrix file " + DEFAULT_MATRIX_PATH + " (broken package — reinstall @carljia/omd-dsh)");
|
|
60
87
|
const parsed = parseMatrix(readFileSync(DEFAULT_MATRIX_PATH, "utf8"));
|
|
@@ -62,6 +89,27 @@ function readDefaultMatrix() {
|
|
|
62
89
|
throw new Error("omd-dsh: malformed default matrix file " + DEFAULT_MATRIX_PATH + " (broken package — reinstall @carljia/omd-dsh)");
|
|
63
90
|
return parsed;
|
|
64
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Read the user's matrix file (<DSH_HOME>/omd-matrix.json) without touching
|
|
94
|
+
* the filesystem beyond the read: missing or malformed returns undefined and
|
|
95
|
+
* NEVER auto-generates — the caller decides what to fall back to (settings
|
|
96
|
+
* namespace resolved value / default matrix). This is the host row's
|
|
97
|
+
* "import CLI / legacy edits into the settings namespace" read.
|
|
98
|
+
*/
|
|
99
|
+
export function readMatrixFileIfExists() {
|
|
100
|
+
if (!existsSync(MATRIX_PATH))
|
|
101
|
+
return undefined;
|
|
102
|
+
try {
|
|
103
|
+
return parseMatrix(readFileSync(MATRIX_PATH, "utf8"));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Structural equality over the small JSON matrix (stringify equality suffices). */
|
|
110
|
+
export function matrixEquals(a, b) {
|
|
111
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
112
|
+
}
|
|
65
113
|
function writeMatrixFile(path, text) {
|
|
66
114
|
mkdirSync(dirname(path), { recursive: true });
|
|
67
115
|
writeFileSync(path, text, "utf8");
|
|
@@ -192,8 +240,13 @@ async function locallyModified(dir, meta) {
|
|
|
192
240
|
}
|
|
193
241
|
return undefined;
|
|
194
242
|
}
|
|
195
|
-
|
|
196
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Render and materialize the presets from an ALREADY-RESOLVED matrix. This is
|
|
245
|
+
* the shared core between the CLI file path (`runSync`) and the settings
|
|
246
|
+
* namespace path (host row): the matrix comes from the caller, everything
|
|
247
|
+
* after `loadMatrix(...)` is here.
|
|
248
|
+
*/
|
|
249
|
+
export async function runSyncWithMatrix(matrix, flags, log = console.log) {
|
|
197
250
|
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
198
251
|
const sourceVersion = manifest.version;
|
|
199
252
|
const presetsSourceDir = join(PACKAGE_ROOT, "presets");
|
|
@@ -343,3 +396,7 @@ export async function runSync(flags, log = console.log) {
|
|
|
343
396
|
const summary = ["synced", "updated", "conflicts", "orphan", "removed"].map((key) => report[key].length + " " + key).join(", ");
|
|
344
397
|
log("omd-dsh sync: " + summary + (flags.dryRun ? " (dry-run)" : ""));
|
|
345
398
|
}
|
|
399
|
+
/** CLI / manual-fallback path: load the matrix from <DSH_HOME>/omd-matrix.json (generating/migrating it on first run), then render. */
|
|
400
|
+
export async function runSync(flags, log = console.log) {
|
|
401
|
+
await runSyncWithMatrix(loadMatrix(flags, log), flags, log);
|
|
402
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carljia/omd-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "OMD 理念的 DeepSeek Harness 插件:模式能力边界 + 按模式配模型 + tier 差异化子代理委派",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -24,10 +24,14 @@
|
|
|
24
24
|
"node": ">=20"
|
|
25
25
|
},
|
|
26
26
|
"type": "module",
|
|
27
|
-
"main": "lib/
|
|
28
|
-
"types": "lib/
|
|
27
|
+
"main": "lib/host.js",
|
|
28
|
+
"types": "lib/host.d.ts",
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
31
|
+
"types": "./lib/host.d.ts",
|
|
32
|
+
"default": "./lib/host.js"
|
|
33
|
+
},
|
|
34
|
+
"./omd-mode": {
|
|
31
35
|
"types": "./lib/index.d.ts",
|
|
32
36
|
"default": "./lib/index.js"
|
|
33
37
|
},
|
|
@@ -39,6 +43,7 @@
|
|
|
39
43
|
"types": "./lib/boot.d.ts",
|
|
40
44
|
"default": "./lib/boot.js"
|
|
41
45
|
},
|
|
46
|
+
"./client": "./lib/client.js",
|
|
42
47
|
"./package.json": "./package.json"
|
|
43
48
|
},
|
|
44
49
|
"bin": {
|
|
@@ -47,6 +52,10 @@
|
|
|
47
52
|
"dsh": {
|
|
48
53
|
"bundle": {
|
|
49
54
|
"patch": "./cordis.patch.yml"
|
|
55
|
+
},
|
|
56
|
+
"client": {
|
|
57
|
+
"platform": "web",
|
|
58
|
+
"inject": []
|
|
50
59
|
}
|
|
51
60
|
},
|
|
52
61
|
"files": [
|
|
@@ -62,15 +71,21 @@
|
|
|
62
71
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
63
72
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
64
73
|
"@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
65
75
|
"@deepseek-ai/dsh-subagent": "^0.1.1-rc.2",
|
|
66
76
|
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
|
|
67
77
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
68
78
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
69
79
|
},
|
|
70
80
|
"devDependencies": {
|
|
81
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
71
82
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
72
83
|
"@types/node": "^24.0.0",
|
|
84
|
+
"@types/react": "^18.3.1",
|
|
85
|
+
"@types/react-dom": "^18.3.0",
|
|
73
86
|
"esbuild": "^0.28.2",
|
|
87
|
+
"react": "^18.2.0",
|
|
88
|
+
"react-dom": "^18.2.0",
|
|
74
89
|
"typescript": "^5.6.0",
|
|
75
90
|
"vitest": "^3.0.0"
|
|
76
91
|
},
|