@carljia/omd-dsh 0.1.7 → 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 +36 -15
- package/cordis.patch.yml +13 -6
- package/lib/boot.d.ts +4 -4
- package/lib/boot.js +6 -11
- package/lib/cli.js +9 -21
- 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/index.js +4 -5
- package/lib/mode.js +1 -4
- package/lib/omd-matrix-controller.d.ts +108 -0
- package/lib/omd-matrix-controller.js +180 -0
- package/lib/plan.js +1 -4
- package/lib/startwork.js +1 -4
- package/lib/sync.d.ts +38 -20
- package/lib/sync.js +68 -215
- package/lib/task.js +1 -4
- package/lib/vendor/omd-mode-switch.mjs +967 -127
- package/lib/vendor/omd-mode.mjs +889 -174
- package/lib/vendor/omd-plan.mjs +95 -177
- package/lib/vendor/omd-start-work.mjs +96 -132
- package/lib/vendor/omd-task.mjs +20735 -265
- package/lib/vendor/omd-ulw.mjs +44 -49
- package/package.json +23 -7
package/lib/sync.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { promises as fs, existsSync, mkdirSync,
|
|
1
|
+
import { promises as fs, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
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"];
|
|
9
9
|
/** User-owned model matrix: lives under DSH_HOME, never inside the package or the repo. */
|
|
@@ -16,210 +16,6 @@ const TASK_FENCE = { start: "# [omd-dsh:task:start]", end: "# [omd-dsh:task:end]
|
|
|
16
16
|
const RENAMED_FROM = { "omd-architect": "omd-ultraworker" };
|
|
17
17
|
function sha256(text) { return createHash("sha256").update(text).digest("hex"); }
|
|
18
18
|
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
19
|
function readMeta(dir) {
|
|
224
20
|
const metaPath = join(dir, ".omd-meta.json");
|
|
225
21
|
if (!existsSync(metaPath))
|
|
@@ -250,6 +46,32 @@ async function collectSourceFiles(rootDir) {
|
|
|
250
46
|
}
|
|
251
47
|
// ── matrix ──
|
|
252
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
|
+
});
|
|
253
75
|
function parseMatrix(text) {
|
|
254
76
|
try {
|
|
255
77
|
const parsed = JSON.parse(text);
|
|
@@ -259,7 +81,7 @@ function parseMatrix(text) {
|
|
|
259
81
|
catch { /* fall through */ }
|
|
260
82
|
return undefined;
|
|
261
83
|
}
|
|
262
|
-
function readDefaultMatrix() {
|
|
84
|
+
export function readDefaultMatrix() {
|
|
263
85
|
if (!existsSync(DEFAULT_MATRIX_PATH))
|
|
264
86
|
throw new Error("omd-dsh: missing default matrix file " + DEFAULT_MATRIX_PATH + " (broken package — reinstall @carljia/omd-dsh)");
|
|
265
87
|
const parsed = parseMatrix(readFileSync(DEFAULT_MATRIX_PATH, "utf8"));
|
|
@@ -267,6 +89,27 @@ function readDefaultMatrix() {
|
|
|
267
89
|
throw new Error("omd-dsh: malformed default matrix file " + DEFAULT_MATRIX_PATH + " (broken package — reinstall @carljia/omd-dsh)");
|
|
268
90
|
return parsed;
|
|
269
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
|
+
}
|
|
270
113
|
function writeMatrixFile(path, text) {
|
|
271
114
|
mkdirSync(dirname(path), { recursive: true });
|
|
272
115
|
writeFileSync(path, text, "utf8");
|
|
@@ -397,8 +240,13 @@ async function locallyModified(dir, meta) {
|
|
|
397
240
|
}
|
|
398
241
|
return undefined;
|
|
399
242
|
}
|
|
400
|
-
|
|
401
|
-
|
|
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) {
|
|
402
250
|
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
403
251
|
const sourceVersion = manifest.version;
|
|
404
252
|
const presetsSourceDir = join(PACKAGE_ROOT, "presets");
|
|
@@ -473,8 +321,9 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
473
321
|
if (!existsSync(sourceFile)) {
|
|
474
322
|
throw new Error("omd-dsh sync: missing vendored source " + sourceFile + " -- run \`npm run build\` first");
|
|
475
323
|
}
|
|
476
|
-
|
|
477
|
-
|
|
324
|
+
// Self-contained bundles: copied verbatim, no import rewriting, no
|
|
325
|
+
// harness tree knowledge (see the module doc comment).
|
|
326
|
+
const sourceText = await fs.readFile(sourceFile, "utf8");
|
|
478
327
|
const sourceHash = sha256(sourceText);
|
|
479
328
|
const destFile = join(vendorTargetDir, vendorName);
|
|
480
329
|
let action = "synced";
|
|
@@ -507,7 +356,7 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
507
356
|
}
|
|
508
357
|
if (!flags.dryRun && VENDOR_SOURCES.length > 0) {
|
|
509
358
|
await fs.mkdir(vendorTargetDir, { recursive: true });
|
|
510
|
-
await fs.writeFile(join(vendorTargetDir, ".omd-meta.json"), JSON.stringify({ source: "omd-dsh", sourceVersion,
|
|
359
|
+
await fs.writeFile(join(vendorTargetDir, ".omd-meta.json"), JSON.stringify({ source: "omd-dsh", sourceVersion, files: nextVendorFiles }, null, 2) + "\n", "utf8");
|
|
511
360
|
}
|
|
512
361
|
}
|
|
513
362
|
if (existsSync(agentPresetsRoot)) {
|
|
@@ -539,7 +388,7 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
539
388
|
}
|
|
540
389
|
log("omd-dsh sync: DSH_HOME=" + dshHome());
|
|
541
390
|
log("omd-dsh sync: matrix=" + MATRIX_PATH + " (customize the model matrix any time with \`omd-dsh setup\`)");
|
|
542
|
-
log("omd-dsh sync: harness
|
|
391
|
+
log("omd-dsh sync: vendored rows are self-contained bundles (no harness tree dependency)");
|
|
543
392
|
log("omd-dsh sync: source version=" + sourceVersion + (flags.dryRun ? " (dry-run)" : ""));
|
|
544
393
|
for (const key of ["synced", "updated", "skipped", "conflicts", "orphan", "removed"])
|
|
545
394
|
for (const line of report[key])
|
|
@@ -547,3 +396,7 @@ export async function runSync(flags, harnessNodeModules, log = console.log) {
|
|
|
547
396
|
const summary = ["synced", "updated", "conflicts", "orphan", "removed"].map((key) => report[key].length + " " + key).join(", ");
|
|
548
397
|
log("omd-dsh sync: " + summary + (flags.dryRun ? " (dry-run)" : ""));
|
|
549
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/lib/task.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
3
|
import { assertSubagentMaxDepth } from "@deepseek-ai/dsh-subagent";
|
|
4
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
5
4
|
import { modeOverrideFor } from "./shared.js";
|
|
6
5
|
/**
|
|
7
6
|
* @module @carljia/omd-dsh/task
|
|
@@ -136,9 +135,7 @@ function resolveTier(config, requested) {
|
|
|
136
135
|
throw new Error("omd_task: choose a tier for this task -- valid tiers: " + tierNames.join(", "));
|
|
137
136
|
}
|
|
138
137
|
function apply(ctx, config) {
|
|
139
|
-
|
|
140
|
-
throw new Error("omd-task: refusing to mount outside a scoped context; mount this row inside an agent preset");
|
|
141
|
-
}
|
|
138
|
+
// 无 scope 守卫:见 src/index.ts 的说明(自包含 bundle 无法读取 harness 的 kScope)。
|
|
142
139
|
const invalid = configError(config);
|
|
143
140
|
if (invalid !== undefined)
|
|
144
141
|
throw new Error(invalid);
|