dsh-image-gen 0.6.10 → 0.7.0

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/lib/index.js CHANGED
@@ -5,861 +5,7 @@ import { lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeF
5
5
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import http from "node:http";
8
- //#region \0rolldown/runtime.js
9
- var __defProp = Object.defineProperty;
10
- var __exportAll = (all, no_symbols) => {
11
- let target = {};
12
- for (var name in all) __defProp(target, name, {
13
- get: all[name],
14
- enumerable: true
15
- });
16
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
17
- return target;
18
- };
19
- //#endregion
20
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-value_005f00f2ec004257b269f27fd5e936b4/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
21
- /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
22
- /**
23
- * Mark an unreachable closed-union branch.
24
- * @param value - impossible value; an unhandled typed variant fails at the call site.
25
- * @param context - optional switch-site label included in the failure message.
26
- * @returns never; a runtime value that escaped its type always throws.
27
- */
28
- function assertNever(value, context) {
29
- const rendered = JSON.stringify(value) ?? String(value);
30
- throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
31
- }
32
- /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
33
- function hasIntrinsicConstructor$1(prototype, name) {
34
- const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
35
- if (typeof constructor !== "function") return false;
36
- try {
37
- return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
38
- } catch {
39
- return false;
40
- }
41
- }
42
- /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
43
- function isIntrinsicObjectPrototype$1(value) {
44
- return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, "Object");
45
- }
46
- /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
47
- function hasPlainArrayPrototype$1(value) {
48
- const prototype = Object.getPrototypeOf(value);
49
- if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, "Array")) return false;
50
- const objectPrototype = Object.getPrototypeOf(prototype);
51
- return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);
52
- }
53
- /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
54
- function hasPlainObjectPrototype(value) {
55
- const prototype = Object.getPrototypeOf(value);
56
- return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype$1(prototype);
57
- }
58
- /** Return every JSON-visible object key, or reject own data JSON would discard. */
59
- function enumerableStringKeys(value) {
60
- const keys = Reflect.ownKeys(value);
61
- if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
62
- return keys;
63
- }
64
- /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
65
- function walkJsonValue(value, detach) {
66
- const ancestors = /* @__PURE__ */ new Set();
67
- let root;
68
- const assign = (destination, item) => {
69
- if (destination === void 0) return;
70
- if (destination.kind === "root") root = item;
71
- else if (destination.kind === "array") destination.target[destination.index] = item;
72
- else Object.defineProperty(destination.target, destination.key, {
73
- value: item,
74
- enumerable: true,
75
- configurable: true,
76
- writable: true
77
- });
78
- };
79
- const tasks = [{
80
- kind: "visit",
81
- value,
82
- ...detach ? { destination: { kind: "root" } } : {}
83
- }];
84
- for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
85
- if (task.kind === "leave") {
86
- ancestors.delete(task.source);
87
- continue;
88
- }
89
- if (task.kind === "array-item") {
90
- if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
91
- tasks.push({
92
- kind: "visit",
93
- value: task.source[task.index],
94
- ...task.target === void 0 ? {} : { destination: {
95
- kind: "array",
96
- target: task.target,
97
- index: task.index
98
- } }
99
- });
100
- continue;
101
- }
102
- if (task.kind === "object-property") {
103
- tasks.push({
104
- kind: "visit",
105
- value: task.source[task.key],
106
- ...task.target === void 0 ? {} : { destination: {
107
- kind: "object",
108
- target: task.target,
109
- key: task.key
110
- } }
111
- });
112
- continue;
113
- }
114
- const current = task.value;
115
- if (current === null) {
116
- assign(task.destination, null);
117
- continue;
118
- }
119
- if (typeof current === "boolean" || typeof current === "string") {
120
- assign(task.destination, current);
121
- continue;
122
- }
123
- if (typeof current === "number") {
124
- if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
125
- assign(task.destination, current);
126
- continue;
127
- }
128
- if (typeof current !== "object") return void 0;
129
- if (ancestors.has(current)) return void 0;
130
- if (Array.isArray(current)) {
131
- if (!hasPlainArrayPrototype$1(current)) return void 0;
132
- const length = current.length;
133
- if (Reflect.ownKeys(current).length !== length + 1) return void 0;
134
- const target = detach ? [] : void 0;
135
- if (target !== void 0) assign(task.destination, target);
136
- ancestors.add(current);
137
- tasks.push({
138
- kind: "leave",
139
- source: current
140
- });
141
- for (let index = length - 1; index >= 0; index--) tasks.push({
142
- kind: "array-item",
143
- source: current,
144
- index,
145
- ...target === void 0 ? {} : { target }
146
- });
147
- continue;
148
- }
149
- if (!hasPlainObjectPrototype(current)) return void 0;
150
- const keys = enumerableStringKeys(current);
151
- if (keys === void 0) return void 0;
152
- const target = detach ? {} : void 0;
153
- if (target !== void 0) assign(task.destination, target);
154
- ancestors.add(current);
155
- tasks.push({
156
- kind: "leave",
157
- source: current
158
- });
159
- for (let index = keys.length - 1; index >= 0; index--) {
160
- const key = keys[index];
161
- /* v8 ignore next -- the loop is bounded by the captured key count. */
162
- if (key === void 0) return void 0;
163
- tasks.push({
164
- kind: "object-property",
165
- source: current,
166
- key,
167
- ...target === void 0 ? {} : { target }
168
- });
169
- }
170
- }
171
- return detach ? root : true;
172
- }
173
- /**
174
- * Validate and detach lossless JSON in one read per property.
175
- * @param value - candidate value to validate and detach.
176
- * @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
177
- */
178
- function snapshotJsonValue(value) {
179
- return walkJsonValue(value, true);
180
- }
181
- /**
182
- * Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
183
- * @param value - candidate value to test.
184
- * @returns whether the value survives a JSON round trip without loss.
185
- */
186
- function isJsonValue(value) {
187
- return walkJsonValue(value, false) === true;
188
- }
189
- /**
190
- * Compare JSON-compatible values structurally.
191
- * @param a - one JSON-compatible value.
192
- * @param b - the other JSON-compatible value.
193
- * @returns whether both values contain the same JSON data.
194
- */
195
- function deepEqualJson(a, b) {
196
- if (a === b) return true;
197
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
198
- if (Array.isArray(a) || Array.isArray(b)) {
199
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
200
- return a.every((entry, index) => deepEqualJson(entry, b[index]));
201
- }
202
- const left = a;
203
- const right = b;
204
- const keys = Object.keys(left);
205
- if (keys.length !== Object.keys(right).length) return false;
206
- return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
207
- }
208
- /**
209
- * Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
210
- * @param value - value to freeze.
211
- * @returns the same value after every reachable enumerable child is frozen.
212
- */
213
- function deepFreeze(value) {
214
- const seen = /* @__PURE__ */ new WeakSet();
215
- const pending = [{
216
- kind: "visit",
217
- node: value
218
- }];
219
- while (pending.length > 0) {
220
- const task = pending.pop();
221
- /* v8 ignore next -- the loop condition guarantees one pending task. */
222
- if (task === void 0) continue;
223
- if (task.kind === "property") {
224
- pending.push({
225
- kind: "visit",
226
- node: task.source[task.key]
227
- });
228
- continue;
229
- }
230
- const node = task.node;
231
- if (node === null || typeof node !== "object") continue;
232
- if (node instanceof AbortSignal) continue;
233
- if (seen.has(node)) continue;
234
- seen.add(node);
235
- Object.freeze(node);
236
- const keys = Object.keys(node);
237
- for (let index = keys.length - 1; index >= 0; index--) {
238
- const key = keys[index];
239
- /* v8 ignore next -- the loop is bounded by the captured key count. */
240
- if (key === void 0) continue;
241
- pending.push({
242
- kind: "property",
243
- source: node,
244
- key
245
- });
246
- }
247
- }
248
- return value;
249
- }
250
- //#endregion
251
- //#region node_modules/.pnpm/@deepseek-ai+dsh-settings@0_935f3a2ac52a1f8e83ba61ff4169507a/node_modules/@deepseek-ai/dsh-settings/lib/index.js
252
- var lib_exports = /* @__PURE__ */ __exportAll({
253
- SettingsConflictError: () => SettingsConflictError,
254
- SettingsProvider: () => SettingsProvider,
255
- default: () => SettingsProvider,
256
- redactSecrets: () => redactSecrets$1
257
- });
258
- /**
259
- * Structural secret redaction for settings values. `role('secret')` fields are
260
- * removed from a value before it crosses a wire boundary; a sidecar records
261
- * each schema-declared secret position and whether it currently holds a value,
262
- * so a configuration surface can render a write-only input without ever
263
- * receiving the secret itself.
264
- * @module @deepseek-ai/dsh-settings/redact
265
- */
266
- /** Whether a value is a plain data object the walker may recurse into. */
267
- function isRecord$1(value) {
268
- return typeof value === "object" && value !== null && !Array.isArray(value);
269
- }
270
- function walk(node, value, path, secrets) {
271
- if (node === void 0) return value;
272
- if (node.meta?.role === "secret") {
273
- secrets.push({
274
- path,
275
- set: value !== void 0
276
- });
277
- return;
278
- }
279
- switch (node.type) {
280
- case "object": {
281
- const properties = node.dict ?? {};
282
- const source = isRecord$1(value) ? value : void 0;
283
- const rebuilt = {};
284
- if (source !== void 0) for (const [key, entry] of Object.entries(source)) {
285
- if (key in properties) continue;
286
- rebuilt[key] = entry;
287
- }
288
- for (const [key, child] of Object.entries(properties)) {
289
- const stripped = walk(child, source?.[key], [...path, key], secrets);
290
- if (stripped !== void 0) rebuilt[key] = stripped;
291
- }
292
- return source === void 0 && Object.keys(rebuilt).length === 0 ? value : rebuilt;
293
- }
294
- case "dict": {
295
- if (!isRecord$1(value)) return value;
296
- const rebuilt = {};
297
- for (const [key, entry] of Object.entries(value)) {
298
- const stripped = walk(node.inner, entry, [...path, key], secrets);
299
- if (stripped !== void 0) rebuilt[key] = stripped;
300
- }
301
- return rebuilt;
302
- }
303
- case "array":
304
- if (!Array.isArray(value)) return value;
305
- return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
306
- default: return value;
307
- }
308
- }
309
- /**
310
- * Remove every `role('secret')` field a schema declares from a value. The
311
- * walker follows `object`, `dict`, and `array` containers; a secret must be
312
- * declared directly on a field reachable through those containers (a secret
313
- * buried inside a union branch or transform is not reachable and must not be
314
- * modeled that way). The input is never mutated.
315
- * @param schema - live schemastery schema describing the value.
316
- * @param value - the value to strip; `undefined` yields an empty record with
317
- * object-property secret slots still enumerated.
318
- * @returns the stripped detached value and the ordered secret positions.
319
- */
320
- function redactSecrets$1(schema, value) {
321
- const secrets = [];
322
- return {
323
- value: walk(schema, value, [], secrets),
324
- secrets
325
- };
326
- }
327
- /**
328
- * Service Definition for the user-settings capability seam (`ctx.settings`). Providers store one raw document of
329
- * per-namespace sections; plugins register a namespace schema and read the
330
- * resolved value, which layers schema defaults, the registrant's composition
331
- * `base`, and the user document section, in that order.
332
- * @module @deepseek-ai/dsh-settings
333
- */
334
- const NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*$/;
335
- function parseSettingsNamespace(value) {
336
- if (!NAMESPACE_PATTERN.test(value)) throw new TypeError(`settings namespace "${value}" must match ${String(NAMESPACE_PATTERN)}`);
337
- return value;
338
- }
339
- /**
340
- * A write refused because the namespace moved since the caller read it. The
341
- * Service Definition's serialized write queue orders writes; it cannot tell a fresh writer
342
- * from one holding a stale snapshot, which is what this reports.
343
- */
344
- var SettingsConflictError = class extends Error {
345
- /** Stable machine code for wire layers mapping this to their own taxonomy. */
346
- code = "SETTINGS_CONFLICT";
347
- /** The revision the write expected. */
348
- expected;
349
- /** The revision the namespace actually stands at. */
350
- actual;
351
- /**
352
- * @param ns - the namespace whose write was refused.
353
- * @param expected - the revision the caller sent.
354
- * @param actual - the revision now stored.
355
- */
356
- constructor(ns, expected, actual) {
357
- super(`settings namespace "${ns}" changed since it was read (expected revision ${String(expected)}, now ${String(actual)})`);
358
- this.name = "SettingsConflictError";
359
- this.expected = expected;
360
- this.actual = actual;
361
- }
362
- };
363
- /** Whether a value is a plain data object (not an array, null, or class instance). */
364
- function isPlainObject(value) {
365
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
366
- const proto = Object.getPrototypeOf(value);
367
- return proto === Object.prototype || proto === null;
368
- }
369
- /** Apply one path op to a detached section, returning the next section. */
370
- function applyPathOp(section, op) {
371
- const [head, ...rest] = op.path;
372
- if (head === void 0) {
373
- if (op.op === "unset") return {};
374
- if (!isPlainObject(op.value)) throw new TypeError("settings mutate: setting the section root requires a plain object");
375
- return { ...op.value };
376
- }
377
- if (rest.length === 0) {
378
- if (op.op === "set") return {
379
- ...section,
380
- [head]: op.value
381
- };
382
- const { [head]: _removed, ...kept } = section;
383
- return kept;
384
- }
385
- const child = section[head];
386
- if (!isPlainObject(child)) {
387
- if (op.op === "unset") return section;
388
- return {
389
- ...section,
390
- [head]: applyPathOp({}, {
391
- ...op,
392
- path: rest
393
- })
394
- };
395
- }
396
- return {
397
- ...section,
398
- [head]: applyPathOp(child, {
399
- ...op,
400
- path: rest
401
- })
402
- };
403
- }
404
- /** Human label for a value that lossless JSON cannot represent (numbers reject inline). */
405
- function describeRejected(value) {
406
- if (value === void 0) return "undefined";
407
- if (typeof value === "object" && value !== null) {
408
- const name = Object.getPrototypeOf(value)?.constructor?.name;
409
- return name === void 0 || name === "Object" ? "a non-plain object" : `a ${name}`;
410
- }
411
- return `a ${typeof value}`;
412
- }
413
- /**
414
- * Detach and validate one write input in a single walk before persistence:
415
- * only JSON data (plain objects, arrays, strings, finite numbers,
416
- * booleans, `null`) may reach a provider document. `structuredClone` alone
417
- * would admit Dates, Maps, BigInts, and cycles that YAML/JSON storage then
418
- * silently distorts on the reload round-trip. `undefined` entries in objects
419
- * are skipped — the same sparse-patch semantics as {@link mergeLayers} — while
420
- * an `undefined` array entry is rejected rather than coerced.
421
- * @param root - plain-object write input (caller-checked).
422
- * @param reject - builds the validation error from a value label and its `$`-rooted path.
423
- * @returns the detached JSON-compatible clone.
424
- */
425
- function cloneJsonShaped(root, reject) {
426
- const visiting = /* @__PURE__ */ new WeakSet();
427
- const clone = (value, path) => {
428
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
429
- if (typeof value === "number") {
430
- if (!Number.isFinite(value)) throw reject("a non-finite number", path);
431
- return value;
432
- }
433
- if (Array.isArray(value)) {
434
- if (visiting.has(value)) throw reject("a circular reference", path);
435
- visiting.add(value);
436
- const entries = value.map((entry, index) => clone(entry, `${path}[${index}]`));
437
- visiting.delete(value);
438
- return entries;
439
- }
440
- if (isPlainObject(value)) {
441
- if (visiting.has(value)) throw reject("a circular reference", path);
442
- visiting.add(value);
443
- const out = {};
444
- for (const [key, entry] of Object.entries(value)) {
445
- if (entry === void 0) continue;
446
- out[key] = clone(entry, `${path}.${key}`);
447
- }
448
- visiting.delete(value);
449
- return out;
450
- }
451
- throw reject(describeRejected(value), path);
452
- };
453
- return clone(root, "$");
454
- }
455
- /**
456
- * Layer `over` onto `under`: plain objects merge recursively, every other
457
- * value (arrays included) replaces the lower layer wholesale. `over` never
458
- * carries `undefined` entries — sections come from parsed documents and write
459
- * snapshots pass {@link cloneJsonShaped}, which strips them so a sparse patch
460
- * cannot erase lower keys.
461
- */
462
- function mergeLayers(under, over) {
463
- if (over === void 0) return under;
464
- if (!isPlainObject(under) || !isPlainObject(over)) return over;
465
- const merged = { ...under };
466
- for (const [key, value] of Object.entries(over)) merged[key] = key in merged ? mergeLayers(merged[key], value) : value;
467
- return merged;
468
- }
469
- /**
470
- * Abstract settings service. Providers implement raw-document storage
471
- * (`load`/`persist`) and push external changes through {@link Settings.publish};
472
- * the base class owns namespace registration, resolution, validation, change
473
- * detection, and the `settings/updated` commit event.
474
- */
475
- var SettingsProvider = class extends Service {
476
- registrations = /* @__PURE__ */ new Map();
477
- /** Latest published raw document; empty until the provider's first publish. */
478
- document = {};
479
- /** Per-namespace write chains; settled tails, so a failure never poisons the queue. */
480
- writeQueues = /* @__PURE__ */ new Map();
481
- /** In-flight watcher invocation segments, drained by the dispose teardown. */
482
- pendingTails = /* @__PURE__ */ new Set();
483
- /** Set at service dispose: refuse new writes while queued ones drain. */
484
- stopped = false;
485
- /** Opaque read of {@link stopped}: control flow cannot narrow it across awaits. */
486
- isStopped() {
487
- return this.stopped;
488
- }
489
- constructor(ctx) {
490
- super(ctx, "settings");
491
- }
492
- /**
493
- * Load the provider's document once and publish it before the service
494
- * becomes injectable, and register the write-drain teardown. Providers with
495
- * their own init (watchers, connections) delegate here first via
496
- * `yield* super[Service.init]()`; their disposers then run before the drain.
497
- */
498
- async *[Service.init]() {
499
- yield async () => {
500
- this.stopped = true;
501
- await Promise.allSettled([...this.writeQueues.values(), ...this.pendingTails]);
502
- };
503
- this.publish(await this.load());
504
- }
505
- /**
506
- * Absolute path of the provider's user-editable document, when its storage
507
- * is one local file. Configuration surfaces use this only as availability
508
- * metadata; the guarded open operation resolves the path again Host-side.
509
- * Non-file providers leave it undefined and expose no open-document affordance.
510
- * @returns the absolute local document path, or undefined for non-file storage.
511
- */
512
- get documentPath() {}
513
- /**
514
- * Prepare the provider's user-editable document for a native editor. File
515
- * providers may materialize an absent document before returning its path;
516
- * non-file providers return undefined.
517
- * @returns the absolute local document path, or undefined for non-file storage.
518
- */
519
- prepareDocument() {
520
- return Promise.resolve(this.documentPath);
521
- }
522
- /**
523
- * Register a namespace schema and receive its owner scope. The registration
524
- * is an effect on the calling plugin's fiber: disposing that fiber removes
525
- * the namespace and its observers. An invalid stored section fails the
526
- * registration itself — the earliest point where the schema can judge it.
527
- * @param ns - unique namespace; duplicate registration fails loud.
528
- * @param schema - schemastery schema resolving this namespace's value.
529
- * @param options - composition `base` layer and effect timing.
530
- * @returns the owner scope for reads, observation, and updates.
531
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
532
- */
533
- register(ns, schema, options) {
534
- const parsedNs = parseSettingsNamespace(ns);
535
- if (this.registrations.has(parsedNs)) throw new Error(`settings namespace "${parsedNs}" is already registered`);
536
- const registration = {
537
- ns: parsedNs,
538
- schema,
539
- base: options?.base,
540
- applies: options?.applies ?? "live",
541
- ...options?.validate === void 0 ? {} : { validate: options.validate },
542
- resolved: deepFreeze(this.resolve(schema, options?.base, this.section(parsedNs), options?.validate)),
543
- revision: 0,
544
- watchers: /* @__PURE__ */ new Set()
545
- };
546
- this.ctx.effect(() => {
547
- this.registrations.set(parsedNs, registration);
548
- return () => this.registrations.delete(parsedNs);
549
- }, `settings.register(${JSON.stringify(String(parsedNs))})`);
550
- return {
551
- get: () => registration.resolved,
552
- watch: (callback) => {
553
- const watcher = {
554
- callback,
555
- tail: Promise.resolve(),
556
- active: true
557
- };
558
- registration.watchers.add(watcher);
559
- return () => {
560
- watcher.active = false;
561
- registration.watchers.delete(watcher);
562
- };
563
- },
564
- update: (patch) => this.update(parsedNs, patch),
565
- replace: (section) => this.replace(parsedNs, section)
566
- };
567
- }
568
- /**
569
- * Attach one optional-settings consumer to this provider. The consumer
570
- * registers its composition entry as the base layer while this provider is
571
- * present, then falls back to that entry if the provider detaches.
572
- * @param owner - consumer context whose unload suppresses fallback work.
573
- * @param ns - consumer-owned settings namespace.
574
- * @param schema - schema resolving the namespace.
575
- * @param entry - composition entry used as the base and fallback value.
576
- * @param hooks - source sink, change notification, and optional validation.
577
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
578
- */
579
- installSection(owner, ns, schema, entry, hooks) {
580
- const scope = this.register(ns, schema, {
581
- base: entry,
582
- ...hooks.validate === void 0 ? {} : { validate: hooks.validate }
583
- });
584
- hooks.setSource(() => scope.get());
585
- this.ctx.effect(() => () => {
586
- if (isUnloading(owner)) return;
587
- hooks.setSource(() => entry);
588
- hooks.onChange();
589
- });
590
- hooks.onChange();
591
- scope.watch(() => {
592
- if (isUnloading(owner)) return;
593
- hooks.onChange();
594
- });
595
- }
596
- /**
597
- * Describe every registered namespace for configuration surfaces, including
598
- * the composition `base` and raw user layers so a form can mark which fields
599
- * the user overrode (presence in `user`) and what a reset returns to.
600
- * @param options - redaction switch; wire surfaces must redact.
601
- * @returns one descriptor per registered namespace, in registration order.
602
- */
603
- describe(options) {
604
- return [...this.registrations.values()].map((registration) => {
605
- let user;
606
- try {
607
- user = this.section(registration.ns);
608
- } catch {
609
- user = void 0;
610
- }
611
- const base = registration.base === void 0 ? void 0 : structuredClone(registration.base);
612
- const detachedUser = user === void 0 ? void 0 : structuredClone(user);
613
- const descriptor = {
614
- ns: registration.ns,
615
- schema: registration.schema.toJSON(),
616
- value: registration.resolved,
617
- revision: registration.revision,
618
- ...base === void 0 ? {} : { base },
619
- ...detachedUser === void 0 ? {} : { user: detachedUser },
620
- applies: registration.applies
621
- };
622
- if (options?.redactSecrets !== true) return descriptor;
623
- const schema = registration.schema;
624
- const redacted = redactSecrets$1(schema, registration.resolved);
625
- return {
626
- ...descriptor,
627
- value: redacted.value,
628
- ...base === void 0 ? {} : { base: redactSecrets$1(schema, base).value },
629
- ...detachedUser === void 0 ? {} : { user: redactSecrets$1(schema, detachedUser).value },
630
- secrets: redacted.secrets
631
- };
632
- });
633
- }
634
- /**
635
- * Read one registered namespace's resolved value.
636
- * @param ns - the namespace to read.
637
- * @returns the resolved value, or `undefined` while unregistered.
638
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
639
- */
640
- get(ns) {
641
- return this.registrations.get(parseSettingsNamespace(ns))?.resolved;
642
- }
643
- /**
644
- * Merge a patch into one registered namespace's user layer, validate the
645
- * resolved candidate, persist through the provider, then commit and emit.
646
- * A validation failure rejects before anything is persisted. Writes to one
647
- * namespace are serialized: concurrent updates apply in call order, each
648
- * merging over the previous write's committed section.
649
- * @param ns - the registered namespace to update.
650
- * @param patch - plain-object patch over the user section.
651
- * @param expectedRevision - the descriptor `revision` the caller read; a
652
- * namespace that moved past it rejects with {@link SettingsConflictError}.
653
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
654
- */
655
- async update(ns, patch, expectedRevision) {
656
- return this.write(parseSettingsNamespace(ns), patch, "merge", expectedRevision);
657
- }
658
- /**
659
- * Replace one registered namespace's user section wholesale, validate,
660
- * persist, then commit and emit. Keys absent from `section` fall back to the
661
- * composition `base` and schema defaults — this is the removal/reset path a
662
- * merge-only patch cannot express (`replace({})` re-inherits everything).
663
- * @param ns - the registered namespace to replace.
664
- * @param section - the complete next user section.
665
- * @param expectedRevision - the descriptor `revision` the caller read; a
666
- * namespace that moved past it rejects with {@link SettingsConflictError}.
667
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
668
- */
669
- async replace(ns, section, expectedRevision) {
670
- return this.write(parseSettingsNamespace(ns), section, "replace", expectedRevision);
671
- }
672
- /**
673
- * Apply path-addressed edits to one registered namespace's user section,
674
- * validate, persist, then commit and emit. The ops are applied to the
675
- * section as it stands when the write reaches the front of the queue, so a
676
- * caller never has to restate fields it did not touch — and, crucially,
677
- * cannot delete fields it never saw. This is the write path for any caller
678
- * holding a redacted view; `replace` remains the wholesale reset.
679
- * @param ns - the registered namespace to edit.
680
- * @param ops - ordered path edits; later ops observe earlier ones.
681
- * @param expectedRevision - the descriptor `revision` the caller read; a
682
- * namespace that moved past it rejects with {@link SettingsConflictError}.
683
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
684
- */
685
- async mutate(ns, ops, expectedRevision) {
686
- const parsedNs = parseSettingsNamespace(ns);
687
- if (!Array.isArray(ops)) throw new TypeError(`settings mutate for "${parsedNs}" must be an array of path ops`);
688
- for (const op of ops) {
689
- if (!isPlainObject(op) || op["op"] !== "set" && op["op"] !== "unset") throw new TypeError(`settings mutate for "${parsedNs}" ops must be {op:'set'|'unset', path}`);
690
- if (!Array.isArray(op["path"]) || op["path"].some((part) => typeof part !== "string")) throw new TypeError(`settings mutate for "${parsedNs}" op paths must be arrays of strings`);
691
- }
692
- return this.write(parsedNs, ops, "mutate", expectedRevision);
693
- }
694
- /** Validate a write, then queue it on the namespace's serialized write chain. */
695
- write(ns, input, mode, expectedRevision) {
696
- const verb = mode === "merge" ? "update" : mode === "replace" ? "replace" : "mutate";
697
- const registration = this.registrations.get(ns);
698
- if (registration === void 0) throw new Error(`settings namespace "${ns}" is not registered`);
699
- if (this.isStopped()) throw new Error(`settings service is disposed: "${ns}" cannot be written`);
700
- if (!this.writable) throw new Error(`settings provider is read-only: "${ns}" cannot be updated in-process`);
701
- let payload;
702
- if (mode === "mutate") payload = { ops: input };
703
- else {
704
- if (!isPlainObject(input)) throw new TypeError(`settings ${verb} for "${ns}" must be a plain object`);
705
- payload = input;
706
- }
707
- const snapshot = cloneJsonShaped(payload, (label, path) => /* @__PURE__ */ new TypeError(`settings ${verb} for "${ns}" must contain only JSON-compatible data (found ${label} at ${path})`));
708
- const run = (this.writeQueues.get(ns) ?? Promise.resolve()).catch(() => void 0).then(async () => {
709
- if (this.isStopped()) throw new Error(`settings service was disposed before the queued "${ns}" ${verb} ran`);
710
- if (this.registrations.get(ns) !== registration) throw new Error(`settings namespace "${ns}" registration was disposed before the queued ${verb} ran`);
711
- const current = this.section(ns) ?? {};
712
- if (expectedRevision !== void 0 && expectedRevision !== registration.revision) throw new SettingsConflictError(ns, expectedRevision, registration.revision);
713
- const section = mode === "merge" ? mergeLayers(current, snapshot) : mode === "replace" ? snapshot : snapshot["ops"].reduce(applyPathOp, current);
714
- const next = deepFreeze(this.resolve(registration.schema, registration.base, section, registration.validate));
715
- await this.persist(ns, section);
716
- this.document[ns] = section;
717
- if (this.registrations.get(ns) === registration && !this.isStopped()) {
718
- this.bumpRevision(registration, current, section);
719
- this.commit(registration, next, "update");
720
- }
721
- });
722
- this.writeQueues.set(ns, run);
723
- return run;
724
- }
725
- /**
726
- * Provider hook: commit a complete raw document observed in storage. Each
727
- * registered namespace re-resolves; an invalid section keeps that
728
- * namespace's last good value and warns, other namespaces still commit.
729
- * @param doc - the detached raw document (unregistered sections preserved).
730
- * @param source - change origin; defaults to `provider`.
731
- */
732
- publish(doc, source = "provider") {
733
- const before = /* @__PURE__ */ new Map();
734
- for (const registration of this.registrations.values()) try {
735
- before.set(registration.ns, this.section(registration.ns));
736
- } catch {
737
- before.set(registration.ns, void 0);
738
- }
739
- this.document = doc;
740
- for (const registration of this.registrations.values()) {
741
- let next;
742
- try {
743
- next = deepFreeze(this.resolve(registration.schema, registration.base, this.section(registration.ns), registration.validate));
744
- } catch (error) {
745
- this.ctx.logger.warn("settings: keeping last good \"%s\" after invalid stored section", registration.ns);
746
- this.ctx.logger.warn(error);
747
- continue;
748
- }
749
- this.bumpRevision(registration, before.get(registration.ns), this.section(registration.ns));
750
- this.commit(registration, next, source);
751
- }
752
- }
753
- /** Read one namespace's raw user section, rejecting non-object sections. */
754
- section(ns) {
755
- const section = this.document[ns];
756
- if (section === void 0) return void 0;
757
- if (!isPlainObject(section)) throw new TypeError(`settings section "${ns}" must be an object of keys`);
758
- return section;
759
- }
760
- /** Resolve one namespace value: schema defaults, then `base`, then the user layer. */
761
- resolve(schema, base, section, validate) {
762
- const value = schema(mergeLayers(base, section));
763
- validate?.(value);
764
- return value;
765
- }
766
- /**
767
- * Advance a namespace's revision when its RAW section changed, and announce
768
- * it. Deliberately independent of {@link commit}'s resolved-value equality:
769
- * storing an override equal to the composition base leaves the resolved
770
- * value alone but changes what the document says, which is exactly what a
771
- * configuration surface must re-read.
772
- */
773
- bumpRevision(registration, before, after) {
774
- if (deepEqualJson(before, after)) return;
775
- registration.revision += 1;
776
- this.emitDocumentUpdated(registration.ns, registration.revision);
777
- }
778
- /** Contained fan-out of `settings/document-updated`, mirroring {@link commit}'s. */
779
- emitDocumentUpdated(ns, revision) {
780
- let invariantFailure;
781
- const args = [
782
- "settings/document-updated",
783
- ns,
784
- revision
785
- ];
786
- for (const listener of this.ctx.events.dispatch("emit", args)) try {
787
- const returned = listener(ns, revision);
788
- if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
789
- this.warnListenerFailure(ns, error);
790
- });
791
- } catch (error) {
792
- if (error?.code === "INVARIANT") {
793
- invariantFailure ??= error;
794
- continue;
795
- }
796
- this.warnListenerFailure(ns, error);
797
- }
798
- if (invariantFailure !== void 0) throw invariantFailure;
799
- }
800
- /** Commit a resolved value when changed: swap, notify watchers, emit the event. */
801
- commit(registration, next, source) {
802
- const prev = registration.resolved;
803
- if (deepEqualJson(next, prev)) return;
804
- registration.resolved = next;
805
- for (const watcher of [...registration.watchers]) {
806
- const segment = watcher.tail.then(() => {
807
- if (!watcher.active || this.isStopped()) return;
808
- return watcher.callback(next, prev);
809
- }).then(() => void 0, (error) => {
810
- this.warnWatcherFailure(registration.ns, error);
811
- });
812
- watcher.tail = segment;
813
- this.pendingTails.add(segment);
814
- segment.then(() => this.pendingTails.delete(segment));
815
- }
816
- let invariantFailure;
817
- const args = [
818
- "settings/updated",
819
- registration.ns,
820
- next,
821
- prev,
822
- source
823
- ];
824
- for (const listener of this.ctx.events.dispatch("emit", args)) try {
825
- const returned = listener(registration.ns, next, prev, source);
826
- if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
827
- this.warnListenerFailure(registration.ns, error);
828
- });
829
- } catch (error) {
830
- if (error?.code === "INVARIANT") {
831
- invariantFailure ??= error;
832
- continue;
833
- }
834
- this.warnListenerFailure(registration.ns, error);
835
- }
836
- if (invariantFailure !== void 0) throw invariantFailure;
837
- }
838
- /** Contained-watcher diagnostic shared by the sync and async failure paths. */
839
- warnWatcherFailure(ns, error) {
840
- this.ctx.logger.warn("settings: watcher for \"%s\" failed", ns);
841
- this.ctx.logger.warn(error);
842
- }
843
- /** Contained-listener diagnostic shared by the sync and async failure paths. */
844
- warnListenerFailure(ns, error) {
845
- this.ctx.logger.warn("settings: a settings/updated listener for \"%s\" failed", ns);
846
- this.ctx.logger.warn(error);
847
- }
848
- };
849
- /**
850
- * Value mirror of the `FiberState` members {@link isUnloading} compares
851
- * against: a const enum has no runtime object to import, and the value is
852
- * needed at runtime (same rationale as the CLI boot driver's mirror).
853
- */
854
- const FIBER_DISPOSED = 4;
855
- const FIBER_UNLOADING = 5;
856
- /** Whether the consumer's own fiber is tearing down (not just losing the settings service). */
857
- function isUnloading(ctx) {
858
- const state = ctx.fiber.state;
859
- return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
860
- }
861
- //#endregion
862
- //#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1._7f15c7653b2619acc206cea5c06e49b3/node_modules/@deepseek-ai/dsh-scope/lib/index.js
8
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-scope@0.1._0271d3a82adfe807020938592f267574/node_modules/@deepseek-ai/dsh-scope/lib/index.js
863
9
  /**
864
10
  * Shared insertion-ordered storage and effect ownership for scope-aware registries.
865
11
  *
@@ -1138,7 +284,7 @@ function scopeTarget(base, key) {
1138
284
  return carrier;
1139
285
  }
1140
286
  //#endregion
1141
- //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-pro_32eafa0ce53608e9afb5dafcec019560/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
287
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-typert-pro_3c0b69ae3886dc169eb7f5273170dc50/node_modules/@deepseek-ai/dsh-typert-protocol/lib/index.js
1142
288
  /** The one Remote failure class shared by owners, the Gateway, and consumers. */
