dsh-toolfold 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1
- package/README.en.md +9 -7
- package/README.md +9 -7
- package/lib/client.js +2029 -1921
- package/lib/index.js +386 -128
- package/package.json +14 -3
- package/lib/build-dynamic.cjs +0 -69
- package/lib/dynamic-body.js +0 -1814
package/lib/index.js
CHANGED
|
@@ -1,137 +1,395 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import z from "schemastery";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
//#region src/host/version.js
|
|
1
6
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
* dsh-toolfold — version-range helpers (host side, zero dependencies).
|
|
8
|
+
*
|
|
9
|
+
* The single source of truth for the supported DSH product range is this
|
|
10
|
+
* package's own `engines.dsh` field (an npm-style range such as
|
|
11
|
+
* ">=0.1.2-rc.1 <0.1.3 || >=0.1.5-rc.1 <0.1.6"). The build stamps that
|
|
12
|
+
* field into the host artifact via tsdown `define` (__DSH_ENGINES__, see
|
|
13
|
+
* tsdown.config.mjs) and the host half judges the running DSH with the
|
|
14
|
+
* matcher below — no range is hardcoded anywhere, so widening support is
|
|
15
|
+
* a one-line package.json edit + rebuild.
|
|
16
|
+
*
|
|
17
|
+
* Supported range grammar (the subset npm engines ranges actually use):
|
|
18
|
+
* range := branch ("||" branch)*
|
|
19
|
+
* branch := comparator ((" " | ",") comparator)*
|
|
20
|
+
* comparator := (">=" | "<=" | ">" | "<" | "=" | "==")? version
|
|
21
|
+
* A bare version means "=". Anything else (caret/tilde/x-ranges, hyphen
|
|
22
|
+
* ranges, junk) invalidates its branch; a range with no valid branch
|
|
23
|
+
* matches nothing and the caller reports 'unknown' instead of crying wolf.
|
|
24
|
+
*
|
|
25
|
+
* Prerelease gating follows npm semver: a prerelease running version only
|
|
26
|
+
* satisfies a branch that names a prerelease on the same
|
|
27
|
+
* [major, minor, patch] tuple (e.g. 0.1.5-rc.1 satisfies
|
|
28
|
+
* ">=0.1.5-rc.1 <0.1.6", but 0.1.3-rc.1 does NOT satisfy
|
|
29
|
+
* ">=0.1.2-rc.1 <0.1.3").
|
|
30
|
+
*/
|
|
31
|
+
/** Parse "v1.2.3-rc.4+build" into comparable parts; null on junk. */
|
|
32
|
+
function parseVersion(value) {
|
|
33
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(String(value));
|
|
34
|
+
if (!match) return null;
|
|
35
|
+
return {
|
|
36
|
+
core: [
|
|
37
|
+
Number(match[1]),
|
|
38
|
+
Number(match[2]),
|
|
39
|
+
Number(match[3])
|
|
40
|
+
],
|
|
41
|
+
pre: match[4] === void 0 ? null : match[4].split(".")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Compare two parsed versions (semver precedence, prerelease-aware): -1/0/1. */
|
|
45
|
+
function compareParsed(a, b) {
|
|
46
|
+
for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] < b.core[i] ? -1 : 1;
|
|
47
|
+
if (a.pre === null && b.pre === null) return 0;
|
|
48
|
+
if (a.pre === null) return 1;
|
|
49
|
+
if (b.pre === null) return -1;
|
|
50
|
+
const len = Math.max(a.pre.length, b.pre.length);
|
|
51
|
+
for (let i = 0; i < len; i++) {
|
|
52
|
+
const x = a.pre[i];
|
|
53
|
+
const y = b.pre[i];
|
|
54
|
+
if (x === void 0) return -1;
|
|
55
|
+
if (y === void 0) return 1;
|
|
56
|
+
const xn = /^\d+$/.test(x);
|
|
57
|
+
const yn = /^\d+$/.test(y);
|
|
58
|
+
if (xn && yn) {
|
|
59
|
+
const dx = Number(x);
|
|
60
|
+
const dy = Number(y);
|
|
61
|
+
if (dx !== dy) return dx < dy ? -1 : 1;
|
|
62
|
+
} else if (xn !== yn) return xn ? -1 : 1;
|
|
63
|
+
else if (x !== y) return x < y ? -1 : 1;
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse one "||" branch into comparators; null when the branch is unusable.
|
|
69
|
+
* The operator may be separated from its version by whitespace (npm
|
|
70
|
+
* semver tolerates ">= 1.2.3"), so comparators are matched globally and
|
|
71
|
+
* the gaps between matches must contain only whitespace/commas — anything
|
|
72
|
+
* else invalidates the branch.
|
|
73
|
+
*/
|
|
74
|
+
function parseBranch(text) {
|
|
75
|
+
const src = String(text).trim();
|
|
76
|
+
if (src === "") return null;
|
|
77
|
+
const re = /(>=|<=|>|<|==|=)?\s*(v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/g;
|
|
78
|
+
const comps = [];
|
|
79
|
+
let pos = 0;
|
|
80
|
+
let m;
|
|
81
|
+
while ((m = re.exec(src)) !== null) {
|
|
82
|
+
if (m[0] === "") {
|
|
83
|
+
re.lastIndex++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!/^[\s,]*$/.test(src.slice(pos, m.index))) return null;
|
|
87
|
+
const v = parseVersion(m[2]);
|
|
88
|
+
if (v === null) return null;
|
|
89
|
+
comps.push({
|
|
90
|
+
op: m[1] === void 0 || m[1] === "==" ? "=" : m[1],
|
|
91
|
+
v
|
|
92
|
+
});
|
|
93
|
+
pos = m.index + m[0].length;
|
|
94
|
+
}
|
|
95
|
+
if (!/^[\s,]*$/.test(src.slice(pos))) return null;
|
|
96
|
+
return comps.length === 0 ? null : comps;
|
|
97
|
+
}
|
|
98
|
+
/** Parse a full range string into its valid branches; [] when unusable. */
|
|
99
|
+
function parseEnginesRange(rangeStr) {
|
|
100
|
+
if (typeof rangeStr !== "string") return [];
|
|
101
|
+
const branches = [];
|
|
102
|
+
for (const part of rangeStr.split("||")) {
|
|
103
|
+
const comps = parseBranch(part.trim());
|
|
104
|
+
if (comps !== null) branches.push(comps);
|
|
105
|
+
}
|
|
106
|
+
return branches;
|
|
107
|
+
}
|
|
108
|
+
function testComp(op, cmp) {
|
|
109
|
+
switch (op) {
|
|
110
|
+
case ">=": return cmp >= 0;
|
|
111
|
+
case "<=": return cmp <= 0;
|
|
112
|
+
case ">": return cmp > 0;
|
|
113
|
+
case "<": return cmp < 0;
|
|
114
|
+
default: return cmp === 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function sameCore(a, b) {
|
|
118
|
+
return a.core[0] === b.core[0] && a.core[1] === b.core[1] && a.core[2] === b.core[2];
|
|
119
|
+
}
|
|
120
|
+
/** npm semver prerelease gate for one branch. */
|
|
121
|
+
function gateOk(comps, ver) {
|
|
122
|
+
if (ver.pre === null) return true;
|
|
123
|
+
return comps.some((c) => c.v.pre !== null && sameCore(c.v, ver));
|
|
124
|
+
}
|
|
125
|
+
/** True when the parsed version satisfies at least one branch. */
|
|
126
|
+
function satisfiesEngines(parsed, branches) {
|
|
127
|
+
for (const comps of branches) {
|
|
128
|
+
let ok = true;
|
|
129
|
+
for (const c of comps) if (!testComp(c.op, compareParsed(parsed, c.v))) {
|
|
130
|
+
ok = false;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
if (ok && gateOk(comps, parsed)) return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/** Lowest lower bound across all branches (>=, >, =); null when unbounded. */
|
|
138
|
+
function lowestFloor(branches) {
|
|
139
|
+
let floor = null;
|
|
140
|
+
for (const comps of branches) for (const c of comps) if (c.op === ">=" || c.op === ">" || c.op === "=") {
|
|
141
|
+
if (floor === null || compareParsed(c.v, floor) < 0) floor = c.v;
|
|
142
|
+
}
|
|
143
|
+
return floor;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Judge a running version against a raw engines range string:
|
|
147
|
+
* 'ok' | 'old' | 'new' | 'unknown'. Never throws. Below every supported
|
|
148
|
+
* floor → 'old'; outside anywhere else (including between "||" gaps) →
|
|
149
|
+
* 'new'; unusable input on either side → 'unknown' (fail silent, never
|
|
150
|
+
* false-alarm).
|
|
151
|
+
*/
|
|
152
|
+
function compatFor(version, rangeStr) {
|
|
153
|
+
const parsed = parseVersion(version);
|
|
154
|
+
if (parsed === null) return "unknown";
|
|
155
|
+
const branches = parseEnginesRange(rangeStr);
|
|
156
|
+
if (branches.length === 0) return "unknown";
|
|
157
|
+
if (satisfiesEngines(parsed, branches)) return "ok";
|
|
158
|
+
const floor = lowestFloor(branches);
|
|
159
|
+
if (floor !== null && compareParsed(parsed, floor) < 0) return "old";
|
|
160
|
+
return "new";
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/host/index.js
|
|
164
|
+
/**
|
|
165
|
+
* dsh-toolfold — HOST half.
|
|
166
|
+
*
|
|
167
|
+
* Registers the `toolfold` settings namespace in the DSH settings service
|
|
168
|
+
* (persisted in `~/.dsh/settings.yaml`, the same document every product and
|
|
169
|
+
* family plugin uses) and serves the browser half through one same-origin
|
|
170
|
+
* JSON route:
|
|
171
|
+
*
|
|
172
|
+
* GET /api/dsh-toolfold/settings → { ok, value: { value, revision, writable } }
|
|
173
|
+
* POST /api/dsh-toolfold/settings → body { op: 'set'|'unset', field, value?,
|
|
174
|
+
* expectedRevision? }
|
|
175
|
+
* → { ok, value: { value, revision, writable } }
|
|
176
|
+
*
|
|
177
|
+
* Every success response additionally carries `dsh: { version, state, range }` —
|
|
178
|
+
* the running DSH product version, its compatibility with the supported
|
|
179
|
+
* range ('ok' | 'old' | 'new' | 'unknown'), and the raw `engines.dsh`
|
|
180
|
+
* requirement the verdict was judged against. DSH does not enforce
|
|
181
|
+
* `engines.dsh` anywhere, so the host half reports the mismatch itself
|
|
182
|
+
* and the settings card warns the user (once-only console warning +
|
|
183
|
+
* hover-tip icon quoting the live range).
|
|
184
|
+
*
|
|
185
|
+
* The browser half prefers the official `settingsScope` transport when the
|
|
186
|
+
* deployment exposes this namespace, then this route, then browser
|
|
187
|
+
* localStorage as a degraded fallback. `value` is always the fully resolved
|
|
188
|
+
* section (schema defaults + composition base + the user's settings.yaml
|
|
189
|
+
* overrides), so the client never re-implements the resolution.
|
|
190
|
+
*
|
|
191
|
+
* The same package also declares `dsh.bundle.patch` (see cordis.patch.yml),
|
|
192
|
+
* which makes `dsh plugin --profile <name> add <this package>` install AND
|
|
193
|
+
* mount both halves in one command.
|
|
194
|
+
*/
|
|
195
|
+
const name = "toolfold";
|
|
196
|
+
const inject = ["settings", "webServer"];
|
|
197
|
+
const NS = "toolfold";
|
|
198
|
+
const API_PATH = "/api/dsh-toolfold/settings";
|
|
199
|
+
const FIELDS = [
|
|
200
|
+
"enabled",
|
|
201
|
+
"durMs",
|
|
202
|
+
"thinkMode",
|
|
203
|
+
"keepThink",
|
|
204
|
+
"thinkAuto",
|
|
205
|
+
"splitThink",
|
|
206
|
+
"stats"
|
|
207
|
+
];
|
|
34
208
|
const SCHEMA = z.object({
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
209
|
+
enabled: z.boolean().default(true),
|
|
210
|
+
durMs: z.number().step(10).min(0).max(2e3).default(240),
|
|
211
|
+
thinkMode: z.union([
|
|
212
|
+
z.const("auto"),
|
|
213
|
+
z.const("keep"),
|
|
214
|
+
z.const("hide")
|
|
215
|
+
]),
|
|
216
|
+
keepThink: z.boolean().default(false),
|
|
217
|
+
thinkAuto: z.boolean().default(true),
|
|
218
|
+
splitThink: z.boolean().default(true),
|
|
219
|
+
stats: z.boolean().default(false)
|
|
220
|
+
});
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the canonical think mode for one section. An explicitly set,
|
|
223
|
+
* valid thinkMode wins; otherwise derive it from the deprecated keys so
|
|
224
|
+
* pre-merge preferences survive the upgrade; default 'auto'. Total —
|
|
225
|
+
* never throws. (Client twin: resolveThinkMode in src/client/settings.js.)
|
|
226
|
+
*/
|
|
227
|
+
function resolveThinkMode(section) {
|
|
228
|
+
var mode = section !== void 0 && section !== null ? section.thinkMode : void 0;
|
|
229
|
+
if (mode === "keep" || mode === "hide" || mode === "auto") return mode;
|
|
230
|
+
if (section !== void 0 && section !== null && section.thinkAuto === false) return section.keepThink === true ? "keep" : "hide";
|
|
231
|
+
return "auto";
|
|
232
|
+
}
|
|
233
|
+
const ENGINES_RANGE = ">=0.1.2-rc.1 <0.1.3 || >=0.1.5-rc.1 <0.1.6";
|
|
234
|
+
/**
|
|
235
|
+
* The @deepseek-ai/dsh version this process was launched from: process.argv[1]
|
|
236
|
+
* is the CLI's entry, and module resolution from that file reaches the
|
|
237
|
+
* @deepseek-ai/dsh package.json it belongs to. Null when undetectable
|
|
238
|
+
* (workers, dev launchers without a resolvable package).
|
|
239
|
+
*/
|
|
240
|
+
let cachedDshVersion;
|
|
241
|
+
function dshVersion() {
|
|
242
|
+
if (cachedDshVersion !== void 0) return cachedDshVersion;
|
|
243
|
+
cachedDshVersion = null;
|
|
244
|
+
try {
|
|
245
|
+
const entry = process.argv && process.argv[1];
|
|
246
|
+
if (!entry) return cachedDshVersion;
|
|
247
|
+
const pkgPath = createRequire(resolve(entry)).resolve("@deepseek-ai/dsh/package.json");
|
|
248
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
249
|
+
if (pkg && pkg.name === "@deepseek-ai/dsh" && typeof pkg.version === "string" && pkg.version !== "") cachedDshVersion = pkg.version;
|
|
250
|
+
} catch (error) {}
|
|
251
|
+
return cachedDshVersion;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Running DSH vs the supported range (see ./version.js): the report the
|
|
255
|
+
* settings card renders its warning icon from. `range` is the raw
|
|
256
|
+
* `engines.dsh` string so the card can quote the live requirement instead
|
|
257
|
+
* of hardcoding it: { version, state: 'ok'|'old'|'new'|'unknown', range }.
|
|
258
|
+
*/
|
|
259
|
+
function dshCompat() {
|
|
260
|
+
const version = dshVersion();
|
|
261
|
+
const range = ENGINES_RANGE;
|
|
262
|
+
if (version === null || false) return {
|
|
263
|
+
version,
|
|
264
|
+
state: "unknown",
|
|
265
|
+
range
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
version,
|
|
269
|
+
state: compatFor(version, range),
|
|
270
|
+
range
|
|
271
|
+
};
|
|
272
|
+
}
|
|
41
273
|
/** Write one JSON response. */
|
|
42
274
|
function json(res, status, body) {
|
|
43
|
-
|
|
44
|
-
|
|
275
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
276
|
+
res.end(JSON.stringify(body));
|
|
45
277
|
}
|
|
46
|
-
|
|
47
278
|
/** Read a JSON request body (bounded). */
|
|
48
279
|
function readJsonBody(req) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
let size = 0;
|
|
282
|
+
const chunks = [];
|
|
283
|
+
req.on("data", (chunk) => {
|
|
284
|
+
size += chunk.length;
|
|
285
|
+
if (size > 65536) {
|
|
286
|
+
reject(/* @__PURE__ */ new Error("body-too-large"));
|
|
287
|
+
req.destroy();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
chunks.push(chunk);
|
|
291
|
+
});
|
|
292
|
+
req.on("end", () => {
|
|
293
|
+
if (chunks.length === 0) {
|
|
294
|
+
resolve({});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
299
|
+
} catch {
|
|
300
|
+
reject(/* @__PURE__ */ new Error("invalid-json"));
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
req.on("error", reject);
|
|
304
|
+
});
|
|
74
305
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
306
|
+
function apply(ctx) {
|
|
307
|
+
ctx.settings.register(NS, SCHEMA, { base: {} });
|
|
308
|
+
/** Current resolved section + revision + writability, as one JSON view. */
|
|
309
|
+
const snapshot = () => {
|
|
310
|
+
let value = ctx.settings.get(NS);
|
|
311
|
+
let revision;
|
|
312
|
+
for (const descriptor of ctx.settings.describe()) if (descriptor.ns === NS) {
|
|
313
|
+
value = descriptor.value;
|
|
314
|
+
revision = descriptor.revision;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
value: {
|
|
320
|
+
value: {
|
|
321
|
+
...value,
|
|
322
|
+
thinkMode: resolveThinkMode(value)
|
|
323
|
+
},
|
|
324
|
+
...revision === void 0 ? {} : { revision },
|
|
325
|
+
writable: ctx.settings.writable
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
const handler = (req, res) => {
|
|
330
|
+
if (req.method === "GET") {
|
|
331
|
+
try {
|
|
332
|
+
json(res, 200, {
|
|
333
|
+
...snapshot(),
|
|
334
|
+
dsh: dshCompat()
|
|
335
|
+
});
|
|
336
|
+
} catch (error) {
|
|
337
|
+
json(res, 500, {
|
|
338
|
+
ok: false,
|
|
339
|
+
error: error instanceof Error ? error.message : String(error)
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (req.method === "POST") {
|
|
345
|
+
readJsonBody(req).then((body) => {
|
|
346
|
+
const field = String(body?.field ?? "");
|
|
347
|
+
if (!FIELDS.includes(field)) {
|
|
348
|
+
json(res, 400, {
|
|
349
|
+
ok: false,
|
|
350
|
+
error: "invalid-field"
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const ops = (body?.op === "unset" ? "unset" : "set") === "unset" ? [{
|
|
355
|
+
op: "unset",
|
|
356
|
+
path: [field]
|
|
357
|
+
}] : [{
|
|
358
|
+
op: "set",
|
|
359
|
+
path: [field],
|
|
360
|
+
value: body.value
|
|
361
|
+
}];
|
|
362
|
+
const expectedRevision = typeof body?.expectedRevision === "number" ? body.expectedRevision : void 0;
|
|
363
|
+
return ctx.settings.mutate(NS, ops, expectedRevision).then(() => json(res, 200, {
|
|
364
|
+
...snapshot(),
|
|
365
|
+
dsh: dshCompat()
|
|
366
|
+
})).catch((error) => json(res, 400, {
|
|
367
|
+
ok: false,
|
|
368
|
+
error: error instanceof Error ? error.message : String(error)
|
|
369
|
+
}));
|
|
370
|
+
}, (error) => {
|
|
371
|
+
json(res, 400, {
|
|
372
|
+
ok: false,
|
|
373
|
+
error: error instanceof Error ? error.message : String(error)
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
json(res, 405, {
|
|
379
|
+
ok: false,
|
|
380
|
+
error: "method-not-allowed"
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
ctx.effect(() => {
|
|
384
|
+
const dispose = ctx.webServer.register({
|
|
385
|
+
kind: "exact",
|
|
386
|
+
path: API_PATH,
|
|
387
|
+
handler
|
|
388
|
+
});
|
|
389
|
+
return () => dispose();
|
|
390
|
+
});
|
|
137
391
|
}
|
|
392
|
+
//#endregion
|
|
393
|
+
export { apply, inject, name };
|
|
394
|
+
|
|
395
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-toolfold",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "工具调用与思考的折叠显示:连续工具调用折叠为最后一个调用(可展开),已完成的思考随组折叠(可保留、展开时按原顺序显示),进行中的思考保持独立显示。",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,20 +27,31 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
|
-
"lib",
|
|
30
|
+
"lib/index.js",
|
|
31
|
+
"lib/client.js",
|
|
31
32
|
"cordis.patch.yml",
|
|
32
33
|
"scripts",
|
|
33
34
|
"README.md",
|
|
34
35
|
"README.en.md",
|
|
35
36
|
"CHANGELOG.md"
|
|
36
37
|
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"dsh": ">=0.1.2-rc.1 <0.1.3 || >=0.1.5-rc.1 <0.1.6"
|
|
40
|
+
},
|
|
37
41
|
"peerDependencies": {
|
|
38
|
-
"@deepseek-ai/dsh-settings": ">=0.1.0-rc.7 <0.1.1 || >=0.1.1-rc.0 <0.1.2",
|
|
39
42
|
"schemastery": "^3.18.0"
|
|
40
43
|
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"jsdom": "29.1.1",
|
|
46
|
+
"playwright": "1.61.1",
|
|
47
|
+
"tsdown": "^0.22.2"
|
|
48
|
+
},
|
|
41
49
|
"license": "MIT",
|
|
42
50
|
"scripts": {
|
|
51
|
+
"build": "tsdown",
|
|
52
|
+
"build:watch": "tsdown --watch",
|
|
43
53
|
"test:engine": "node tools/engine-smoke.mjs",
|
|
54
|
+
"test:version": "node tools/version-smoke.mjs",
|
|
44
55
|
"probe:live": "node tools/live-probe.mjs",
|
|
45
56
|
"install:dsh": "node scripts/install-dsh.cjs",
|
|
46
57
|
"uninstall:dsh": "node scripts/install-dsh.cjs uninstall"
|
package/lib/build-dynamic.cjs
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* build-dynamic.js — generate lib/dynamic-body.js (the `code.client` body for
|
|
3
|
-
* cordis_define) from lib/client.js.
|
|
4
|
-
*
|
|
5
|
-
* The dynamic runner wraps the body in `async () => { ... }` and evaluates it
|
|
6
|
-
* in a scope where `React` is a closure parameter, so the body must:
|
|
7
|
-
* - NOT redeclare `var React` (var hoisting would shadow the closure
|
|
8
|
-
* parameter and the settings card would silently never render);
|
|
9
|
-
* - end with `return plugin;` (a bare function body has no exports).
|
|
10
|
-
*
|
|
11
|
-
* Usage: node lib/build-dynamic.js
|
|
12
|
-
*/
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
|
|
18
|
-
const srcPath = path.join(__dirname, 'client.js');
|
|
19
|
-
const outPath = path.join(__dirname, 'dynamic-body.js');
|
|
20
|
-
|
|
21
|
-
let src = fs.readFileSync(srcPath, 'utf8');
|
|
22
|
-
|
|
23
|
-
// 1. Extract the factory function body: everything between
|
|
24
|
-
// "factory: function (require) {" and the final "\n});".
|
|
25
|
-
const factoryMarker = 'factory: function (require) {';
|
|
26
|
-
const start = src.indexOf(factoryMarker);
|
|
27
|
-
if (start === -1) throw new Error('factory marker not found in client.js');
|
|
28
|
-
const bodyStart = start + factoryMarker.length;
|
|
29
|
-
|
|
30
|
-
const endMarker = '\n});';
|
|
31
|
-
const end = src.lastIndexOf(endMarker);
|
|
32
|
-
if (end === -1) throw new Error('module end marker not found in client.js');
|
|
33
|
-
|
|
34
|
-
let body = src.slice(bodyStart, end);
|
|
35
|
-
// Drop the factory's own closing brace (the trailing " }").
|
|
36
|
-
body = body.replace(/\n[ \t]*\}\s*$/, '');
|
|
37
|
-
|
|
38
|
-
// 2. Remove the CommonJS export boilerplate.
|
|
39
|
-
const boilerplate =
|
|
40
|
-
' var module = { exports: {} };\n' +
|
|
41
|
-
' var exports = module.exports;\n' +
|
|
42
|
-
' Object.defineProperty(exports, Symbol.toStringTag, { value: \'Module\' });\n';
|
|
43
|
-
if (body.indexOf(boilerplate) === -1) throw new Error('module boilerplate not found');
|
|
44
|
-
body = body.replace(boilerplate, '');
|
|
45
|
-
|
|
46
|
-
// 3. Replace the React require block with an explanatory comment (see header).
|
|
47
|
-
const reactBlock =
|
|
48
|
-
' // React is a platform seed word in the web shell; the settings card\n' +
|
|
49
|
-
' // needs it, the folding engine does not.\n' +
|
|
50
|
-
' var React = null;\n' +
|
|
51
|
-
' try {\n' +
|
|
52
|
-
' React = require(\'react\');\n' +
|
|
53
|
-
' } catch (err) {\n' +
|
|
54
|
-
' React = null;\n' +
|
|
55
|
-
' }\n';
|
|
56
|
-
if (body.indexOf(reactBlock) === -1) throw new Error('React block not found');
|
|
57
|
-
body = body.replace(reactBlock,
|
|
58
|
-
' // React is the runner\'s closure parameter; the folding engine does not\n' +
|
|
59
|
-
' // need it directly, and redeclaring it here would shadow the parameter.\n');
|
|
60
|
-
|
|
61
|
-
// 4. Replace the module.exports tail with a bare `return plugin;`.
|
|
62
|
-
const tail =
|
|
63
|
-
' module.exports = plugin;\n' +
|
|
64
|
-
' return module.exports;';
|
|
65
|
-
if (body.indexOf(tail) === -1) throw new Error('export tail not found');
|
|
66
|
-
body = body.replace(tail, 'return plugin;\n');
|
|
67
|
-
|
|
68
|
-
fs.writeFileSync(outPath, body, 'utf8');
|
|
69
|
-
console.log('wrote', outPath, body.length, 'bytes');
|