1143
289
  /**
1144
290
  * One Remote call failure: a real Error carrying its stable code and typed
@@ -1182,7 +328,10 @@ function isTypertRemoteSegment(value) {
1182
328
  }
1183
329
  const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
1184
330
  /**
1185
- * Bind one visible Service field to a Cordis key and Remote namespace.
331
+ * Bind one visible Service field to a Cordis key and Remote namespace. A
332
+ * service that owns a Cordis Context also gives its tree `ctx.invocation`,
333
+ * `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
334
+ * a Host composition to read it.
1186
335
  * @param service - owning Service instance, normally `this`.
1187
336
  * @param serviceKey - exact Cordis service key.
1188
337
  * @param options - optional distinct wire namespace.
@@ -1192,6 +341,8 @@ function bindTypertRemote(service, serviceKey, options = {}) {
1192
341
  validateName("service key", serviceKey);
1193
342
  const namespace = options.namespace ?? serviceKey;
1194
343
  validateName("namespace", namespace);
344
+ const ctx = Reflect.get(service, "ctx");
345
+ if (ctx instanceof Context) provideInvocationAccessor(ctx);
1195
346
  return Object.freeze({
1196
347
  service,
1197
348
  serviceKey,
@@ -1213,6 +364,16 @@ var TypertRemoteService = class extends Service {
1213
364
  this.typertRemote = bindTypertRemote(this, this.name, options);
1214
365
  }
1215
366
  };
367
+ /**
368
+ * Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
369
+ * reflect service's "cannot get property" error; a call-derived Context shadows
370
+ * the accessor with its own property. The first Remote Service constructed in a
371
+ * tree registers it on the root, where it outlives any one Service.
372
+ */
373
+ function provideInvocationAccessor(ctx) {
374
+ if (Object.hasOwn(ctx.root.reflect.props, "invocation")) return;
375
+ ctx.root.accessor("invocation", { get: () => void 0 });
376
+ }
1216
377
  function Remote(methodExportOrOptions, context) {
1217
378
  if (typeof methodExportOrOptions === "string") {
1218
379
  validateName("Remote export name", methodExportOrOptions);
@@ -1222,68 +383,280 @@ function Remote(methodExportOrOptions, context) {
1222
383
  if (remoteOptionMode(methodExportOrOptions) !== "stream" || Reflect.ownKeys(methodExportOrOptions).length !== 1) throw new TypeError("typert-protocol: Remote options must contain exactly mode: \"stream\"");
1223
384
  return remoteDecorator({ kind: "direct" }, "stream");
1224
385
  }
1225
- if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
1226
- addMarkerInitializer(context, { kind: "direct" });
1227
- }
1228
- function remoteOptionMode(options) {
1229
- return Reflect.get(options, "mode");
1230
- }
1231
- function remoteDecorator(invocation, mode, exportName) {
1232
- return function(_method, context) {
1233
- addMarkerInitializer(context, invocation, mode, exportName);
1234
- };
386
+ if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
387
+ addMarkerInitializer(context, { kind: "direct" });
388
+ }
389
+ function remoteOptionMode(options) {
390
+ return Reflect.get(options, "mode");
391
+ }
392
+ function remoteDecorator(invocation, mode, exportName) {
393
+ return function(_method, context) {
394
+ addMarkerInitializer(context, invocation, mode, exportName);
395
+ };
396
+ }
397
+ function readRemoteMethodDescriptor(prototype) {
398
+ const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
399
+ if (property === void 0) return void 0;
400
+ const descriptor = property.value;
401
+ if (descriptor === null || typeof descriptor !== "object") throw new TypeError("typert-protocol: Remote method descriptor must be an object");
402
+ const version = Reflect.get(descriptor, "version");
403
+ if (version !== 1) throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`);
404
+ const methods = Reflect.get(descriptor, "methods");
405
+ if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
406
+ return descriptor;
407
+ }
408
+ function addMarkerInitializer(context, invocation, mode, exportName) {
409
+ if (context.private || context.static || typeof context.name !== "string") throw new TypeError("typert-protocol: Remote decorators require a public instance method with a string name");
410
+ const method = context.name;
411
+ context.addInitializer(function() {
412
+ const prototype = Object.getPrototypeOf(this);
413
+ if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
414
+ mark(prototype, method, invocation, mode, exportName);
415
+ });
416
+ }
417
+ function mark(prototype, method, invocation, mode, exportName) {
418
+ const descriptor = readRemoteMethodDescriptor(prototype);
419
+ const marker = Object.freeze({
420
+ method,
421
+ ...exportName === void 0 || exportName === method ? {} : { exportName },
422
+ ...mode === void 0 ? {} : { mode },
423
+ invocation: Object.freeze(invocation)
424
+ });
425
+ const current = descriptor?.methods.find((candidate) => candidate.method === method);
426
+ if (current !== void 0) {
427
+ if (current.exportName === marker.exportName && current.mode === marker.mode && sameInvocation(current.invocation, invocation)) return;
428
+ throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
429
+ }
430
+ Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
431
+ configurable: true,
432
+ value: Object.freeze({
433
+ version: 1,
434
+ methods: Object.freeze([...descriptor?.methods ?? [], marker])
435
+ })
436
+ });
437
+ }
438
+ function sameInvocation(left, right) {
439
+ if (left.kind === "direct") return right.kind === "direct";
440
+ if (right.kind === "direct") return false;
441
+ return left.context === right.context;
442
+ }
443
+ function validateName(subject, value) {
444
+ if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
445
+ }
446
+ //#endregion
447
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-value_1dc92f68acf2742e77b3d5d44dd1e11b/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
448
+ /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
449
+ /**
450
+ * Mark an unreachable closed-union branch.
451
+ * @param value - impossible value; an unhandled typed variant fails at the call site.
452
+ * @param context - optional switch-site label included in the failure message.
453
+ * @returns never; a runtime value that escaped its type always throws.
454
+ */
455
+ function assertNever(value, context) {
456
+ const rendered = JSON.stringify(value) ?? String(value);
457
+ throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
458
+ }
459
+ /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
460
+ function hasIntrinsicConstructor$1(prototype, name) {
461
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
462
+ if (typeof constructor !== "function") return false;
463
+ try {
464
+ return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+ /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
470
+ function isIntrinsicObjectPrototype$1(value) {
471
+ return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, "Object");
472
+ }
473
+ /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
474
+ function hasPlainArrayPrototype$1(value) {
475
+ const prototype = Object.getPrototypeOf(value);
476
+ if (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, "Array")) return false;
477
+ const objectPrototype = Object.getPrototypeOf(prototype);
478
+ return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);
479
+ }
480
+ /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
481
+ function hasPlainObjectPrototype(value) {
482
+ const prototype = Object.getPrototypeOf(value);
483
+ return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype$1(prototype);
484
+ }
485
+ /** Return every JSON-visible object key, or reject own data JSON would discard. */
486
+ function enumerableStringKeys(value) {
487
+ const keys = Reflect.ownKeys(value);
488
+ if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
489
+ return keys;
490
+ }
491
+ /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
492
+ function walkJsonValue(value, detach) {
493
+ const ancestors = /* @__PURE__ */ new Set();
494
+ let root;
495
+ const assign = (destination, item) => {
496
+ if (destination === void 0) return;
497
+ if (destination.kind === "root") root = item;
498
+ else if (destination.kind === "array") destination.target[destination.index] = item;
499
+ else Object.defineProperty(destination.target, destination.key, {
500
+ value: item,
501
+ enumerable: true,
502
+ configurable: true,
503
+ writable: true
504
+ });
505
+ };
506
+ const tasks = [{
507
+ kind: "visit",
508
+ value,
509
+ ...detach ? { destination: { kind: "root" } } : {}
510
+ }];
511
+ for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
512
+ if (task.kind === "leave") {
513
+ ancestors.delete(task.source);
514
+ continue;
515
+ }
516
+ if (task.kind === "array-item") {
517
+ if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
518
+ tasks.push({
519
+ kind: "visit",
520
+ value: task.source[task.index],
521
+ ...task.target === void 0 ? {} : { destination: {
522
+ kind: "array",
523
+ target: task.target,
524
+ index: task.index
525
+ } }
526
+ });
527
+ continue;
528
+ }
529
+ if (task.kind === "object-property") {
530
+ tasks.push({
531
+ kind: "visit",
532
+ value: task.source[task.key],
533
+ ...task.target === void 0 ? {} : { destination: {
534
+ kind: "object",
535
+ target: task.target,
536
+ key: task.key
537
+ } }
538
+ });
539
+ continue;
540
+ }
541
+ const current = task.value;
542
+ if (current === null) {
543
+ assign(task.destination, null);
544
+ continue;
545
+ }
546
+ if (typeof current === "boolean" || typeof current === "string") {
547
+ assign(task.destination, current);
548
+ continue;
549
+ }
550
+ if (typeof current === "number") {
551
+ if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
552
+ assign(task.destination, current);
553
+ continue;
554
+ }
555
+ if (typeof current !== "object") return void 0;
556
+ if (ancestors.has(current)) return void 0;
557
+ if (Array.isArray(current)) {
558
+ if (!hasPlainArrayPrototype$1(current)) return void 0;
559
+ const length = current.length;
560
+ if (Reflect.ownKeys(current).length !== length + 1) return void 0;
561
+ const target = detach ? [] : void 0;
562
+ if (target !== void 0) assign(task.destination, target);
563
+ ancestors.add(current);
564
+ tasks.push({
565
+ kind: "leave",
566
+ source: current
567
+ });
568
+ for (let index = length - 1; index >= 0; index--) tasks.push({
569
+ kind: "array-item",
570
+ source: current,
571
+ index,
572
+ ...target === void 0 ? {} : { target }
573
+ });
574
+ continue;
575
+ }
576
+ if (!hasPlainObjectPrototype(current)) return void 0;
577
+ const keys = enumerableStringKeys(current);
578
+ if (keys === void 0) return void 0;
579
+ const target = detach ? {} : void 0;
580
+ if (target !== void 0) assign(task.destination, target);
581
+ ancestors.add(current);
582
+ tasks.push({
583
+ kind: "leave",
584
+ source: current
585
+ });
586
+ for (let index = keys.length - 1; index >= 0; index--) {
587
+ const key = keys[index];
588
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
589
+ if (key === void 0) return void 0;
590
+ tasks.push({
591
+ kind: "object-property",
592
+ source: current,
593
+ key,
594
+ ...target === void 0 ? {} : { target }
595
+ });
596
+ }
597
+ }
598
+ return detach ? root : true;
1235
599
  }
1236
- function readRemoteMethodDescriptor(prototype) {
1237
- const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
1238
- if (property === void 0) return void 0;
1239
- const descriptor = property.value;
1240
- if (descriptor === null || typeof descriptor !== "object") throw new TypeError("typert-protocol: Remote method descriptor must be an object");
1241
- const version = Reflect.get(descriptor, "version");
1242
- if (version !== 1) throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`);
1243
- const methods = Reflect.get(descriptor, "methods");
1244
- if (!Array.isArray(methods)) throw new TypeError("typert-protocol: Remote method descriptor methods must be an array");
1245
- return descriptor;
600
+ /**
601
+ * Validate and detach lossless JSON in one read per property.
602
+ * @param value - candidate value to validate and detach.
603
+ * @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
604
+ */
605
+ function snapshotJsonValue(value) {
606
+ return walkJsonValue(value, true);
1246
607
  }
1247
- function addMarkerInitializer(context, invocation, mode, exportName) {
1248
- if (context.private || context.static || typeof context.name !== "string") throw new TypeError("typert-protocol: Remote decorators require a public instance method with a string name");
1249
- const method = context.name;
1250
- context.addInitializer(function() {
1251
- const prototype = Object.getPrototypeOf(this);
1252
- if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
1253
- mark(prototype, method, invocation, mode, exportName);
1254
- });
608
+ /**
609
+ * Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
610
+ * @param value - candidate value to test.
611
+ * @returns whether the value survives a JSON round trip without loss.
612
+ */
613
+ function isJsonValue(value) {
614
+ return walkJsonValue(value, false) === true;
1255
615
  }
1256
- function mark(prototype, method, invocation, mode, exportName) {
1257
- const descriptor = readRemoteMethodDescriptor(prototype);
1258
- const marker = Object.freeze({
1259
- method,
1260
- ...exportName === void 0 || exportName === method ? {} : { exportName },
1261
- ...mode === void 0 ? {} : { mode },
1262
- invocation: Object.freeze(invocation)
1263
- });
1264
- const current = descriptor?.methods.find((candidate) => candidate.method === method);
1265
- if (current !== void 0) {
1266
- if (current.exportName === marker.exportName && current.mode === marker.mode && sameInvocation(current.invocation, invocation)) return;
1267
- throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
616
+ /**
617
+ * Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
618
+ * @param value - value to freeze.
619
+ * @returns the same value after every reachable enumerable child is frozen.
620
+ */
621
+ function deepFreeze(value) {
622
+ const seen = /* @__PURE__ */ new WeakSet();
623
+ const pending = [{
624
+ kind: "visit",
625
+ node: value
626
+ }];
627
+ while (pending.length > 0) {
628
+ const task = pending.pop();
629
+ /* v8 ignore next -- the loop condition guarantees one pending task. */
630
+ if (task === void 0) continue;
631
+ if (task.kind === "property") {
632
+ pending.push({
633
+ kind: "visit",
634
+ node: task.source[task.key]
635
+ });
636
+ continue;
637
+ }
638
+ const node = task.node;
639
+ if (node === null || typeof node !== "object") continue;
640
+ if (node instanceof AbortSignal) continue;
641
+ if (seen.has(node)) continue;
642
+ seen.add(node);
643
+ Object.freeze(node);
644
+ const keys = Object.keys(node);
645
+ for (let index = keys.length - 1; index >= 0; index--) {
646
+ const key = keys[index];
647
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
648
+ if (key === void 0) continue;
649
+ pending.push({
650
+ kind: "property",
651
+ source: node,
652
+ key
653
+ });
654
+ }
1268
655
  }
1269
- Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
1270
- configurable: true,
1271
- value: Object.freeze({
1272
- version: 1,
1273
- methods: Object.freeze([...descriptor?.methods ?? [], marker])
1274
- })
1275
- });
1276
- }
1277
- function sameInvocation(left, right) {
1278
- if (left.kind === "direct") return right.kind === "direct";
1279
- if (right.kind === "direct") return false;
1280
- return left.context === right.context;
1281
- }
1282
- function validateName(subject, value) {
1283
- if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
656
+ return value;
1284
657
  }
1285
658
  //#endregion
1286
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypt_66ec5f64228d8b6b846c0e0318a734f3/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
659
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-crypt_fa1ca4579d1c8036d3a90fef6a53e902/node_modules/@deepseek-ai/dsh-util-crypto/lib/index.js
1287
660
  /**
1288
661
  * Random v4 UUID, minted from `crypto.getRandomValues`.
1289
662
  * @returns the UUID string.
@@ -1296,7 +669,7 @@ function randomUUID$1() {
1296
669
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1297
670
  }
1298
671
  //#endregion
1299
- //#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-rc.1_@deepseek-ai+cordis@4.0.1/node_modules/@deepseek-ai/dsh-brand/lib/index.js
672
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-brand/lib/index.js
1300
673
  /**
1301
674
  * Duplicate-install-safe nominal primitive helpers.
1302
675
  *
@@ -1319,11 +692,11 @@ function brandString(value) {
1319
692
  return value;
1320
693
  }
1321
694
  //#endregion
1322
- //#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0._5b95bb48758e6032066aa142c5fbb450/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
695
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-timeout@0._c2aac02a97d68b3ee31a2a9dae07960b/node_modules/@deepseek-ai/dsh-timeout/lib/index.js
1323
696
  /** Largest delay Node schedules without clamping it to one millisecond. */
1324
697
  const MAX_TIMER_DELAY_MS = 2147483647;
1325
698
  //#endregion
1326
- //#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.5-rc.1_@deepseek-ai+cordis@4.0.1/node_modules/@deepseek-ai/dsh-llm/lib/index.js
699
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-llm@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-llm/lib/index.js
1327
700
  /**
1328
701
  * Detach and deep-freeze a message whose identity already exists.
1329
702
  * @param message - complete message, including its stable identity.
@@ -1338,10 +711,10 @@ function freezeMessage(message) {
1338
711
  * @returns an immutable message with a fresh stable identity.
1339
712
  */
1340
713
  function createMessage(input) {
1341
- return freezeMessage({
714
+ return deepFreeze(structuredClone({
1342
715
  ...input,
1343
716
  id: brandString(randomUUID$1())
1344
- });
717
+ }));
1345
718
  }
1346
719
  /**
1347
720
  * Create one identified user-role message and freeze it before publication.
@@ -1565,13 +938,15 @@ function failureSnapshot(value) {
1565
938
  const status = candidate.status;
1566
939
  const providerRetryAfterMs = candidate.providerRetryAfterMs;
1567
940
  const requestId = candidate.requestId;
1568
- if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0)) return void 0;
941
+ const offloadImages = candidate.offloadImages;
942
+ if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0) || offloadImages !== void 0 && (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)) return void 0;
1569
943
  return Object.freeze({
1570
944
  message,
1571
945
  code,
1572
946
  ...status === void 0 ? {} : { status },
1573
947
  ...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
1574
- ...requestId === void 0 ? {} : { requestId }
948
+ ...requestId === void 0 ? {} : { requestId },
949
+ ...offloadImages === void 0 ? {} : { offloadImages }
1575
950
  });
1576
951
  } catch (_sdkFailureGetter) {
1577
952
  return;
@@ -1601,25 +976,23 @@ function textOnlyImageText(ref) {
1601
976
  return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
1602
977
  }
1603
978
  /**
1604
- * True when typed model content contains an image block, walking nested
1605
- * tool-result content. This is the one recursive image walk shared by every
1606
- * image policy (capability gating, text-only serialization, compaction
1607
- * survey), so a consumer cannot silently diverge on nesting depth.
979
+ * True when typed model content contains an image block. This is the one image
980
+ * walk shared by every image policy (capability gating, text-only
981
+ * serialization, compaction survey), so a consumer cannot silently diverge.
1608
982
  * @param content - typed model content blocks.
1609
- * @returns whether any nested block is an image.
983
+ * @returns whether any block is an image.
1610
984
  */
1611
985
  function contentHasImage(content) {
1612
- return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
986
+ return content.some((block) => block.type === "image");
1613
987
  }
1614
988
  /**
1615
- * True when typed model content contains a file block, walking nested
1616
- * tool-result content on the same recursion every file policy shares.
989
+ * True when typed model content contains a file block.
1617
990
  * Reads current content on every call without retaining scan results.
1618
991
  * @param content - typed model content blocks.
1619
- * @returns whether any nested block is a file.
992
+ * @returns whether any block is a file.
1620
993
  */
1621
994
  function contentHasFile(content) {
1622
- for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
995
+ for (const block of content) if (block.type === "file") return true;
1623
996
  return false;
1624
997
  }
1625
998
  /**
@@ -1636,7 +1009,7 @@ function fileHandleText(ref, readonlyPath) {
1636
1009
  if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
1637
1010
  return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
1638
1011
  }
1639
- /** Replace every file occurrence, including nested tool results, with handle text. */
1012
+ /** Replace every file occurrence with handle text. */
1640
1013
  function replaceFilesWithHandles(blocks, resolvePath) {
1641
1014
  let next;
1642
1015
  for (const [index, block] of blocks.entries()) {
@@ -1648,29 +1021,10 @@ function replaceFilesWithHandles(blocks, resolvePath) {
1648
1021
  });
1649
1022
  continue;
1650
1023
  }
1651
- if (block.type === "tool-result") {
1652
- const content = replaceFilesWithHandles(block.content, resolvePath);
1653
- if (content !== block.content) {
1654
- next ??= blocks.slice(0, index);
1655
- next.push({
1656
- ...block,
1657
- content
1658
- });
1659
- continue;
1660
- }
1661
- }
1662
1024
  next?.push(block);
1663
1025
  }
1664
1026
  return next ?? blocks;
1665
1027
  }
1666
- /**
1667
- * Project durable file history into deterministic handle text for every model
1668
- * route. Unlike images, no provider receives file blocks natively, so this
1669
- * projection is unconditional in request assembly.
1670
- * @param messages - complete request history.
1671
- * @param resolvePath - resolve one reference's current execution-world read path.
1672
- * @returns the original list without files, otherwise shallow message copies with handle text.
1673
- */
1674
1028
  function projectFilesToText(messages, resolvePath) {
1675
1029
  if (!messages.some((message) => contentHasFile(message.content))) return messages;
1676
1030
  return messages.map((message) => {
@@ -1681,7 +1035,7 @@ function projectFilesToText(messages, resolvePath) {
1681
1035
  };
1682
1036
  });
1683
1037
  }
1684
- /** Replace every image occurrence, including nested tool results, for a text-only model. */
1038
+ /** Replace every image occurrence for a text-only model. */
1685
1039
  function replaceImagesForTextModel(blocks) {
1686
1040
  let next;
1687
1041
  for (const [index, block] of blocks.entries()) {
@@ -1693,26 +1047,10 @@ function replaceImagesForTextModel(blocks) {
1693
1047
  });
1694
1048
  continue;
1695
1049
  }
1696
- if (block.type === "tool-result") {
1697
- const content = replaceImagesForTextModel(block.content);
1698
- if (content !== block.content) {
1699
- next ??= blocks.slice(0, index);
1700
- next.push({
1701
- ...block,
1702
- content
1703
- });
1704
- continue;
1705
- }
1706
- }
1707
1050
  next?.push(block);
1708
1051
  }
1709
1052
  return next ?? blocks;
1710
1053
  }
1711
- /**
1712
- * Project durable image history into deterministic text for an exact text-only model.
1713
- * @param messages - complete request history.
1714
- * @returns the original list without images, otherwise shallow message copies with stable placeholders.
1715
- */
1716
1054
  function projectImagesForTextModel(messages) {
1717
1055
  if (!messages.some((message) => contentHasImage(message.content))) return messages;
1718
1056
  return messages.map((message) => {
@@ -1723,6 +1061,89 @@ function projectImagesForTextModel(messages) {
1723
1061
  };
1724
1062
  });
1725
1063
  }
1064
+ function withoutDeveloperMessages(messages) {
1065
+ const retained = messages.filter((message) => message.role !== "developer");
1066
+ return retained.length === messages.length ? messages : retained;
1067
+ }
1068
+ function toolDeclarations(tools, mode, history) {
1069
+ const declarations = new Map(history.tools.map((tool) => [tool.name, tool]));
1070
+ for (const update of history.updates) for (const tool of update.additions) if (!declarations.has(tool.name)) declarations.set(tool.name, {
1071
+ ...tool,
1072
+ deferLoading: true
1073
+ });
1074
+ switch (mode) {
1075
+ case "in-history": return declarations;
1076
+ case "addition-only": {
1077
+ const activeNames = new Set(tools?.map((tool) => tool.name));
1078
+ for (const name of declarations.keys()) if (!activeNames.has(name)) declarations.delete(name);
1079
+ return declarations;
1080
+ }
1081
+ /* v8 ignore next 2 -- closed-union exhaustiveness guard */
1082
+ default: return assertNever(mode);
1083
+ }
1084
+ }
1085
+ /**
1086
+ * Construct provider declarations from session-folded history without changing logged active tools.
1087
+ * Unsupported routes and incomplete history use current declarations without developer updates.
1088
+ * Explicitly deferred baseline tools become available only after their first retained addition.
1089
+ * @param messages - complete request inputs, or the prefix selected for an auxiliary call.
1090
+ * @param tools - currently active tool schemas.
1091
+ * @param toolUpdate - the resolved route's update mode.
1092
+ * @param history - immutable state folded from committed headers and developer messages.
1093
+ * @returns provider declarations and the corresponding filtered history.
1094
+ */
1095
+ function projectToolUpdates(messages, tools, toolUpdate, history) {
1096
+ if (toolUpdate === void 0) {
1097
+ let immediateTools = tools;
1098
+ if (tools?.some((tool) => tool.deferLoading === true)) immediateTools = tools.map(({ deferLoading: _loading, ...tool }) => tool);
1099
+ return {
1100
+ messages: withoutDeveloperMessages(messages),
1101
+ tools: immediateTools
1102
+ };
1103
+ }
1104
+ if (history === void 0) return {
1105
+ messages: withoutDeveloperMessages(messages),
1106
+ tools
1107
+ };
1108
+ const messageIds = new Set(messages.flatMap((message) => message.role === "developer" ? [message.id] : []));
1109
+ if (history.updates.some((update) => !messageIds.has(update.messageId))) return {
1110
+ messages: withoutDeveloperMessages(messages),
1111
+ tools
1112
+ };
1113
+ const declarations = toolDeclarations(tools, toolUpdate, history);
1114
+ const updateIds = new Set(history.updates.map((update) => update.messageId));
1115
+ const offered = new Set(history.tools.filter((tool) => !tool.deferLoading).map((tool) => tool.name));
1116
+ const projectedMessages = [];
1117
+ for (const message of messages) {
1118
+ if (message.role !== "developer") {
1119
+ projectedMessages.push(message);
1120
+ continue;
1121
+ }
1122
+ if (!updateIds.has(message.id)) continue;
1123
+ const content = message.content.filter((block) => {
1124
+ switch (block.type) {
1125
+ case "tool-addition":
1126
+ if (!declarations.has(block.toolName) || offered.has(block.toolName)) return false;
1127
+ offered.add(block.toolName);
1128
+ return true;
1129
+ case "tool-removal":
1130
+ if (toolUpdate !== "in-history") return false;
1131
+ return offered.delete(block.toolName);
1132
+ default: return true;
1133
+ }
1134
+ });
1135
+ if (content.length === 0) continue;
1136
+ if (content.length === message.content.length) projectedMessages.push(message);
1137
+ else projectedMessages.push({
1138
+ ...message,
1139
+ content
1140
+ });
1141
+ }
1142
+ return {
1143
+ messages: projectedMessages.length === messages.length && projectedMessages.every((message, index) => message === messages[index]) ? messages : projectedMessages,
1144
+ tools: [...declarations.values()]
1145
+ };
1146
+ }
1726
1147
  /**
1727
1148
  * Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
1728
1149
  * adapters from drifting. See
@@ -1802,7 +1223,8 @@ var LlmError = class extends HarnessError {
1802
1223
  code,
1803
1224
  ...options?.status === void 0 ? {} : { status: options.status },
1804
1225
  ...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
1805
- ...options?.requestId === void 0 ? {} : { requestId: options.requestId }
1226
+ ...options?.requestId === void 0 ? {} : { requestId: options.requestId },
1227
+ ...options?.offloadImages === void 0 ? {} : { offloadImages: options.offloadImages }
1806
1228
  });
1807
1229
  }
1808
1230
  };
@@ -2070,7 +1492,8 @@ var LlmError = class extends HarnessError {
2070
1492
  id: model.id,
2071
1493
  ...model.name === void 0 ? {} : { name: model.name },
2072
1494
  ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
2073
- ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
1495
+ ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
1496
+ ...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
2074
1497
  });
2075
1498
  }
2076
1499
  return models;
@@ -2128,7 +1551,8 @@ var LlmError = class extends HarnessError {
2128
1551
  }
2129
1552
  /**
2130
1553
  * Discover models advertised by one registered provider. Catalog membership
2131
- * is advisory and never changes routing or request validation.
1554
+ * does not constrain core routing. Catalog-driven entry points may restrict
1555
+ * selection and submission to the advertised models.
2132
1556
  * @param provider - registered provider route to inspect.
2133
1557
  * @returns detached model metadata in adapter-preferred order.
2134
1558
  */
@@ -2173,6 +1597,8 @@ var LlmError = class extends HarnessError {
2173
1597
  const inputModalities = this.detachedModalities(resolved.inputModalities);
2174
1598
  const systemPromptUpdate = resolved.systemPromptUpdate;
2175
1599
  if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new LlmError(`adapter returned invalid system prompt update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
1600
+ const toolUpdate = resolved.toolUpdate;
1601
+ if (toolUpdate !== void 0 && toolUpdate !== "in-history" && toolUpdate !== "addition-only") throw new LlmError(`adapter returned invalid tool update mode for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
2176
1602
  const defaultMaxTokens = resolved.defaultMaxTokens;
2177
1603
  if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
2178
1604
  const info = {
@@ -2183,7 +1609,8 @@ var LlmError = class extends HarnessError {
2183
1609
  ...inputModalities === void 0 ? {} : { inputModalities },
2184
1610
  ...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
2185
1611
  ...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens },
2186
- ...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate }
1612
+ ...resolved.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: resolved.systemPromptUpdate },
1613
+ ...resolved.toolUpdate === void 0 ? {} : { toolUpdate: resolved.toolUpdate }
2187
1614
  };
2188
1615
  const reasoning = resolved.reasoning;
2189
1616
  if (reasoning === void 0) return info;
@@ -2278,6 +1705,7 @@ var LlmError = class extends HarnessError {
2278
1705
  ...context === void 0 ? {} : { context },
2279
1706
  ...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
2280
1707
  ...modelInfo.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: modelInfo.systemPromptUpdate },
1708
+ ...modelInfo.toolUpdate === void 0 ? {} : { toolUpdate: modelInfo.toolUpdate },
2281
1709
  stream: (options) => {
2282
1710
  if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
2283
1711
  if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
@@ -2299,8 +1727,9 @@ var LlmError = class extends HarnessError {
2299
1727
  /** Remove replay state whose historical route is owned by another adapter. */
2300
1728
  forAdapter(options, adapter) {
2301
1729
  const messages = options.messages.map((message) => {
1730
+ if (message.role !== "assistant") return message;
2302
1731
  const source = message.source;
2303
- if (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
1732
+ if (source.replayState === void 0) return message;
2304
1733
  if (this.adapters.get(source.provider)?.adapter === adapter) return message;
2305
1734
  return freezeMessage({
2306
1735
  ...message,
@@ -2366,13 +1795,17 @@ var LlmError = class extends HarnessError {
2366
1795
  let projectedMessages = resolvedOptions.messages;
2367
1796
  if (projectedMessages.some((message) => contentHasFile(message.content))) projectedMessages = projectFilesToText(projectedMessages, (ref) => this.fileReadPath(ref));
2368
1797
  if (modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && projectedMessages.some((message) => contentHasImage(message.content))) projectedMessages = projectImagesForTextModel(projectedMessages);
2369
- const projectedOptions = projectedMessages === resolvedOptions.messages ? resolvedOptions : Object.isFrozen(resolvedOptions) ? deepFreeze({
2370
- ...resolvedOptions,
2371
- messages: projectedMessages
2372
- }) : {
2373
- ...resolvedOptions,
2374
- messages: projectedMessages
2375
- };
1798
+ const projectedTools = projectToolUpdates(projectedMessages, resolvedOptions.tools, modelInfo.toolUpdate, resolvedOptions.toolHistory);
1799
+ projectedMessages = projectedTools.messages;
1800
+ let projectedOptions = resolvedOptions;
1801
+ if (projectedMessages !== resolvedOptions.messages || projectedTools.tools !== resolvedOptions.tools) {
1802
+ projectedOptions = {
1803
+ ...resolvedOptions,
1804
+ messages: projectedMessages,
1805
+ ...projectedTools.tools === void 0 ? {} : { tools: projectedTools.tools }
1806
+ };
1807
+ if (Object.isFrozen(resolvedOptions)) deepFreeze(projectedOptions);
1808
+ }
2376
1809
  iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
2377
1810
  } catch (error) {
2378
1811
  yield adapterFailureChunk(error, options.signal);
@@ -2440,7 +1873,93 @@ function adapterFailureChunk(error, signal) {
2440
1873
  };
2441
1874
  }
2442
1875
  //#endregion
2443
- //#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1._aaa8ad560a1b4ea9b6e9490b78b8a32d/node_modules/@deepseek-ai/dsh-tools/lib/index.js
1876
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-sandbox@0._a8c9906be8c926809212c5fbb3bc7c61/node_modules/@deepseek-ai/dsh-sandbox/lib/index.js
1877
+ /**
1878
+ * The escalation vocabulary and choreography shared by every sandbox-enforcing
1879
+ * tool family (`@deepseek-ai/dsh-tool-bash`, `@deepseek-ai/dsh-tool-fs`): the
1880
+ * strictly-wider ladder, the argument-pairing validation, the model-facing
1881
+ * denial/hint markers, and {@link approveEscalation} — the ordered fail-closed
1882
+ * sequence that resolves a `sandbox_permissions` request through a
1883
+ * user-approval channel BEFORE anything executes. One home keeps the two
1884
+ * families' approval ordering and verbatim error texts from drifting apart.
1885
+ *
1886
+ * The channel is a minimal STRUCTURAL function shape ({@link EscalationAsk}),
1887
+ * not the approval service type: the tool layer — which owns the agent, the
1888
+ * call id, and the tool name — closes over `ctx.approval.request(...)` and
1889
+ * hands the closure down, so this package never depends on the approval or
1890
+ * agent packages.
1891
+ *
1892
+ * @module dsh-sandbox/escalation
1893
+ */
1894
+ /**
1895
+ * The strictly-wider table: what a call whose effective mode is the key may
1896
+ * escalate TO. Checked at EXECUTION, never baked into a tool schema — the
1897
+ * schema's enum is {@link ESCALATION_TARGETS}, because schemas are
1898
+ * registry-global while the effective mode is per-call truth.
1899
+ */
1900
+ const WIDER_MODES = {
1901
+ "read-only": ["workspace-write", "danger-full-access"],
1902
+ "workspace-write": ["danger-full-access"]
1903
+ };
1904
+ /**
1905
+ * The closed escalation-target vocabulary — every mode a call could ever
1906
+ * escalate TO (`read-only` is the floor; nothing escalates to it). Advertised
1907
+ * whenever the mounted capability confines: cutting the enum down to the modes
1908
+ * wider than the composition's DEFAULT would strand a session whose effective
1909
+ * mode sits below it (a `danger-full-access` default would advertise nothing
1910
+ * while a narrower-switched session stays confined with no lever).
1911
+ */
1912
+ const ESCALATION_TARGETS = ["workspace-write", "danger-full-access"];
1913
+ /**
1914
+ * Validate the escalation argument pairing a tool schema cannot express:
1915
+ * `sandbox_permissions` and `justification` travel together — an approval
1916
+ * prompt without a reason, or a reason driving nothing, is a malformed ask —
1917
+ * and the justification must be a non-empty sentence.
1918
+ * @param sandboxPermissions - the raw `sandbox_permissions` argument, if given.
1919
+ * @param justification - the raw `justification` argument, if given.
1920
+ */
1921
+ function validateEscalationArgs(sandboxPermissions, justification) {
1922
+ if (sandboxPermissions !== void 0 && justification === void 0) throw new Error("invalid escalation: sandbox_permissions requires a justification");
1923
+ if (justification !== void 0 && sandboxPermissions === void 0) throw new Error("invalid escalation: justification is only valid together with sandbox_permissions");
1924
+ if (justification !== void 0 && justification.trim().length === 0) throw new Error("invalid justification: expected a non-empty sentence");
1925
+ }
1926
+ /**
1927
+ * Resolve a sandbox permission request before execution. Repeating the call's
1928
+ * effective mode returns it without approval. A strictly wider mode requires
1929
+ * approval and applies only to this call. Narrower or unsupported targets,
1930
+ * missing approval services or agents for widening, and non-grant outcomes
1931
+ * throw before execution.
1932
+ * @param request - the escalation to judge (see {@link EscalationRequest}).
1933
+ * @param approval - the approval ingredients the tool holds (see {@link EscalationApproval}).
1934
+ * @returns the granted mode, consumed by the one call that asked.
1935
+ */
1936
+ async function approveEscalation(request, approval) {
1937
+ const { requestedMode: mode, effectiveMode, justification, subject } = request;
1938
+ if (mode === effectiveMode) return effectiveMode;
1939
+ if (!(WIDER_MODES[effectiveMode] ?? []).includes(mode)) throw new Error(`sandbox escalation to "${mode}" is not strictly wider than this call's current "${effectiveMode}" mode`);
1940
+ if (approval.approver === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval service is composed`);
1941
+ if (approval.agent === void 0) throw new Error(`sandbox escalation to "${mode}" requires approval, but the call has no agent to route it through`);
1942
+ const outcome = await approval.approver.request({
1943
+ agent: approval.agent,
1944
+ toolName: approval.toolName,
1945
+ callId: approval.callId,
1946
+ reason: `escalate sandbox to ${mode}: ${justification}`,
1947
+ displayReason: {
1948
+ en: `Allow this operation with ${mode} permissions: ${justification}`,
1949
+ zh: `允许本次操作使用 ${mode} 权限:${justification}`
1950
+ },
1951
+ ...approval.signal ? { signal: approval.signal } : {}
1952
+ });
1953
+ switch (outcome) {
1954
+ case "allowed-once": return mode;
1955
+ case "rejected": throw new Error(`the user rejected escalating this ${subject} to "${mode}"; it stays denied, so stop and explain instead of working around it`);
1956
+ case "cancelled": throw new Error(`approval for escalating to "${mode}" was cancelled`);
1957
+ case "unavailable": throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval channel is available`);
1958
+ default: return assertNever(outcome, "EscalationOutcome");
1959
+ }
1960
+ }
1961
+ //#endregion
1962
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-tools@0.1._97968258e5effb0cd09174fd979b65a8/node_modules/@deepseek-ai/dsh-tools/lib/index.js
2444
1963
  /**
2445
1964
  * Enforced JSON Schema subset shared by tool outputs, generated PTC mode
2446
1965
  * types, subagents, and workflows. The subset accepts any JSON root, an
@@ -3248,6 +2767,7 @@ var ToolArgsError = class extends HarnessError {
3248
2767
  function defineTool(options) {
3249
2768
  const userExecute = options.execute;
3250
2769
  const userFinalizeContent = options.finalizeContent;
2770
+ const userProjectContent = options.projectContent;
3251
2771
  const userRender = options.output.render;
3252
2772
  const userPresentationMeta = options.output.presentationMeta;
3253
2773
  const userPresentCall = options.presentCall;
@@ -3270,6 +2790,7 @@ function defineTool(options) {
3270
2790
  return userPresentationMeta(args, value);
3271
2791
  } } : {}
3272
2792
  },
2793
+ ...options.deferLoading === true ? { deferLoading: options.deferLoading } : {},
3273
2794
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
3274
2795
  async execute(args, exec) {
3275
2796
  const violations = validate(args);
@@ -3277,6 +2798,7 @@ function defineTool(options) {
3277
2798
  return userExecute(args, exec);
3278
2799
  }
3279
2800
  };
2801
+ if (userProjectContent) tool.projectContent = (exec, result) => userProjectContent(exec, result);
3280
2802
  if (userFinalizeContent) tool.finalizeContent = (exec, result) => userFinalizeContent(exec, result);
3281
2803
  if (userPresentCall) tool.presentCall = (args) => {
3282
2804
  if (validate(args).length > 0) return void 0;
@@ -3311,7 +2833,7 @@ const TYPESCRIPT_FLAVOR = {
3311
2833
  description: "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run.",
3312
2834
  codeDescription: "The program: the body of an async TypeScript function."
3313
2835
  };
3314
- /** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link CodeSdkLanguage}. */
2836
+ /** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */
3315
2837
  const RUN_CODE_FLAVORS = {
3316
2838
  typescript: TYPESCRIPT_FLAVOR,
3317
2839
  python: {
@@ -3326,17 +2848,48 @@ const RUN_CODE_FLAVORS = {
3326
2848
  * can never drift.
3327
2849
  */
3328
2850
  const RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION = "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\".";
2851
+ const RUN_CODE_CONTROLS = {
2852
+ timeoutMs: {
2853
+ type: "number",
2854
+ description: "Positive elapsed-time budget in milliseconds, capped by the deployment maximum."
2855
+ },
2856
+ sandbox_permissions: {
2857
+ type: "string",
2858
+ enum: [...ESCALATION_TARGETS],
2859
+ description: "Wider sandbox mode for this complete program execution; requires justification and approval."
2860
+ },
2861
+ justification: {
2862
+ type: "string",
2863
+ description: "Reason this complete program needs wider access, shown to the user for approval. Use the language of the user’s current request."
2864
+ }
2865
+ };
2866
+ function controlParameters(runtime) {
2867
+ if (runtime === void 0) return RUN_CODE_CONTROLS;
2868
+ return {
2869
+ ...runtime.timeout === void 0 ? {} : { timeoutMs: {
2870
+ ...RUN_CODE_CONTROLS.timeoutMs,
2871
+ description: `Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default ${runtime.timeout.defaultMs}; capped at ${runtime.timeout.maxMs}. Zero does not disable the deadline.`
2872
+ } },
2873
+ ...runtime.sandboxMode === void 0 ? {} : {
2874
+ sandbox_permissions: RUN_CODE_CONTROLS.sandbox_permissions,
2875
+ justification: RUN_CODE_CONTROLS.justification
2876
+ }
2877
+ };
2878
+ }
2879
+ function escalationGuidance(runtime) {
2880
+ return runtime?.sandboxMode === void 0 ? "" : " A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.";
2881
+ }
3329
2882
  /**
3330
2883
  * Resolve the {@link RunCodeFlavor} for the loaded runtime's language, read at
3331
2884
  * schema-emission time so the model-visible `run_code` schema always matches
3332
2885
  * the SDK section's language. `peekRuntime` returns `undefined` only when no
3333
2886
  * runtime is mounted, which reaches this function through definition readers
3334
2887
  * and `schemas()` — the doc-catalog harvest is the only shipped one, and none
3335
- * of them feeds a model, because `wireSchemas` calls `requireCodeRuntime`
2888
+ * of them feeds a model, because `wireSchemas` calls `requirePtcRuntime`
3336
2889
  * before projecting — so that path degrades to {@link TYPESCRIPT_FLAVOR}. A
3337
2890
  * mounted runtime whose language has no flavor entry fails loud, exactly as
3338
- * `requireCodeRuntime` rejects it at assembly. Keeping this table in step with
3339
- * `SDK_RENDERERS` is the compiler's job ({@link CodeSdkLanguage}); what this
2891
+ * `requirePtcRuntime` rejects it at assembly. Keeping this table in step with
2892
+ * `SDK_RENDERERS` is the compiler's job ({@link PtcSdkLanguage}); what this
3340
2893
  * guard owns is the runtime-supplied language neither table knows, which never
3341
2894
  * yields a wrong-language schema for a real runtime.
3342
2895
  */
@@ -3505,7 +3058,8 @@ function createRunCodeTool(registry, options) {
3505
3058
  type: "string",
3506
3059
  required: true,
3507
3060
  description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
3508
- }
3061
+ },
3062
+ ...RUN_CODE_CONTROLS
3509
3063
  },
3510
3064
  output: {
3511
3065
  schema: {
@@ -3517,12 +3071,37 @@ function createRunCodeTool(registry, options) {
3517
3071
  required: true,
3518
3072
  items: { type: "string" }
3519
3073
  },
3520
- result: { type: "json" }
3074
+ result: { type: "json" },
3075
+ sandbox: {
3076
+ type: "object",
3077
+ additionalProperties: false,
3078
+ properties: {
3079
+ mode: {
3080
+ type: "string",
3081
+ required: true,
3082
+ enum: [
3083
+ "read-only",
3084
+ "workspace-write",
3085
+ "danger-full-access"
3086
+ ]
3087
+ },
3088
+ denied: {
3089
+ type: "boolean",
3090
+ required: true
3091
+ },
3092
+ enforcement: {
3093
+ type: "string",
3094
+ enum: ["full", "partial"]
3095
+ }
3096
+ }
3097
+ }
3521
3098
  }
3522
3099
  },
3523
3100
  render: (_args, value) => {
3524
3101
  const rendered = value.result === void 0 ? "" : renderValue(value.result);
3525
3102
  const parts = [value.logs.join("\n"), rendered].filter((part) => part.length > 0);
3103
+ if (value.sandbox?.enforcement === "partial") parts.push("File sandbox enforcement is partial on this host.");
3104
+ if (value.sandbox?.denied) parts.push(`The ${value.sandbox.mode} file sandbox denied an operation.${escalationGuidance(peekRuntime())}`);
3526
3105
  return [{
3527
3106
  type: "text",
3528
3107
  text: parts.length > 0 ? parts.join("\n") : "(run_code completed with no output)"
@@ -3532,6 +3111,31 @@ function createRunCodeTool(registry, options) {
3532
3111
  async execute(args, exec) {
3533
3112
  if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
3534
3113
  const runtime = requireRuntime();
3114
+ validateEscalationArgs(args.sandbox_permissions, args.justification);
3115
+ if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
3116
+ if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
3117
+ const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
3118
+ let policy = standingPolicy;
3119
+ if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
3120
+ if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
3121
+ const approvedMode = await approveEscalation({
3122
+ requestedMode: args.sandbox_permissions,
3123
+ justification: args.justification,
3124
+ effectiveMode: standingPolicy.mode,
3125
+ subject: "program"
3126
+ }, {
3127
+ approver: options.peekApprover(),
3128
+ agent: exec.agent,
3129
+ callId: exec.callId,
3130
+ toolName: RUN_CODE_NAME,
3131
+ signal: exec.signal
3132
+ });
3133
+ policy = {
3134
+ ...standingPolicy,
3135
+ mode: approvedMode
3136
+ };
3137
+ }
3138
+ exec.signal.throwIfAborted();
3535
3139
  const runController = new AbortController();
3536
3140
  const onOuterAbort = () => {
3537
3141
  runController.abort(exec.signal.reason);
@@ -3613,7 +3217,8 @@ function createRunCodeTool(registry, options) {
3613
3217
  while (logWork.size > 0) await Promise.allSettled([...logWork]);
3614
3218
  };
3615
3219
  const runOver = () => runController.signal.aborted;
3616
- const binding = (name) => async (rawArgs) => {
3220
+ const binding = (schema) => async (rawArgs) => {
3221
+ const { name } = schema;
3617
3222
  if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
3618
3223
  const normalized = jsonNormalizeArgs(rawArgs);
3619
3224
  const n = ++dispatches;
@@ -3622,6 +3227,7 @@ function createRunCodeTool(registry, options) {
3622
3227
  callId: subCallId,
3623
3228
  rootCallId: exec.rootCallId,
3624
3229
  name,
3230
+ schema,
3625
3231
  arguments: normalized.dispatched,
3626
3232
  ...exec.agent ? { agent: exec.agent } : {},
3627
3233
  parent: exec.token,
@@ -3656,6 +3262,7 @@ function createRunCodeTool(registry, options) {
3656
3262
  name,
3657
3263
  arguments: normalized.logged,
3658
3264
  isError: result.isError,
3265
+ ...result.error?.info === void 0 ? {} : { error: result.error.info },
3659
3266
  content: logged
3660
3267
  });
3661
3268
  })().finally(() => {
@@ -3703,10 +3310,7 @@ function createRunCodeTool(registry, options) {
3703
3310
  const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
3704
3311
  if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
3705
3312
  content: result.content,
3706
- source: {
3707
- kind: "plugin",
3708
- plugin: "tools-ptc"
3709
- }
3313
+ source: { kind: "ptc-mode" }
3710
3314
  }));
3711
3315
  for (const context of result.additionalContexts ?? []) exec.deferContext(context);
3712
3316
  if (result.concludesTurn) exec.concludeTurn();
@@ -3726,13 +3330,13 @@ function createRunCodeTool(registry, options) {
3726
3330
  if (schema.name === "run_code") continue;
3727
3331
  Object.defineProperty(functions, schema.name, {
3728
3332
  enumerable: true,
3729
- value: binding(schema.name)
3333
+ value: binding(deepFreeze(schema))
3730
3334
  });
3731
3335
  }
3732
3336
  try {
3733
3337
  let result;
3734
3338
  try {
3735
- result = await runtime.run({
3339
+ result = await runtime.run(runtime.resolve({
3736
3340
  program: args.code,
3737
3341
  bindings: [{
3738
3342
  global: "tools",
@@ -3742,18 +3346,23 @@ function createRunCodeTool(registry, options) {
3742
3346
  memberNameProperty: "toolName"
3743
3347
  }
3744
3348
  }],
3745
- signal: runController.signal
3746
- });
3349
+ signal: runController.signal,
3350
+ ...exec.agent?.session.header.cwd !== void 0 ? { cwd: exec.agent.session.header.cwd } : {},
3351
+ ...policy !== void 0 ? { sandboxPolicy: policy } : {},
3352
+ ...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
3353
+ }));
3747
3354
  } finally {
3748
3355
  runController.abort("run_code settled");
3749
3356
  await drainDispatches();
3750
3357
  }
3751
3358
  if (result.error) {
3752
3359
  const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
3753
- throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}`);
3360
+ const sandboxText = result.sandbox === void 0 ? "" : `\nFile sandbox: ${result.sandbox.mode}${result.sandbox.enforcement === void 0 ? "" : `; enforcement: ${result.sandbox.enforcement}`}${result.sandbox.denied ? "; operation denied" : ""}.`;
3361
+ throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
3754
3362
  }
3755
3363
  return {
3756
3364
  logs: result.logs,
3365
+ ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
3757
3366
  ...result.value !== void 0 ? { result: result.value } : {}
3758
3367
  };
3759
3368
  } finally {
@@ -3769,7 +3378,11 @@ function createRunCodeTool(registry, options) {
3769
3378
  });
3770
3379
  Object.defineProperty(definition, "description", {
3771
3380
  enumerable: true,
3772
- get: () => resolveFlavor(peekRuntime).description
3381
+ get: () => {
3382
+ const runtime = peekRuntime();
3383
+ const instructions = runtime?.executionInstructions;
3384
+ return resolveFlavor(peekRuntime).description + (instructions ? ` ${instructions}` : "") + (runtime === void 0 ? "" : " The working directory is the Session's current directory.") + escalationGuidance(runtime);
3385
+ }
3773
3386
  });
3774
3387
  Object.defineProperty(definition, "parameters", {
3775
3388
  enumerable: true,
@@ -3783,7 +3396,8 @@ function createRunCodeTool(registry, options) {
3783
3396
  type: "string",
3784
3397
  required: true,
3785
3398
  description: RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION
3786
- }
3399
+ },
3400
+ ...controlParameters(peekRuntime())
3787
3401
  })
3788
3402
  });
3789
3403
  return definition;
@@ -4068,7 +3682,7 @@ function renderToolsSdk(schemas) {
4068
3682
  * PTC mode codegen — Python flavor. The pure projection from registered tool schemas to the
4069
3683
  * Python SDK text the model programs against under `runtime.language === 'python'`. Sibling of
4070
3684
  * {@link ./ts-types.ts | ts-types.ts}; the two files are two projections of the same registry
4071
- * store, keyed by the loaded {@link @deepseek-ai/dsh-code-runtime#CodeRuntime.language | code
3685
+ * store, keyed by the loaded {@link @deepseek-ai/dsh-ptc-runtime#PtcRuntime.language | PTC
4072
3686
  * runtime's language}.
4073
3687
  *
4074
3688
  * Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
@@ -4755,17 +4369,17 @@ function renderToolsSdkPy(schemas) {
4755
4369
  */
4756
4370
  /**
4757
4371
  * Language → SDK-section renderer. The registry looks up the loaded
4758
- * `ctx.codeRuntime.language` in this table when assembling the `tools:sdk`
4372
+ * `ctx.ptcRuntime.language` in this table when assembling the `tools:sdk`
4759
4373
  * section under a non-native mode; a runtime whose language is not a key
4760
4374
  * fails the assembly loudly (same idiom as `toolOrder` violations). Adding a
4761
- * new backend language is three parallel edits — a {@link CodeSdkLanguage}
4375
+ * new backend language is three parallel edits — a {@link PtcSdkLanguage}
4762
4376
  * member, an entry here, and a `RUN_CODE_FLAVORS` entry in `ptc.ts` for
4763
4377
  * its `run_code` schema strings — plus the renderer function this table points
4764
4378
  * at. The `satisfies` clause pins this table's key set to that union, which
4765
4379
  * the flavor table is checked against too, so any of the three left out is a
4766
4380
  * typecheck failure. What no check reaches is the prose that names the values
4767
- * instead of deriving them: the seam's `dsh-code-runtime` README pair, its
4768
- * `CodeRuntime.language` JSDoc, and `docs/subsystems/code-runtime.md`
4381
+ * instead of deriving them: the seam's `dsh-ptc-runtime` README pair, its
4382
+ * `PtcRuntime.language` JSDoc, and `docs/subsystems/ptc-runtime.md`
4769
4383
  * with its zh pair, plus this package's own README pair and the
4770
4384
  * {@link Config.mode} JSDoc.
4771
4385
  */
@@ -4941,6 +4555,8 @@ function resolveMaxParallelSubCalls(value) {
4941
4555
  cancellationStates = /* @__PURE__ */ new WeakMap();
4942
4556
  /** Definition-owned final content transform snapshotted before policy begins. */
4943
4557
  contentFinalizers = /* @__PURE__ */ new WeakMap();
4558
+ /** Execution-prepared content installed before post-execute policy. */
4559
+ contentProjectors = /* @__PURE__ */ new WeakMap();
4944
4560
  layers = new ScopedLayers((scope) => new ToolLayer(scope), () => {
4945
4561
  this.ctx.emit("tools/change");
4946
4562
  });
@@ -5000,12 +4616,13 @@ function resolveMaxParallelSubCalls(value) {
5000
4616
  return {
5001
4617
  name: "tools:sdk",
5002
4618
  order: this.ctx.systemPrompt.getSectionOrder("TOOLS_SDK"),
4619
+ interpolate: false,
5003
4620
  text: (context) => {
5004
4621
  const mode = this.modeFor(context.scope);
5005
4622
  if (mode === "native") return "";
5006
- const runtime = this.requireCodeRuntime(mode);
4623
+ const runtime = this.requirePtcRuntime(mode);
5007
4624
  const render = SDK_RENDERERS[runtime.language];
5008
- /* v8 ignore next -- requireCodeRuntime rejects an unknown language before this runs. */
4625
+ /* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */
5009
4626
  if (render === void 0) throw new Error(`dsh-tools: no SDK renderer for ${runtime.language}`);
5010
4627
  return render(this.sdkSchemas(context.scope));
5011
4628
  }
@@ -5034,10 +4651,16 @@ function resolveMaxParallelSubCalls(value) {
5034
4651
  * and only for scopes whose mode actually presents it.
5035
4652
  * @returns the shared transport definition.
5036
4653
  */
5037
- requireCodeTransport() {
4654
+ requirePtcTransport() {
5038
4655
  this.ptcTransport ??= createRunCodeTool(this, {
5039
- requireRuntime: () => this.requireCodeRuntime(this.defaultMode),
5040
- peekRuntime: () => this.ctx.get("codeRuntime"),
4656
+ requireRuntime: () => this.requirePtcRuntime(this.defaultMode),
4657
+ peekApprover: () => this.ctx.get("approval"),
4658
+ resolveSandboxPolicy: (exec) => {
4659
+ const policy = this.ctx.get("sandboxPolicy");
4660
+ if (policy === void 0) throw new Error("dsh-tools: confined PTC runtime requires sandboxPolicy");
4661
+ return policy.resolve(exec.agent === void 0 ? {} : { session: exec.agent.session });
4662
+ },
4663
+ peekRuntime: () => this.ctx.get("ptcRuntime"),
5041
4664
  maxParallel: this.maxParallelSubCalls,
5042
4665
  shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
5043
4666
  });
@@ -5082,7 +4705,7 @@ function resolveMaxParallelSubCalls(value) {
5082
4705
  schemas: [...view.visible.values()].map((definition) => this.schemaOf(definition, false)),
5083
4706
  knownNames: [...view.knownNames]
5084
4707
  };
5085
- this.requireCodeRuntime(mode);
4708
+ this.requirePtcRuntime(mode);
5086
4709
  const schemas = [...view.visible.values()].map((definition) => this.schemaOf(definition, false));
5087
4710
  if (mode === "ptc") return {
5088
4711
  schemas: schemas.filter((schema) => schema.name === RUN_CODE_NAME),
@@ -5094,10 +4717,10 @@ function resolveMaxParallelSubCalls(value) {
5094
4717
  };
5095
4718
  }
5096
4719
  /**
5097
- * Resolve the code runtime or throw the actionable misconfiguration error.
4720
+ * Resolve the PTC runtime or throw the actionable misconfiguration error.
5098
4721
  * Read at use time (assembly / run_code execution), NOT via static
5099
4722
  * `inject`: an inject entry would hold `ctx.tools` — and every tool plugin
5100
- * behind it — hostage to a code runtime existing even under `mode:
4723
+ * behind it — hostage to a PTC runtime existing even under `mode:
5101
4724
  * 'native'`.
5102
4725
  *
5103
4726
  * Assembly and `run_code` execution read separately, so the language is not
@@ -5107,9 +4730,9 @@ function resolveMaxParallelSubCalls(value) {
5107
4730
  * other. Binding it is deferred until a second backend ships (the first
5108
4731
  * point it is testable).
5109
4732
  */
5110
- requireCodeRuntime(mode) {
5111
- const runtime = this.ctx.get("codeRuntime");
5112
- if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a code runtime — load a ctx.codeRuntime implementation (e.g. @deepseek-ai/dsh-code-runtime-worker-thread) or set tools mode to "native"`);
4733
+ requirePtcRuntime(mode) {
4734
+ const runtime = this.ctx.get("ptcRuntime");
4735
+ if (!runtime) throw new Error(`dsh-tools: mode "${mode}" requires a PTC runtime — load a ctx.ptcRuntime implementation (e.g. @deepseek-ai/dsh-ptc-runtime-node) or set tools mode to "native"`);
5113
4736
  if (!Object.hasOwn(SDK_RENDERERS, runtime.language)) {
5114
4737
  const known = Object.keys(SDK_RENDERERS).map((name) => JSON.stringify(name)).join(", ");
5115
4738
  throw new Error(`dsh-tools: no SDK renderer registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`);
@@ -5223,7 +4846,7 @@ function resolveMaxParallelSubCalls(value) {
5223
4846
  knownNames.add(name);
5224
4847
  visible.set(name, definition);
5225
4848
  }
5226
- if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.requireCodeTransport());
4849
+ if (this.modeFor(scope) !== "native") visible.set(RUN_CODE_NAME, this.requirePtcTransport());
5227
4850
  return {
5228
4851
  visible,
5229
4852
  knownNames,
@@ -5284,13 +4907,14 @@ function resolveMaxParallelSubCalls(value) {
5284
4907
  }
5285
4908
  /** Project one definition onto the model-facing schema fields. */
5286
4909
  schemaOf(definition, detachParameters) {
5287
- const { name, description, parameters } = definition;
4910
+ const { name, description, parameters, deferLoading } = definition;
5288
4911
  const detached = detachParameters ? snapshotJsonValue(parameters) : parameters;
5289
4912
  if (detached === void 0) throw new Error(`tool "${name}" parameters must be lossless JSON before schema projection`);
5290
4913
  return {
5291
4914
  name,
5292
4915
  description,
5293
- parameters: detached
4916
+ parameters: detached,
4917
+ ...deferLoading === true ? { deferLoading } : {}
5294
4918
  };
5295
4919
  }
5296
4920
  /**
@@ -5394,6 +5018,7 @@ function resolveMaxParallelSubCalls(value) {
5394
5018
  signal,
5395
5019
  ...agent !== void 0 ? { agent } : {},
5396
5020
  ...parent !== void 0 ? { parent } : {},
5021
+ ...exec.schema !== void 0 ? { schema: exec.schema } : {},
5397
5022
  deferContext(context) {
5398
5023
  deferredContexts.push(context);
5399
5024
  },
@@ -5402,6 +5027,7 @@ function resolveMaxParallelSubCalls(value) {
5402
5027
  }
5403
5028
  };
5404
5029
  const capturedFinalizer = visible?.finalizeContent?.bind(visible);
5030
+ const capturedProjector = visible?.projectContent?.bind(visible);
5405
5031
  const finalizerFor = () => collapsed && !signal.aborted ? void 0 : capturedFinalizer;
5406
5032
  try {
5407
5033
  const detached = snapshotJsonValue(exec.arguments);
@@ -5412,6 +5038,7 @@ function resolveMaxParallelSubCalls(value) {
5412
5038
  };
5413
5039
  this.deferredContexts.set(execution, deferredContexts);
5414
5040
  this.contentFinalizers.set(execution, finalizerFor());
5041
+ if (!collapsed) this.contentProjectors.set(execution, capturedProjector);
5415
5042
  this.cancellationStates.set(execution, {
5416
5043
  callerSignal: signal,
5417
5044
  bodyInvoked: false
@@ -5476,7 +5103,13 @@ function resolveMaxParallelSubCalls(value) {
5476
5103
  exec,
5477
5104
  result: toolAbortedBeforeDispatchResult()
5478
5105
  });
5106
+ if (decision.kind === "cancel") return await next({
5107
+ kind: "post-result",
5108
+ exec,
5109
+ result: toolAbortedBeforeDispatchResult()
5110
+ });
5479
5111
  const denialReason = decision.kind === "allow" ? this.guardReason(exec) : decision.reason;
5112
+ const denialInfo = decision.kind === "deny" ? decision.info : void 0;
5480
5113
  if (denialReason !== void 0) return await next({
5481
5114
  kind: "post-result",
5482
5115
  exec,
@@ -5486,7 +5119,10 @@ function resolveMaxParallelSubCalls(value) {
5486
5119
  text: `Error: ${denialReason}`
5487
5120
  }],
5488
5121
  isError: true,
5489
- error: { message: denialReason }
5122
+ error: {
5123
+ message: denialReason,
5124
+ ...denialInfo === void 0 ? {} : { info: denialInfo }
5125
+ }
5490
5126
  })
5491
5127
  });
5492
5128
  if (this.callerCancelled(exec)) return await next({
@@ -5592,7 +5228,14 @@ function resolveMaxParallelSubCalls(value) {
5592
5228
  */
5593
5229
  async finalizeScheduledExecution(exec, result) {
5594
5230
  try {
5595
- const postResult = await this.postExecute(exec, result);
5231
+ const project = this.contentProjectors.get(exec);
5232
+ this.contentProjectors.delete(exec);
5233
+ const content = project?.(exec, result);
5234
+ const projected = content === void 0 ? result : this.markCanonical(exec, this.materializeFinalResult({
5235
+ ...result,
5236
+ content
5237
+ }));
5238
+ const postResult = await this.postExecute(exec, projected);
5596
5239
  return this.finishScheduledExecution(exec, this.callerCancelled(exec) && !postResult.isError ? this.cancellationResult(exec, postResult) : postResult);
5597
5240
  } catch (error) {
5598
5241
  return this.finishScheduledExecution(exec, toolErrorResult(error));
@@ -5684,6 +5327,7 @@ function resolveMaxParallelSubCalls(value) {
5684
5327
  toolName: exec.name,
5685
5328
  callId: exec.callId,
5686
5329
  ...ask.reason !== void 0 ? { reason: ask.reason } : {},
5330
+ ...ask.displayReason !== void 0 ? { displayReason: ask.displayReason } : {},
5687
5331
  signal: exec.signal
5688
5332
  });
5689
5333
  switch (outcome) {
@@ -6186,40 +5830,56 @@ const IMAGE_SIZES = [
6186
5830
  "2K",
6187
5831
  "4K"
6188
5832
  ];
6189
- /** Cordis configuration schema. */
5833
+ /**
5834
+ * Mark a schema field live for DSH 0.1.7+: the host serves a plugin's settings
5835
+ * form only for `.volatile()` fields and hands `apply` a `Volatile` box per
5836
+ * field (unwrapped in `src/index.ts`). Hosts ≤0.1.6 ship a schemastery without
5837
+ * the method, so marking is conditional — an unmarked schema stays plain there
5838
+ * and the old settings relay keeps working.
5839
+ */
5840
+ function volatile(schema) {
5841
+ const mark = schema.volatile;
5842
+ return typeof mark === "function" ? mark.call(schema) : schema;
5843
+ }
5844
+ /**
5845
+ * Cordis configuration schema. Volatile marking happens per field (see
5846
+ * `volatile`); the `z<Config>` annotation keeps the plain output type for the
5847
+ * rest of the codebase because the wrapping is unwrapped at the entry.
5848
+ */
6190
5849
  const Config = z.object({
6191
- provider: z.union(IMAGE_PROVIDERS).default("google"),
6192
- googleModel: z.string().default(DEFAULT_GOOGLE_MODEL),
6193
- googleEndpoint: z.string().default(DEFAULT_GOOGLE_ENDPOINT),
6194
- openaiBaseURL: z.string().default(DEFAULT_OPENAI_BASE_URL),
6195
- openaiModel: z.string().default(DEFAULT_OPENAI_MODEL),
6196
- openaiCompatBaseURL: z.string().default(""),
6197
- openaiCompatModel: z.string().default(""),
6198
- openaiCompatEditFormat: z.union([z.const("multipart"), z.const("jsonImageUrlArray")]).default("multipart"),
6199
- openaiCompatEditExtra: z.dict(z.any()).default({}),
6200
- seedreamBaseURL: z.string().default(DEFAULT_SEEDREAM_BASE_URL),
6201
- seedreamModel: z.string().default(DEFAULT_SEEDREAM_MODEL),
6202
- seedreamOutputFormat: z.union(ARK_OUTPUT_FORMATS).default("jpeg"),
6203
- seedreamWatermark: z.boolean().default(true),
6204
- seedreamBackground: z.union(ARK_BACKGROUND_MODES).default("opaque"),
6205
- dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
6206
- dashscopeModel: z.string().default(DEFAULT_DASHSCOPE_MODEL),
6207
- xaiBaseURL: z.string().default(DEFAULT_XAI_BASE_URL),
6208
- xaiModel: z.string().default(DEFAULT_XAI_MODEL),
6209
- zhipuBaseURL: z.string().default(DEFAULT_ZHIPU_BASE_URL),
6210
- zhipuModel: z.string().default(DEFAULT_ZHIPU_MODEL),
6211
- comfyuiBaseURL: z.string().default(DEFAULT_COMFYUI_BASE_URL),
6212
- comfyuiWorkflows: z.array(z.object({
5850
+ provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
5851
+ googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
5852
+ googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
5853
+ openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
5854
+ openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
5855
+ openaiCompatBaseURL: volatile(z.string().default("")),
5856
+ openaiCompatModel: volatile(z.string().default("")),
5857
+ openaiCompatEditFormat: volatile(z.union([z.const("multipart"), z.const("jsonImageUrlArray")]).default("multipart")),
5858
+ openaiCompatEditExtra: volatile(z.dict(z.any()).default({})),
5859
+ seedreamBaseURL: volatile(z.string().default(DEFAULT_SEEDREAM_BASE_URL)),
5860
+ seedreamModel: volatile(z.string().default(DEFAULT_SEEDREAM_MODEL)),
5861
+ seedreamOutputFormat: volatile(z.union(ARK_OUTPUT_FORMATS).default("jpeg")),
5862
+ seedreamWatermark: volatile(z.boolean().default(true)),
5863
+ seedreamBackground: volatile(z.union(ARK_BACKGROUND_MODES).default("opaque")),
5864
+ dashscopeEndpoint: volatile(z.string().default(DEFAULT_DASHSCOPE_ENDPOINT)),
5865
+ dashscopeModel: volatile(z.string().default(DEFAULT_DASHSCOPE_MODEL)),
5866
+ xaiBaseURL: volatile(z.string().default(DEFAULT_XAI_BASE_URL)),
5867
+ xaiModel: volatile(z.string().default(DEFAULT_XAI_MODEL)),
5868
+ zhipuBaseURL: volatile(z.string().default(DEFAULT_ZHIPU_BASE_URL)),
5869
+ zhipuModel: volatile(z.string().default(DEFAULT_ZHIPU_MODEL)),
5870
+ comfyuiBaseURL: volatile(z.string().default(DEFAULT_COMFYUI_BASE_URL)),
5871
+ comfyuiWorkflows: volatile(z.array(z.object({
6213
5872
  name: z.string(),
6214
5873
  json: z.string(),
6215
5874
  presetPrompt: z.string().default("")
6216
- })).default([]),
6217
- comfyuiActiveWorkflow: z.string().default(""),
6218
- comfyuiWorkflowJson: z.string().default(""),
6219
- comfyuiWorkflowName: z.string().default(""),
6220
- comfyuiTimeoutMs: z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS),
6221
- saveToWorkspace: z.boolean().default(true),
6222
- workspaceFolder: z.string().default(DEFAULT_WORKSPACE_FOLDER)
5875
+ })).default([])),
5876
+ comfyuiActiveWorkflow: volatile(z.string().default("")),
5877
+ comfyuiWorkflowJson: volatile(z.string().default("")),
5878
+ comfyuiWorkflowName: volatile(z.string().default("")),
5879
+ comfyuiTimeoutMs: volatile(z.number().min(1e3).max(36e5).default(DEFAULT_COMFYUI_TIMEOUT_MS)),
5880
+ saveToWorkspace: volatile(z.boolean().default(true)),
5881
+ workspaceFolder: volatile(z.string().default(DEFAULT_WORKSPACE_FOLDER)),
5882
+ showProviderPill: volatile(z.boolean().default(false))
6223
5883
  });
6224
5884
  /** Resolve exactly one provider profile for a tool call. */
6225
5885
  function resolveProvider(config) {
@@ -6400,7 +6060,7 @@ function selectComfyUIWorkflow(active, requested) {
6400
6060
  return workflow;
6401
6061
  }
6402
6062
  //#endregion
6403
- //#region node_modules/.pnpm/@deepseek-ai+dsh-credential_bc2d3e5c9a179df7ac53b40c354d894e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
6063
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-credential_98fa173416f048996704a605a96a2b0e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
6404
6064
  /**
6405
6065
  * Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
6406
6066
  * *references* to secrets — environment-variable names — while providers own
@@ -6722,6 +6382,13 @@ function parseImageAttachmentRef(value) {
6722
6382
  ...originalDimensions === void 0 ? {} : { originalDimensions }
6723
6383
  };
6724
6384
  }
6385
+ /**
6386
+ * Newest matching image in one message's blocks. Tool results arrive in two
6387
+ * shapes: 0.1.7 promotes them to first-class `role: 'tool'` messages (walked
6388
+ * by the message-level callers below), while <=0.1.6 nests them as blocks with
6389
+ * a `content` array inside a message. The 0.1.7 `ContentBlock` union declares
6390
+ * no nested `content`, so recurse only when a block actually carries one.
6391
+ */
6725
6392
  function findInBlocks(blocks, sourceAttachmentId) {
6726
6393
  for (let index = blocks.length - 1; index >= 0; index -= 1) {
6727
6394
  const block = blocks[index];
@@ -6730,9 +6397,10 @@ function findInBlocks(blocks, sourceAttachmentId) {
6730
6397
  if (sourceAttachmentId === void 0 || attachmentIdsEqual(String(block.attachment.attachmentId), sourceAttachmentId)) return block.attachment;
6731
6398
  continue;
6732
6399
  }
6733
- if (block.type === "tool-result") {
6734
- const nested = findInBlocks(block.content, sourceAttachmentId);
6735
- if (nested !== void 0) return nested;
6400
+ const nested = block.content;
6401
+ if (Array.isArray(nested)) {
6402
+ const found = findInBlocks(nested, sourceAttachmentId);
6403
+ if (found !== void 0) return found;
6736
6404
  }
6737
6405
  }
6738
6406
  }
@@ -6751,7 +6419,8 @@ function collectInBlocks(blocks) {
6751
6419
  const refs = [];
6752
6420
  for (const block of blocks) {
6753
6421
  if (block.type === "image") refs.push(block.attachment);
6754
- if (block.type === "tool-result") refs.push(...collectInBlocks(block.content));
6422
+ const nested = block.content;
6423
+ if (Array.isArray(nested)) refs.push(...collectInBlocks(nested));
6755
6424
  }
6756
6425
  return refs;
6757
6426
  }
@@ -8609,6 +8278,16 @@ async function formTokenRequest(url, params, fetchImpl) {
8609
8278
  const OK_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录成功,可以关闭此页返回设置。</p>";
8610
8279
  const ERR_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录失败,请返回设置重试。</p>";
8611
8280
  /**
8281
+ * Loopback addresses accepted by the request handler. On dual-stack systems
8282
+ * Node reports IPv4-mapped IPv6 addresses as `::ffff:127.0.0.1`, so we must
8283
+ * accept that form in addition to the canonical IPv4 and IPv6 loopback.
8284
+ */
8285
+ const LOOPBACK_ADDRESSES = /* @__PURE__ */ new Set([
8286
+ "127.0.0.1",
8287
+ "::1",
8288
+ "::ffff:127.0.0.1"
8289
+ ]);
8290
+ /**
8612
8291
  * Listen on the redirect_uri's port and resolve when the provider calls back.
8613
8292
  * Rejects on port conflicts, network errors, or when nothing arrives in time.
8614
8293
  */
@@ -8621,6 +8300,12 @@ function startLoopback(options) {
8621
8300
  const state = { done: false };
8622
8301
  return new Promise((resolve, reject) => {
8623
8302
  const server = http.createServer((req, res) => {
8303
+ const remote = req.socket.remoteAddress ?? "";
8304
+ if (!LOOPBACK_ADDRESSES.has(remote)) {
8305
+ res.writeHead(403);
8306
+ res.end();
8307
+ return;
8308
+ }
8624
8309
  const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
8625
8310
  if (!url.pathname.startsWith(path)) {
8626
8311
  res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
@@ -8652,7 +8337,7 @@ function startLoopback(options) {
8652
8337
  server.close();
8653
8338
  reject(/* @__PURE__ */ new Error("loopback timeout: no callback received"));
8654
8339
  }, timeoutMs);
8655
- server.listen(port, parsed.hostname);
8340
+ server.listen(port);
8656
8341
  });
8657
8342
  }
8658
8343
  //#endregion
@@ -21554,14 +21239,19 @@ function providerOverrideOf(value) {
21554
21239
  return value;
21555
21240
  }
21556
21241
  function apply(ctx, config = {}) {
21557
- let current = () => migrateOpenAICompatConfig(config);
21242
+ const plainConfig = (source) => {
21243
+ const out = {};
21244
+ for (const [key, value] of Object.entries(source)) out[key] = value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value;
21245
+ return out;
21246
+ };
21247
+ let current = () => migrateOpenAICompatConfig(plainConfig(config));
21558
21248
  const knownWorkspaceRoots = /* @__PURE__ */ new Set();
21559
21249
  const canvasMirror = new CanvasMirror();
21560
21250
  const subscriptionManager = new SubscriptionManager(ctx);
21561
21251
  registerSubscriptionRoutes(ctx, subscriptionManager);
21562
- installImageSettings(ctx, config, {
21252
+ installImageSettings(ctx, current(), {
21563
21253
  setSource: (source) => {
21564
- current = () => migrateOpenAICompatConfig(source());
21254
+ current = () => migrateOpenAICompatConfig(plainConfig(source()));
21565
21255
  },
21566
21256
  onChange: () => {}
21567
21257
  });
@@ -22145,26 +21835,21 @@ function imagePresentation(result) {
22145
21835
  }
22146
21836
  /**
22147
21837
  * Wire the settings namespace across both dsh-settings API generations.
22148
- * A namespace import keeps module loading safe on either version; the branch
22149
- * picks the service method (0.1.2+) or the legacy top-level relay (<= rc.2),
22150
- * and falls back to the composition entry with a warning when neither exists
22151
- * so an incompatible host degrades the settings UI instead of failing boot.
21838
+ * The probe reads the HOST's `settings` service, never the bundled
21839
+ * dsh-settings module: the build inlines that module, so its exports always
21840
+ * look like the compile-time generation while only the service handed to this
21841
+ * fiber reflects the running host. dsh-settings 0.1.2–0.1.6 put `installSection`
21842
+ * on the service, so the namespace registers through it; DSH 0.1.7 replaced it
21843
+ * with schema-derived forms served by the loader itself (keyed by entry id),
21844
+ * so its service carries no installer and a host without any settings service
21845
+ * simply never runs the callback — both stay silent instead of warning.
22152
21846
  */
22153
21847
  function installImageSettings(ctx, config, hooks) {
22154
- const namespace = lib_exports;
22155
- if (typeof namespace.SettingsProvider?.prototype?.installSection === "function") {
22156
- ctx.inject(["settings"], (settingsCtx) => {
22157
- settingsCtx.settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
22158
- });
22159
- return;
22160
- }
22161
- const legacyInstall = namespace.installSettingsSection;
22162
- const legacyNamespace = namespace.settingsNamespace;
22163
- if (typeof legacyInstall === "function" && typeof legacyNamespace === "function") {
22164
- legacyInstall(ctx, legacyNamespace(IMAGE_GENERATION_NAMESPACE), Config, config, hooks);
22165
- return;
22166
- }
22167
- ctx.logger.warn("dsh-image-gen: this DSH exposes neither settings API generation; settings UI stays on the composition entry");
21848
+ ctx.inject(["settings"], (settingsCtx) => {
21849
+ const settings = settingsCtx.settings;
21850
+ if (typeof settings.installSection !== "function") return;
21851
+ settings.installSection(ctx, IMAGE_GENERATION_NAMESPACE, Config, config, hooks);
21852
+ });
22168
21853
  }
22169
21854
  //#endregion
22170
21855
  export { CANVAS_STATE_ROUTE, Config, DELETE_ROUTE, IMAGE_ROUTE, INSPIRATION_ROUTE, SAVE_WORKSPACE_ROUTE, STUDIO_ROUTE, TEST_CONNECTION_ROUTE, apply, imageAttachmentFromMeta, inject, name };