@mengyuly/dsh-ponytail 0.1.2

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 ADDED
@@ -0,0 +1,2520 @@
1
+ import { createRequire } from "node:module";
2
+ import { Service } from "@deepseek-ai/cordis";
3
+ import { mkdirSync, readFileSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ //#region ../../llm/llm/src/brand.ts
7
+ /**
8
+ * Brand a message identifier.
9
+ * @param id - the opaque message identifier.
10
+ * @returns the same string, branded; no validation is performed.
11
+ */
12
+ function MessageId(id) {
13
+ return id;
14
+ }
15
+ //#endregion
16
+ //#region ../../llm/llm/src/call-config.ts
17
+ /**
18
+ * Deep-freeze a value in place with an iterative traversal, guarding cycles,
19
+ * so later mutation throws without imposing a JavaScript call-stack depth cap.
20
+ * {@link AbortSignal} objects are deliberately skipped because they are the
21
+ * request's live cancellation channel and freezing them breaks abort.
22
+ * @param value - the value to freeze in place.
23
+ * @returns the same value, frozen.
24
+ */
25
+ function deepFreeze(value) {
26
+ const seen = /* @__PURE__ */ new WeakSet();
27
+ const pending = [{
28
+ kind: "visit",
29
+ node: value
30
+ }];
31
+ while (pending.length > 0) {
32
+ const task = pending.pop();
33
+ /* v8 ignore next -- the loop condition guarantees one pending task. */
34
+ if (task === void 0) continue;
35
+ if (task.kind === "property") {
36
+ pending.push({
37
+ kind: "visit",
38
+ node: task.source[task.key]
39
+ });
40
+ continue;
41
+ }
42
+ const node = task.node;
43
+ if (node === null || typeof node !== "object") continue;
44
+ if (node instanceof AbortSignal) continue;
45
+ if (seen.has(node)) continue;
46
+ seen.add(node);
47
+ Object.freeze(node);
48
+ const keys = Object.keys(node);
49
+ for (let index = keys.length - 1; index >= 0; index--) {
50
+ const key = keys[index];
51
+ /* v8 ignore next -- the loop is bounded by the captured key count. */
52
+ if (key === void 0) continue;
53
+ pending.push({
54
+ kind: "property",
55
+ source: node,
56
+ key
57
+ });
58
+ }
59
+ }
60
+ return value;
61
+ }
62
+ //#endregion
63
+ //#region ../../llm/llm/src/message.ts
64
+ /** Message value types, identity, and immutable construction helpers. */
65
+ /**
66
+ * Detach and deep-freeze a message whose identity already exists.
67
+ * @param message - complete message, including its stable identity.
68
+ * @returns an immutable snapshot that preserves the identity.
69
+ */
70
+ function freezeMessage(message) {
71
+ return deepFreeze(structuredClone(message));
72
+ }
73
+ /**
74
+ * Create one identified message and freeze it before publication.
75
+ * @param input - complete role, content, and source for a new message.
76
+ * @returns an immutable message with a fresh stable identity.
77
+ */
78
+ function createMessage(input) {
79
+ return freezeMessage({
80
+ ...input,
81
+ id: MessageId(crypto.randomUUID())
82
+ });
83
+ }
84
+ /**
85
+ * Create one identified user-role message and freeze it before publication.
86
+ * @param input - complete content and source for a new user message.
87
+ * @returns an immutable user message with a fresh stable identity.
88
+ */
89
+ function createUserMessage(input) {
90
+ return createMessage({
91
+ ...input,
92
+ role: "user"
93
+ });
94
+ }
95
+ //#endregion
96
+ //#region ../../../vendor/cosmokit/src/misc.ts
97
+ /** Return true when a value is `null` or `undefined`. */
98
+ function isNullable(value) {
99
+ return value === null || value === void 0;
100
+ }
101
+ /** Return true for non-array object values. */
102
+ function isPlainObject(data) {
103
+ return data && typeof data === "object" && !Array.isArray(data);
104
+ }
105
+ /** Filter object entries and return a new object. */
106
+ function filterKeys(object, filter) {
107
+ return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
108
+ }
109
+ /** Map object values while preserving the original key set. */
110
+ function mapValues(object, transform) {
111
+ return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
112
+ }
113
+ /** Pick selected keys from an object, optionally including `undefined` values. */
114
+ function pick(source, keys, forced) {
115
+ if (!keys) return { ...source };
116
+ const result = {};
117
+ for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
118
+ return result;
119
+ }
120
+ //#endregion
121
+ //#region ../../../vendor/cosmokit/src/types.ts
122
+ /** Test values using `instanceof` with a `toStringTag` fallback. */
123
+ function is(type, value) {
124
+ if (arguments.length === 1) return (value) => is(type, value);
125
+ return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
126
+ }
127
+ function isArrayBufferLike(value) {
128
+ return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
129
+ }
130
+ function isArrayBufferSource(value) {
131
+ return isArrayBufferLike(value) || ArrayBuffer.isView(value);
132
+ }
133
+ let Binary;
134
+ (function(_Binary) {
135
+ _Binary.is = isArrayBufferLike;
136
+ _Binary.isSource = isArrayBufferSource;
137
+ function fromSource(source) {
138
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
139
+ else return source;
140
+ }
141
+ _Binary.fromSource = fromSource;
142
+ function toBase64(source) {
143
+ source = fromSource(source);
144
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
145
+ let binary = "";
146
+ const bytes = new Uint8Array(source);
147
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
148
+ return btoa(binary);
149
+ }
150
+ _Binary.toBase64 = toBase64;
151
+ function fromBase64(source) {
152
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
153
+ return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
154
+ }
155
+ _Binary.fromBase64 = fromBase64;
156
+ function toHex(source) {
157
+ source = fromSource(source);
158
+ if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
159
+ return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
160
+ }
161
+ _Binary.toHex = toHex;
162
+ function fromHex(source) {
163
+ if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
164
+ const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
165
+ const buffer = [];
166
+ for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
167
+ return Uint8Array.from(buffer).buffer;
168
+ }
169
+ _Binary.fromHex = fromHex;
170
+ })(Binary || (Binary = {}));
171
+ Binary.fromBase64;
172
+ Binary.toBase64;
173
+ Binary.fromHex;
174
+ Binary.toHex;
175
+ /** Deep-clone common JavaScript values while preserving prototypes and cycles. */
176
+ function clone(source, refs = /* @__PURE__ */ new Map()) {
177
+ if (!source || typeof source !== "object") return source;
178
+ if (is("Date", source)) return new Date(source.valueOf());
179
+ if (is("RegExp", source)) return new RegExp(source.source, source.flags);
180
+ if (isArrayBufferLike(source)) return source.slice(0);
181
+ if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
182
+ const cached = refs.get(source);
183
+ if (cached) return cached;
184
+ if (Array.isArray(source)) {
185
+ const result = [];
186
+ refs.set(source, result);
187
+ source.forEach((value, index) => {
188
+ result[index] = Reflect.apply(clone, null, [value, refs]);
189
+ });
190
+ return result;
191
+ }
192
+ const result = Object.create(Object.getPrototypeOf(source));
193
+ refs.set(source, result);
194
+ for (const key of Reflect.ownKeys(source)) {
195
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
196
+ if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
197
+ Reflect.defineProperty(result, key, descriptor);
198
+ }
199
+ return result;
200
+ }
201
+ /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
202
+ function deepEqual(a, b, strict) {
203
+ if (a === b) return true;
204
+ if (!strict && isNullable(a) && isNullable(b)) return true;
205
+ if (typeof a !== typeof b) return false;
206
+ if (typeof a !== "object") return false;
207
+ if (!a || !b) return false;
208
+ function check(test, then) {
209
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
210
+ }
211
+ return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
212
+ if (a.byteLength !== b.byteLength) return false;
213
+ const viewA = new Uint8Array(a);
214
+ const viewB = new Uint8Array(b);
215
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
216
+ return true;
217
+ }) ?? Object.keys({
218
+ ...a,
219
+ ...b
220
+ }).every((key) => deepEqual(a[key], b[key], strict));
221
+ }
222
+ //#endregion
223
+ //#region ../../../vendor/cosmokit/src/time.ts
224
+ let Time;
225
+ (function(_Time) {
226
+ _Time.millisecond = 1;
227
+ const second = _Time.second = 1e3;
228
+ const minute = _Time.minute = second * 60;
229
+ const hour = _Time.hour = minute * 60;
230
+ const day = _Time.day = hour * 24;
231
+ const week = _Time.week = day * 7;
232
+ let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
233
+ function setTimezoneOffset(offset) {
234
+ timezoneOffset = offset;
235
+ }
236
+ _Time.setTimezoneOffset = setTimezoneOffset;
237
+ function getTimezoneOffset() {
238
+ return timezoneOffset;
239
+ }
240
+ _Time.getTimezoneOffset = getTimezoneOffset;
241
+ function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
242
+ if (typeof date === "number") date = new Date(date);
243
+ if (offset === void 0) offset = timezoneOffset;
244
+ return Math.floor((date.valueOf() / minute - offset) / 1440);
245
+ }
246
+ _Time.getDateNumber = getDateNumber;
247
+ function fromDateNumber(value, offset) {
248
+ const date = new Date(value * day);
249
+ if (offset === void 0) offset = timezoneOffset;
250
+ return new Date(+date + offset * minute);
251
+ }
252
+ _Time.fromDateNumber = fromDateNumber;
253
+ const numeric = /\d+(?:\.\d+)?/.source;
254
+ const timeRegExp = new RegExp(`^${[
255
+ "w(?:eek(?:s)?)?",
256
+ "d(?:ay(?:s)?)?",
257
+ "h(?:our(?:s)?)?",
258
+ "m(?:in(?:ute)?(?:s)?)?",
259
+ "s(?:ec(?:ond)?(?:s)?)?"
260
+ ].map((unit) => `(${numeric}${unit})?`).join("")}$`);
261
+ function parseTime(source) {
262
+ const capture = timeRegExp.exec(source);
263
+ if (!capture) return 0;
264
+ return (parseFloat(capture[1]) * week || 0) + (parseFloat(capture[2]) * day || 0) + (parseFloat(capture[3]) * hour || 0) + (parseFloat(capture[4]) * minute || 0) + (parseFloat(capture[5]) * second || 0);
265
+ }
266
+ _Time.parseTime = parseTime;
267
+ function parseDate(date) {
268
+ const parsed = parseTime(date);
269
+ if (parsed) date = Date.now() + parsed;
270
+ else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
271
+ else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
272
+ return date ? new Date(date) : /* @__PURE__ */ new Date();
273
+ }
274
+ _Time.parseDate = parseDate;
275
+ function format(ms) {
276
+ const abs = Math.abs(ms);
277
+ if (abs >= day - hour / 2) return Math.round(ms / day) + "d";
278
+ else if (abs >= hour - minute / 2) return Math.round(ms / hour) + "h";
279
+ else if (abs >= minute - second / 2) return Math.round(ms / minute) + "m";
280
+ else if (abs >= second) return Math.round(ms / second) + "s";
281
+ return ms + "ms";
282
+ }
283
+ _Time.format = format;
284
+ function toDigits(source, length = 2) {
285
+ return source.toString().padStart(length, "0");
286
+ }
287
+ _Time.toDigits = toDigits;
288
+ function template(template, time = /* @__PURE__ */ new Date()) {
289
+ return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
290
+ }
291
+ _Time.template = template;
292
+ })(Time || (Time = {}));
293
+ //#endregion
294
+ //#region ../../../vendor/schemastery/src/index.ts
295
+ const kSchema = Symbol.for("schemastery");
296
+ const kValidationError = Symbol.for("ValidationError");
297
+ globalThis.__schemastery_index__ ??= 0;
298
+ globalThis.__schemastery_refs__ = void 0;
299
+ var ValidationError = class extends TypeError {
300
+ options;
301
+ name = "ValidationError";
302
+ constructor(message, options) {
303
+ let prefix = "$";
304
+ for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
305
+ else if (typeof segment === "number") prefix += "[" + segment + "]";
306
+ else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
307
+ if (prefix.startsWith(".")) prefix = prefix.slice(1);
308
+ super((prefix === "$" ? "" : `${prefix} `) + message);
309
+ this.options = options;
310
+ }
311
+ static is(error) {
312
+ return !!error?.[kValidationError];
313
+ }
314
+ };
315
+ Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
316
+ const Schema = function(options) {
317
+ const schema = function(data, options = {}) {
318
+ return Schema.resolve(data, schema, options)[0];
319
+ };
320
+ if (options.refs) {
321
+ const refs = mapValues(options.refs, (options) => new Schema(options));
322
+ const getRef = (uid) => refs[uid];
323
+ for (const key in refs) {
324
+ const options = refs[key];
325
+ options.sKey = getRef(options.sKey);
326
+ options.inner = getRef(options.inner);
327
+ options.list = options.list && options.list.map(getRef);
328
+ options.dict = options.dict && mapValues(options.dict, getRef);
329
+ }
330
+ return refs[options.uid];
331
+ }
332
+ Object.assign(schema, options);
333
+ if (typeof schema.callback === "string") try {
334
+ schema.callback = new Function("return " + schema.callback)();
335
+ } catch {}
336
+ Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
337
+ Object.setPrototypeOf(schema, Schema.prototype);
338
+ schema.meta ||= {};
339
+ schema.toString = schema.toString.bind(schema);
340
+ return schema;
341
+ };
342
+ Schema.prototype = Object.create(Function.prototype);
343
+ Schema.prototype[kSchema] = true;
344
+ Object.defineProperty(Schema.prototype, "~standard", { get() {
345
+ return {
346
+ version: 1,
347
+ vendor: "schemastery",
348
+ validate: (value) => {
349
+ try {
350
+ return { value: Schema.resolve(value, this, {})[0] };
351
+ } catch (error) {
352
+ if (ValidationError.is(error)) return { issues: [{
353
+ message: error.message,
354
+ path: error.options.path
355
+ }] };
356
+ throw error;
357
+ }
358
+ }
359
+ };
360
+ } });
361
+ Schema.ValidationError = ValidationError;
362
+ Schema.prototype.toJSON = function toJSON() {
363
+ if (globalThis.__schemastery_refs__) {
364
+ globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
365
+ return this.uid;
366
+ }
367
+ globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
368
+ globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
369
+ const result = {
370
+ uid: this.uid,
371
+ refs: globalThis.__schemastery_refs__
372
+ };
373
+ globalThis.__schemastery_refs__ = void 0;
374
+ return result;
375
+ };
376
+ Schema.prototype.set = function set(key, value) {
377
+ this.dict[key] = value;
378
+ return this;
379
+ };
380
+ Schema.prototype.push = function push(value) {
381
+ this.list.push(value);
382
+ return this;
383
+ };
384
+ function mergeDesc(original, messages) {
385
+ const result = typeof original === "string" ? { "": original } : { ...original };
386
+ for (const locale in messages) {
387
+ const value = messages[locale];
388
+ if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
389
+ else if (typeof value === "string") result[locale] = value;
390
+ }
391
+ return result;
392
+ }
393
+ function getInner(value) {
394
+ return value?.$value ?? value?.$inner;
395
+ }
396
+ function extractKeys(data) {
397
+ return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
398
+ }
399
+ Schema.prototype.i18n = function i18n(messages) {
400
+ const schema = Schema(this);
401
+ const desc = mergeDesc(schema.meta.description, messages);
402
+ if (Object.keys(desc).length) schema.meta.description = desc;
403
+ if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
404
+ return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
405
+ });
406
+ if (schema.list) schema.list = schema.list.map((inner, index) => {
407
+ return inner.i18n(mapValues(messages, (data = {}) => {
408
+ if (Array.isArray(getInner(data))) return getInner(data)[index];
409
+ if (Array.isArray(data)) return data[index];
410
+ return extractKeys(data);
411
+ }));
412
+ });
413
+ if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
414
+ if (getInner(data)) return getInner(data);
415
+ return extractKeys(data);
416
+ }));
417
+ if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
418
+ return schema;
419
+ };
420
+ Schema.prototype.extra = function extra(key, value) {
421
+ const schema = Schema(this);
422
+ schema.meta = {
423
+ ...schema.meta,
424
+ [key]: value
425
+ };
426
+ return schema;
427
+ };
428
+ for (const key of [
429
+ "required",
430
+ "disabled",
431
+ "collapse",
432
+ "hidden",
433
+ "loose"
434
+ ]) Object.assign(Schema.prototype, { [key](value = true) {
435
+ const schema = Schema(this);
436
+ schema.meta = {
437
+ ...schema.meta,
438
+ [key]: value
439
+ };
440
+ return schema;
441
+ } });
442
+ Schema.prototype.deprecated = function deprecated() {
443
+ const schema = Schema(this);
444
+ schema.meta.badges ||= [];
445
+ schema.meta.badges.push({
446
+ text: "deprecated",
447
+ type: "danger"
448
+ });
449
+ return schema;
450
+ };
451
+ Schema.prototype.experimental = function experimental() {
452
+ const schema = Schema(this);
453
+ schema.meta.badges ||= [];
454
+ schema.meta.badges.push({
455
+ text: "experimental",
456
+ type: "warning"
457
+ });
458
+ return schema;
459
+ };
460
+ Schema.prototype.pattern = function pattern(regexp) {
461
+ const schema = Schema(this);
462
+ const pattern = pick(regexp, ["source", "flags"]);
463
+ schema.meta = {
464
+ ...schema.meta,
465
+ pattern
466
+ };
467
+ return schema;
468
+ };
469
+ Schema.prototype.simplify = function simplify(value) {
470
+ if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
471
+ if (isNullable(value)) return value;
472
+ if (this.type === "object" || this.type === "dict") {
473
+ const result = {};
474
+ for (const key in value) {
475
+ const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
476
+ if (this.type === "dict" || !isNullable(item)) result[key] = item;
477
+ }
478
+ if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
479
+ return result;
480
+ } else if (this.type === "array" || this.type === "tuple") {
481
+ const result = [];
482
+ value.forEach((value, index) => {
483
+ const schema = this.type === "array" ? this.inner : this.list[index];
484
+ const item = schema ? schema.simplify(value) : value;
485
+ result.push(item);
486
+ });
487
+ return result;
488
+ } else if (this.type === "intersect") {
489
+ const result = {};
490
+ for (const item of this.list) Object.assign(result, item.simplify(value));
491
+ return result;
492
+ } else if (this.type === "union") for (const schema of this.list) try {
493
+ Schema.resolve(value, schema, {});
494
+ return schema.simplify(value);
495
+ } catch {}
496
+ return value;
497
+ };
498
+ Schema.prototype.toString = function toString(inline) {
499
+ return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
500
+ };
501
+ Schema.prototype.role = function role(role, extra) {
502
+ const schema = Schema(this);
503
+ schema.meta = {
504
+ ...schema.meta,
505
+ role,
506
+ extra
507
+ };
508
+ return schema;
509
+ };
510
+ for (const key of [
511
+ "default",
512
+ "link",
513
+ "comment",
514
+ "description",
515
+ "max",
516
+ "min",
517
+ "step"
518
+ ]) Object.assign(Schema.prototype, { [key](value) {
519
+ const schema = Schema(this);
520
+ schema.meta = {
521
+ ...schema.meta,
522
+ [key]: value
523
+ };
524
+ return schema;
525
+ } });
526
+ const resolvers = {};
527
+ Schema.extend = function extend(type, resolve) {
528
+ resolvers[type] = resolve;
529
+ };
530
+ Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
531
+ if (!schema) return [data];
532
+ if (options.ignore?.(data, schema)) return [data];
533
+ if (isNullable(data) && schema.type !== "lazy") {
534
+ if (schema.meta.required) throw new ValidationError(`missing required value`, options);
535
+ let current = schema;
536
+ let fallback = schema.meta.default;
537
+ while (current?.type === "intersect" && isNullable(fallback)) {
538
+ current = current.list[0];
539
+ fallback = current?.meta.default;
540
+ }
541
+ if (isNullable(fallback)) return [data];
542
+ data = clone(fallback);
543
+ }
544
+ const callback = resolvers[schema.type];
545
+ if (!callback) throw new ValidationError(`unsupported type "${schema.type}"`, options);
546
+ try {
547
+ return callback(data, schema, options, strict);
548
+ } catch (error) {
549
+ if (!schema.meta.loose) throw error;
550
+ return [schema.meta.default];
551
+ }
552
+ };
553
+ Schema.from = function from(source) {
554
+ if (isNullable(source)) return Schema.any();
555
+ else if ([
556
+ "string",
557
+ "number",
558
+ "boolean"
559
+ ].includes(typeof source)) return Schema.const(source).required();
560
+ else if (source[kSchema]) return source;
561
+ else if (typeof source === "function") switch (source) {
562
+ case String: return Schema.string().required();
563
+ case Number: return Schema.number().required();
564
+ case Boolean: return Schema.boolean().required();
565
+ case Function: return Schema.function().required();
566
+ default: return Schema.is(source).required();
567
+ }
568
+ else throw new TypeError(`cannot infer schema from ${source}`);
569
+ };
570
+ Schema.lazy = function lazy(builder) {
571
+ const toJSON = () => {
572
+ if (!schema.inner[kSchema]) {
573
+ schema.inner = schema.builder();
574
+ schema.inner.meta = {
575
+ ...schema.meta,
576
+ ...schema.inner.meta
577
+ };
578
+ }
579
+ return schema.inner.toJSON();
580
+ };
581
+ const schema = new Schema({
582
+ type: "lazy",
583
+ builder,
584
+ inner: { toJSON }
585
+ });
586
+ return schema;
587
+ };
588
+ Schema.natural = function natural() {
589
+ return Schema.number().step(1).min(0);
590
+ };
591
+ Schema.percent = function percent() {
592
+ return Schema.number().step(.01).min(0).max(1).role("slider");
593
+ };
594
+ Schema.date = function date() {
595
+ return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
596
+ const date = new Date(value);
597
+ if (isNaN(+date)) throw new ValidationError(`invalid date "${value}"`, options);
598
+ return date;
599
+ }, true)]);
600
+ };
601
+ Schema.regExp = function regExp(flag = "") {
602
+ return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
603
+ try {
604
+ return new RegExp(value, flag);
605
+ } catch (e) {
606
+ throw new ValidationError(e.message, options);
607
+ }
608
+ }, true)]);
609
+ };
610
+ Schema.arrayBuffer = function arrayBuffer(encoding) {
611
+ return Schema.union([
612
+ Schema.is(ArrayBuffer),
613
+ Schema.is(SharedArrayBuffer),
614
+ Schema.transform(Schema.any(), (value, options) => {
615
+ if (Binary.isSource(value)) return Binary.fromSource(value);
616
+ throw new ValidationError(`expected ArrayBufferSource but got ${value}`, options);
617
+ }, true),
618
+ ...encoding ? [Schema.transform(Schema.string(), (value, options) => {
619
+ try {
620
+ return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
621
+ } catch (e) {
622
+ throw new ValidationError(e.message, options);
623
+ }
624
+ }, true)] : []
625
+ ]);
626
+ };
627
+ Schema.extend("lazy", (data, schema, options, strict) => {
628
+ if (!schema.inner[kSchema]) {
629
+ schema.inner = schema.builder();
630
+ schema.inner.meta = {
631
+ ...schema.meta,
632
+ ...schema.inner.meta
633
+ };
634
+ }
635
+ return Schema.resolve(data, schema.inner, options, strict);
636
+ });
637
+ Schema.extend("any", (data) => {
638
+ return [data];
639
+ });
640
+ Schema.extend("never", (data, _, options) => {
641
+ throw new ValidationError(`expected nullable but got ${data}`, options);
642
+ });
643
+ Schema.extend("const", (data, { value }, options) => {
644
+ if (deepEqual(data, value)) return [value];
645
+ throw new ValidationError(`expected ${value} but got ${data}`, options);
646
+ });
647
+ function checkWithinRange(data, meta, description, options, skipMin = false) {
648
+ const { max = Infinity, min = -Infinity } = meta;
649
+ if (data > max) throw new ValidationError(`expected ${description} <= ${max} but got ${data}`, options);
650
+ if (data < min && !skipMin) throw new ValidationError(`expected ${description} >= ${min} but got ${data}`, options);
651
+ }
652
+ Schema.extend("string", (data, { meta }, options) => {
653
+ if (typeof data !== "string") throw new ValidationError(`expected string but got ${data}`, options);
654
+ if (meta.pattern) {
655
+ const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
656
+ if (!regexp.test(data)) throw new ValidationError(`expect string to match regexp ${regexp}`, options);
657
+ }
658
+ checkWithinRange(data.length, meta, "string length", options);
659
+ return [data];
660
+ });
661
+ function decimalShift(data, digits) {
662
+ const str = data.toString();
663
+ if (str.includes("e")) return data * Math.pow(10, digits);
664
+ const index = str.indexOf(".");
665
+ if (index === -1) return data * Math.pow(10, digits);
666
+ const frac = str.slice(index + 1);
667
+ const integer = str.slice(0, index);
668
+ if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
669
+ return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
670
+ }
671
+ function isMultipleOf(data, min, step) {
672
+ step = Math.abs(step);
673
+ if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
674
+ const index = step.toString().indexOf(".");
675
+ const digits = step.toString().slice(index + 1).length;
676
+ return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
677
+ }
678
+ Schema.extend("number", (data, { meta }, options) => {
679
+ if (typeof data !== "number") throw new ValidationError(`expected number but got ${data}`, options);
680
+ checkWithinRange(data, meta, "number", options);
681
+ const { step } = meta;
682
+ if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError(`expected number multiple of ${step} but got ${data}`, options);
683
+ return [data];
684
+ });
685
+ Schema.extend("boolean", (data, _, options) => {
686
+ if (typeof data === "boolean") return [data];
687
+ throw new ValidationError(`expected boolean but got ${data}`, options);
688
+ });
689
+ Schema.extend("bitset", (data, { bits, meta }, options) => {
690
+ let value = 0, keys = [];
691
+ if (typeof data === "number") {
692
+ value = data;
693
+ for (const key in bits) if (data & bits[key]) keys.push(key);
694
+ } else if (Array.isArray(data)) {
695
+ keys = data;
696
+ for (const key of keys) {
697
+ if (typeof key !== "string") throw new ValidationError(`expected string but got ${key}`, options);
698
+ if (key in bits) value |= bits[key];
699
+ }
700
+ } else throw new ValidationError(`expected number or array but got ${data}`, options);
701
+ if (value === meta.default) return [value];
702
+ return [value, keys];
703
+ });
704
+ Schema.extend("function", (data, _, options) => {
705
+ if (typeof data === "function") return [data];
706
+ throw new ValidationError(`expected function but got ${data}`, options);
707
+ });
708
+ Schema.extend("is", (data, { constructor }, options) => {
709
+ if (typeof constructor === "function") {
710
+ if (data instanceof constructor) return [data];
711
+ throw new ValidationError(`expected ${constructor.name} but got ${data}`, options);
712
+ } else {
713
+ if (isNullable(data)) throw new ValidationError(`expected ${constructor} but got ${data}`, options);
714
+ let prototype = Object.getPrototypeOf(data);
715
+ while (prototype) {
716
+ if (prototype.constructor?.name === constructor) return [data];
717
+ prototype = Object.getPrototypeOf(prototype);
718
+ }
719
+ throw new ValidationError(`expected ${constructor} but got ${data}`, options);
720
+ }
721
+ });
722
+ function property(data, key, schema, options) {
723
+ try {
724
+ const [value, adapted] = Schema.resolve(data[key], schema, {
725
+ ...options,
726
+ path: [...options.path || [], key]
727
+ });
728
+ if (adapted !== void 0) data[key] = adapted;
729
+ return value;
730
+ } catch (e) {
731
+ if (!options?.autofix) throw e;
732
+ delete data[key];
733
+ return schema.meta.default;
734
+ }
735
+ }
736
+ Schema.extend("array", (data, { inner, meta }, options) => {
737
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
738
+ checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
739
+ return [data.map((_, index) => property(data, index, inner, options))];
740
+ });
741
+ Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
742
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
743
+ const result = {};
744
+ for (const key in data) {
745
+ let rKey;
746
+ try {
747
+ rKey = Schema.resolve(key, sKey, options)[0];
748
+ } catch (error) {
749
+ if (strict) continue;
750
+ throw error;
751
+ }
752
+ result[rKey] = property(data, key, inner, options);
753
+ data[rKey] = data[key];
754
+ if (key !== rKey) delete data[key];
755
+ }
756
+ return [result];
757
+ });
758
+ Schema.extend("tuple", (data, { list }, options, strict) => {
759
+ if (!Array.isArray(data)) throw new ValidationError(`expected array but got ${data}`, options);
760
+ const result = list.map((inner, index) => property(data, index, inner, options));
761
+ if (strict) return [result];
762
+ result.push(...data.slice(list.length));
763
+ return [result];
764
+ });
765
+ function merge(result, data) {
766
+ for (const key in data) {
767
+ if (key in result) continue;
768
+ result[key] = data[key];
769
+ }
770
+ }
771
+ Schema.extend("object", (data, { dict }, options, strict) => {
772
+ if (!isPlainObject(data)) throw new ValidationError(`expected object but got ${data}`, options);
773
+ const result = {};
774
+ for (const key in dict) {
775
+ const value = property(data, key, dict[key], options);
776
+ if (!isNullable(value) || key in data) result[key] = value;
777
+ }
778
+ if (!strict) merge(result, data);
779
+ return [result];
780
+ });
781
+ Schema.extend("union", (data, { list, toString }, options, strict) => {
782
+ const messages = [];
783
+ for (const inner of list) try {
784
+ return Schema.resolve(data, inner, options, strict);
785
+ } catch (error) {
786
+ messages.push(error);
787
+ }
788
+ throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
789
+ });
790
+ Schema.extend("intersect", (data, { list, toString }, options, strict) => {
791
+ if (!list.length) return [data];
792
+ let result;
793
+ for (const inner of list) {
794
+ const value = Schema.resolve(data, inner, options, true)[0];
795
+ if (isNullable(value)) continue;
796
+ if (isNullable(result)) result = value;
797
+ else if (typeof result !== typeof value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
798
+ else if (typeof value === "object") merge(result ??= {}, value);
799
+ else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
800
+ }
801
+ if (!strict && isPlainObject(data)) merge(result, data);
802
+ return [result];
803
+ });
804
+ Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
805
+ const [result, adapted = data] = Schema.resolve(data, inner, options, true);
806
+ if (preserve) return [callback(result)];
807
+ else return [callback(result), callback(adapted)];
808
+ });
809
+ const formatters = {};
810
+ function defineMethod(name, keys, format) {
811
+ formatters[name] = format;
812
+ Object.assign(Schema, { [name](...args) {
813
+ const schema = new Schema({ type: name });
814
+ keys.forEach((key, index) => {
815
+ switch (key) {
816
+ case "sKey":
817
+ schema.sKey = args[index] ?? Schema.string();
818
+ break;
819
+ case "inner":
820
+ schema.inner = Schema.from(args[index]);
821
+ break;
822
+ case "list":
823
+ schema.list = args[index].map(Schema.from);
824
+ break;
825
+ case "dict":
826
+ schema.dict = mapValues(args[index], Schema.from);
827
+ break;
828
+ case "bits":
829
+ schema.bits = {};
830
+ for (const key in args[index]) {
831
+ if (typeof args[index][key] !== "number") continue;
832
+ schema.bits[key] = args[index][key];
833
+ }
834
+ break;
835
+ case "callback": {
836
+ const callback = schema.callback = args[index];
837
+ callback["toJSON"] ||= () => callback.toString();
838
+ break;
839
+ }
840
+ case "constructor": {
841
+ const constructor = schema.constructor = args[index];
842
+ if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
843
+ break;
844
+ }
845
+ default: schema[key] = args[index];
846
+ }
847
+ });
848
+ if (name === "object" || name === "dict") schema.meta.default = {};
849
+ else if (name === "array" || name === "tuple") schema.meta.default = [];
850
+ else if (name === "bitset") schema.meta.default = 0;
851
+ return schema;
852
+ } });
853
+ }
854
+ defineMethod("is", ["constructor"], ({ constructor }) => {
855
+ if (typeof constructor === "function") return constructor.name;
856
+ else return constructor;
857
+ });
858
+ defineMethod("any", [], () => "any");
859
+ defineMethod("never", [], () => "never");
860
+ defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
861
+ defineMethod("string", [], () => "string");
862
+ defineMethod("number", [], () => "number");
863
+ defineMethod("boolean", [], () => "boolean");
864
+ defineMethod("bitset", ["bits"], () => "bitset");
865
+ defineMethod("function", [], () => "function");
866
+ defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
867
+ defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
868
+ defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
869
+ defineMethod("object", ["dict"], ({ dict }) => {
870
+ if (Object.keys(dict).length === 0) return "{}";
871
+ return `{ ${Object.entries(dict).map(([key, inner]) => {
872
+ return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
873
+ }).join(", ")} }`;
874
+ });
875
+ defineMethod("union", ["list"], ({ list }, inline) => {
876
+ const result = list.map(({ toString: format }) => format()).join(" | ");
877
+ return inline ? `(${result})` : result;
878
+ });
879
+ defineMethod("intersect", ["list"], ({ list }) => {
880
+ return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
881
+ });
882
+ defineMethod("transform", [
883
+ "inner",
884
+ "callback",
885
+ "preserve"
886
+ ], ({ inner }, isInner) => inner.toString(isInner));
887
+ //#endregion
888
+ //#region ../../util/timeout/src/index.ts
889
+ /** Largest delay Node schedules without clamping it to one millisecond. */
890
+ const MAX_TIMER_DELAY_MS = 2147483647;
891
+ //#endregion
892
+ //#region ../../llm/llm/src/error.ts
893
+ /**
894
+ * Canonical provider-neutral code for a response that completed normally but
895
+ * carried no content blocks at all. Providers occasionally emit a degenerate
896
+ * completion (a terminal stop with zero output); adapters classify it as this
897
+ * failure instead of yielding an empty assistant message, because an empty
898
+ * message silently ends the turn with nothing for the user or the loop to act
899
+ * on. The attempt produced nothing durable, so retry policy treats it as safe
900
+ * to repeat.
901
+ */
902
+ const EMPTY_RESPONSE_CODE = "EMPTY_RESPONSE";
903
+ new RegExp(String.raw`(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`, "i");
904
+ new RegExp(String.raw`\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?` + String.raw`too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?` + String.raw`(?:model(?:'s)?\s+)?context(?:\s+window)?\b`, "i");
905
+ new RegExp(String.raw`\b(?:input|prompt|request|messages?)\b.{0,40}` + String.raw`\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}` + String.raw`\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`, "i");
906
+ //#endregion
907
+ //#region ../../llm/llm/src/retry-policy.ts
908
+ /**
909
+ * Provider-owned request-retry policy configuration and resolution.
910
+ *
911
+ * Adapters expose one resolved policy per registered provider route; the
912
+ * optional dsh-llm-retry plugin executes it on the agent's failed-step extension point.
913
+ *
914
+ * @module @deepseek-ai/dsh-llm/retry-policy
915
+ */
916
+ const DEFAULT_MAX_RETRIES = 5;
917
+ const DEFAULT_INITIAL_DELAY_MS = 500;
918
+ const DEFAULT_MAX_DELAY_MS = 1e4;
919
+ const DEFAULT_JITTER_RATIO = .1;
920
+ const DEFAULT_RETRYABLE_CODES = Object.freeze([
921
+ EMPTY_RESPONSE_CODE,
922
+ "RATE_LIMIT",
923
+ "SERVER",
924
+ "TIMEOUT",
925
+ "TRANSPORT"
926
+ ]);
927
+ const backoffSchema = Schema.object({
928
+ initialDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
929
+ maxDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
930
+ jitterRatio: Schema.number().min(0).max(1).default(DEFAULT_JITTER_RATIO)
931
+ });
932
+ const normalPolicySchema = Schema.object({
933
+ mode: Schema.const("normal").required(),
934
+ maxRetries: Schema.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
935
+ retryableCodes: Schema.array(Schema.string()).default([...DEFAULT_RETRYABLE_CODES]),
936
+ backoff: backoffSchema
937
+ });
938
+ const alwaysPolicySchema = Schema.object({
939
+ mode: Schema.const("always").required(),
940
+ backoff: backoffSchema
941
+ });
942
+ Schema.union([normalPolicySchema, alwaysPolicySchema]);
943
+ //#endregion
944
+ //#region ../../llm/llm/src/attribution.ts
945
+ /**
946
+ * Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
947
+ * adapters from drifting. See
948
+ * `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
949
+ *
950
+ * App-attribution vocabulary for provider requests.
951
+ * @module @deepseek-ai/dsh-llm/attribution
952
+ */
953
+ const { version } = createRequire(import.meta.url)("../package.json");
954
+ //#endregion
955
+ //#region ../../llm/llm/src/never.ts
956
+ /**
957
+ * Exhaustiveness helper for closed core unions. Use {@link assertNever} at the default branch so a
958
+ * new variant fails compilation at every required handler. Do not use it for declaration-merged
959
+ * unions such as session events or content blocks: handle known variants and explicitly fall
960
+ * through because plugins may add valid unknown cases.
961
+ * @module @deepseek-ai/dsh-llm/never
962
+ */
963
+ /**
964
+ * Mark an unreachable closed-union branch. A newly unhandled typed variant fails at the call site;
965
+ * a value that escaped its type throws with diagnostics at runtime.
966
+ * @param value - the impossible value; typed `never` so an unhandled variant fails compilation at the call site.
967
+ * @param context - optional label (e.g. the switch site) prefixed into the throw message.
968
+ * @returns never — it always throws, with the offending value JSON-rendered in the message.
969
+ */
970
+ function assertNever(value, context) {
971
+ const rendered = JSON.stringify(value) ?? String(value);
972
+ throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
973
+ }
974
+ //#endregion
975
+ //#region ../../core/scope/src/store.ts
976
+ /**
977
+ * Insertion-ordered named entries with caller-owned duplicate diagnostics.
978
+ *
979
+ * Values are borrowed. Iterators are live within one nonempty table
980
+ * generation; draining the table detaches them from later insertions. Each
981
+ * successful insertion returns an idempotent undo for that exact entry.
982
+ */
983
+ var NamedEntries = class {
984
+ duplicateError;
985
+ data = /* @__PURE__ */ new Map();
986
+ constructor(duplicateError) {
987
+ this.duplicateError = duplicateError;
988
+ }
989
+ /**
990
+ * Insert one unique name.
991
+ * @param name - name unique within this table.
992
+ * @param value - borrowed value to retain.
993
+ * @returns an idempotent undo that removes only this insertion.
994
+ */
995
+ insert(name, value) {
996
+ const data = this.data;
997
+ if (data.has(name)) throw this.duplicateError(name);
998
+ data.set(name, value);
999
+ let active = true;
1000
+ return () => {
1001
+ if (!active) return;
1002
+ active = false;
1003
+ data.delete(name);
1004
+ if (data.size === 0 && this.data === data) this.data = /* @__PURE__ */ new Map();
1005
+ };
1006
+ }
1007
+ /**
1008
+ * Read one named value.
1009
+ * @param name - name to resolve.
1010
+ * @returns the retained value, or `undefined` when absent.
1011
+ */
1012
+ get(name) {
1013
+ return this.data.get(name);
1014
+ }
1015
+ /**
1016
+ * Test one name for membership.
1017
+ * @param name - name to test.
1018
+ * @returns whether the table contains that name.
1019
+ */
1020
+ has(name) {
1021
+ return this.data.has(name);
1022
+ }
1023
+ /**
1024
+ * Iterate live names in insertion order.
1025
+ * @returns the native live key iterator.
1026
+ */
1027
+ keys() {
1028
+ return this.data.keys();
1029
+ }
1030
+ /**
1031
+ * Iterate live entries in insertion order.
1032
+ * @returns the native live entry iterator.
1033
+ */
1034
+ entries() {
1035
+ return this.data.entries();
1036
+ }
1037
+ /**
1038
+ * Iterate live values in insertion order.
1039
+ * @returns the native live value iterator.
1040
+ */
1041
+ values() {
1042
+ return this.data.values();
1043
+ }
1044
+ /**
1045
+ * Test whether this table has no entries.
1046
+ * @returns whether the table is empty.
1047
+ */
1048
+ isEmpty() {
1049
+ return this.data.size === 0;
1050
+ }
1051
+ };
1052
+ /**
1053
+ * Own the global and exact-scope layers for one registry.
1054
+ *
1055
+ * Reads never create scoped layers. Registrations derive both visibility and
1056
+ * effect ownership from the supplied Cordis context, collect undo before
1057
+ * notification, and reclaim only a completely empty aggregate layer.
1058
+ */
1059
+ var ScopedLayers = class {
1060
+ createLayer;
1061
+ onChange;
1062
+ /** The eagerly constructed context-global layer. */
1063
+ global;
1064
+ scoped = /* @__PURE__ */ new Map();
1065
+ constructor(createLayer, onChange) {
1066
+ this.createLayer = createLayer;
1067
+ this.onChange = onChange;
1068
+ this.global = createLayer(void 0);
1069
+ }
1070
+ /**
1071
+ * Read an existing exact-scope overlay. Deliberately chain-blind: callers
1072
+ * addressing one scope's OWN contributions (its restrictions, its guards)
1073
+ * must not silently pick up an ancestor's — use {@link chainLayers} where
1074
+ * inheritance is the point.
1075
+ * @param scope - exact scope key; `undefined` denotes no overlay.
1076
+ * @returns the existing scoped layer, or `undefined` without creating one.
1077
+ */
1078
+ peek(scope) {
1079
+ if (scope === void 0) return void 0;
1080
+ return this.scoped.get(scope);
1081
+ }
1082
+ /**
1083
+ * Existing overlays along the scope's parent chain ({@link scopeChainOf}),
1084
+ * farthest ancestor first and the exact scope last, so a caller layering
1085
+ * them in order gives the nearest scope the final word.
1086
+ * @param scope - viewing scope, or `undefined` for no overlays.
1087
+ * @returns the existing layers, nearest last; absent overlays are skipped.
1088
+ */
1089
+ chainLayers(scope) {
1090
+ const layers = [];
1091
+ for (const key of scopeChainOf(scope).reverse()) {
1092
+ const layer = this.scoped.get(key);
1093
+ if (layer !== void 0) layers.push(layer);
1094
+ }
1095
+ return layers;
1096
+ }
1097
+ /**
1098
+ * Materialize global named entries followed by scope-chain shadows,
1099
+ * farthest ancestor first, so the nearest scope's entry wins a name.
1100
+ * @param scope - viewing scope, or `undefined` for the global view.
1101
+ * @param pick - select the named table from a layer.
1102
+ * @returns an insertion-ordered effective map.
1103
+ */
1104
+ merge(scope, pick) {
1105
+ const merged = new Map(pick(this.global).entries());
1106
+ for (const layer of this.chainLayers(scope)) for (const [name, value] of pick(layer).entries()) merged.set(name, value);
1107
+ return merged;
1108
+ }
1109
+ /**
1110
+ * Attach one synchronous layer mutation to its registration context.
1111
+ * @param ctx - context that determines both scope visibility and effect ownership.
1112
+ * @param action - atomic mutation returning its synchronous undo.
1113
+ * @param options - Cordis effect label and optional change notification.
1114
+ * @returns the exact disposer returned by `ctx.effect()`.
1115
+ */
1116
+ effect(ctx, action, options) {
1117
+ const scope = scopeOf(ctx);
1118
+ const notify = options.notify ?? true;
1119
+ return ctx.effect(function* () {
1120
+ let layer;
1121
+ let created = false;
1122
+ if (scope === void 0) layer = this.global;
1123
+ else {
1124
+ const existing = this.scoped.get(scope);
1125
+ if (existing === void 0) {
1126
+ layer = this.createLayer(scope);
1127
+ this.scoped.set(scope, layer);
1128
+ created = true;
1129
+ } else layer = existing;
1130
+ }
1131
+ let undo;
1132
+ try {
1133
+ undo = action(layer);
1134
+ } catch (error) {
1135
+ if (scope !== void 0 && created && layer.isEmpty()) this.scoped.delete(scope);
1136
+ throw error;
1137
+ }
1138
+ yield () => {
1139
+ undo();
1140
+ if (scope !== void 0 && layer.isEmpty()) this.scoped.delete(scope);
1141
+ if (notify) this.onChange();
1142
+ };
1143
+ if (notify) this.onChange();
1144
+ }.bind(this), options.label);
1145
+ }
1146
+ };
1147
+ //#endregion
1148
+ //#region ../../core/scope/src/index.ts
1149
+ /** Context tag written by {@link createScope}. */
1150
+ const kScope = Symbol("dsh.scope");
1151
+ /**
1152
+ * The enclosing scope of each key. One relation powers both directions of
1153
+ * scope nesting: registration views inherit DOWN the chain (a child scope
1154
+ * sees its ancestors' layers — {@link ScopedLayers}), and event admission
1155
+ * extends UP it (a listener tagged with an ancestor receives events dispatched
1156
+ * to a descendant key — {@link scopeTarget}).
1157
+ */
1158
+ const scopeParents = /* @__PURE__ */ new WeakMap();
1159
+ /**
1160
+ * The chain from a key to its root ancestor.
1161
+ * @param key - the starting key, or `undefined` for the empty chain.
1162
+ * @returns keys nearest-first: `[key, parent, grandparent, …]`.
1163
+ */
1164
+ function scopeChainOf(key) {
1165
+ const chain = [];
1166
+ for (let cursor = key; cursor !== void 0; cursor = scopeParents.get(cursor)) chain.push(cursor);
1167
+ return chain;
1168
+ }
1169
+ /**
1170
+ * Read the nearest scope tag inherited by a context.
1171
+ * @param ctx - context to inspect.
1172
+ * @returns its scope key, or `undefined` for an unscoped context.
1173
+ */
1174
+ function scopeOf(ctx) {
1175
+ return ctx[kScope];
1176
+ }
1177
+ //#endregion
1178
+ //#region ../../skill/skill/src/index.ts
1179
+ /**
1180
+ * Agent skill provider registry.
1181
+ *
1182
+ * This package owns the Service Definition role of the skill capability seam.
1183
+ * Concrete
1184
+ * providers such as `@deepseek-ai/dsh-skill-filesystem` decide where skills come
1185
+ * from; this service only merges provider catalogs, resolves the winning skill
1186
+ * for a name, and exposes the winning summaries and definitions to consumers.
1187
+ *
1188
+ * @module @deepseek-ai/dsh-skill
1189
+ */
1190
+ const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1191
+ const DEFAULT_COLLECT_CACHE_ENTRIES = 128;
1192
+ const MAX_COLLECT_ATTEMPTS = 2;
1193
+ const RUNTIME_PROVIDER = "runtime";
1194
+ const RUNTIME_RANK = 250;
1195
+ /**
1196
+ * Return whether a string is a valid kebab-case skill name.
1197
+ * @param name - candidate skill name to validate.
1198
+ * @returns whether the name matches the public skill-name grammar.
1199
+ */
1200
+ function isSkillName(name) {
1201
+ return SKILL_NAME.test(name);
1202
+ }
1203
+ /**
1204
+ * Render one loaded skill for the model. The output is shared verbatim by the
1205
+ * `skill` tool result and the user-explicit invocation injection, so the model
1206
+ * sees one canonical `<skill_content>` shape on both paths. The name rides an
1207
+ * escaped attribute; the body is embedded verbatim (skills are trusted local
1208
+ * content, and user-supplied invocation text stays outside this wrapper).
1209
+ * @param skill - name, provider, optional resource base, and body to render.
1210
+ * @returns the complete model-facing `<skill_content>` block.
1211
+ */
1212
+ function renderSkillContent(skill) {
1213
+ const resourceHint = renderResourceHint(skill);
1214
+ return [
1215
+ `<skill_content name="${escapeAttr(skill.name)}">`,
1216
+ "<skill_resources>",
1217
+ ...resourceHint,
1218
+ "</skill_resources>",
1219
+ "",
1220
+ "<skill_instructions>",
1221
+ skill.content,
1222
+ "</skill_instructions>",
1223
+ "</skill_content>"
1224
+ ].join("\n");
1225
+ }
1226
+ function renderResourceHint(skill) {
1227
+ const base = skill.resourceBase;
1228
+ if (base === void 0) return [`Resources for this skill are managed by provider "${escapeText(skill.provider)}".`, "Load referenced resources only as needed."];
1229
+ switch (base.kind) {
1230
+ case "directory": return [`Base directory for this skill: ${escapeText(base.path)}`, "Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed."];
1231
+ case "url": return [`Base URL for this skill: ${escapeText(base.url)}`, "Resolve relative URLs mentioned by this skill against the base URL before using them. Load referenced resources only as needed."];
1232
+ case "opaque": return [`Resources for this skill: ${escapeText(base.description)}`, "Load referenced resources only as needed."];
1233
+ /* v8 ignore start -- SkillResourceBase is a closed union; a future kind must fail compilation here. */
1234
+ default: return assertNever(base, "SkillResourceBase.kind");
1235
+ }
1236
+ }
1237
+ function escapeAttr(value) {
1238
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
1239
+ }
1240
+ /**
1241
+ * Escape model-facing prose embedded inside skill markup so provider-supplied
1242
+ * text cannot open or close framing tags.
1243
+ * @param value - raw prose to embed.
1244
+ * @returns the escaped text.
1245
+ */
1246
+ function escapeText(value) {
1247
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1248
+ }
1249
+ /** One scope's complete skill-registry contribution. */
1250
+ var SkillLayer = class {
1251
+ /** Providers registered through contexts carrying this scope, insertion-ordered. */
1252
+ providers;
1253
+ /** Runtime skills registered through contexts carrying this scope. */
1254
+ runtime = /* @__PURE__ */ new Map();
1255
+ constructor(scope) {
1256
+ this.providers = new NamedEntries((name) => /* @__PURE__ */ new Error(scope === void 0 ? `a skill provider named "${name}" is already registered` : `a skill provider named "${name}" is already registered in this scope`));
1257
+ }
1258
+ /** Whether every contribution table in this aggregate layer is empty. */
1259
+ isEmpty() {
1260
+ return this.providers.isEmpty() && this.runtime.size === 0;
1261
+ }
1262
+ };
1263
+ (class extends Service {
1264
+ static Config = Schema.object({ collectCacheMaxEntries: Schema.number().default(DEFAULT_COLLECT_CACHE_ENTRIES) });
1265
+ collectCacheMaxEntries;
1266
+ layers = new ScopedLayers((scope) => new SkillLayer(scope), () => {
1267
+ this.invalidateCache();
1268
+ });
1269
+ collectCache = /* @__PURE__ */ new Map();
1270
+ revision = 0;
1271
+ nextProviderOrder = 0;
1272
+ /** Stable identities for cache keys; scope keys are opaque identity-compared objects. */
1273
+ scopeIds = /* @__PURE__ */ new WeakMap();
1274
+ nextScopeId = 1;
1275
+ constructor(ctx, config = {}) {
1276
+ super(ctx, "skills");
1277
+ this.collectCacheMaxEntries = config.collectCacheMaxEntries ?? DEFAULT_COLLECT_CACHE_ENTRIES;
1278
+ assertPositiveInteger("collectCacheMaxEntries", this.collectCacheMaxEntries);
1279
+ }
1280
+ /**
1281
+ * Register a borrowed same-process provider synchronously during plugin
1282
+ * apply, into the calling context's layer: a scoped context (an agent
1283
+ * preset's standing mount) registers for that scope alone, an unscoped
1284
+ * context registers globally. Duplicate names within one layer and reserved
1285
+ * names throw; remote initialization belongs in `list()`. Fiber disposal
1286
+ * unregisters the provider and invalidates catalog caches.
1287
+ * @param create - synchronous factory receiving this registration's lifecycle and invalidation control.
1288
+ * @returns the exact Cordis effect disposer that unregisters this provider;
1289
+ * composite effects may yield it directly to preserve teardown ordering.
1290
+ */
1291
+ registerProvider(create) {
1292
+ const lifecycle = new AbortController();
1293
+ let registration;
1294
+ let provider;
1295
+ const control = {
1296
+ signal: lifecycle.signal,
1297
+ invalidate: () => {
1298
+ const active = registration;
1299
+ if (active !== void 0 && active.layer.providers.get(active.name)?.provider === provider) this.invalidateCache();
1300
+ }
1301
+ };
1302
+ try {
1303
+ provider = create(control);
1304
+ const name = provider.name;
1305
+ if (name === RUNTIME_PROVIDER) throw new Error(`"${RUNTIME_PROVIDER}" is reserved for runtime skill registrations`);
1306
+ const order = this.nextProviderOrder;
1307
+ this.nextProviderOrder += 1;
1308
+ return this.layers.effect(this.ctx, (layer) => {
1309
+ const undo = layer.providers.insert(name, {
1310
+ provider,
1311
+ order
1312
+ });
1313
+ registration = {
1314
+ layer,
1315
+ name
1316
+ };
1317
+ return () => {
1318
+ registration = void 0;
1319
+ undo();
1320
+ lifecycle.abort(/* @__PURE__ */ new Error(`skill provider "${name}" disposed`));
1321
+ };
1322
+ }, { label: "skills.registerProvider()" });
1323
+ } catch (error) {
1324
+ lifecycle.abort(error);
1325
+ throw error;
1326
+ }
1327
+ }
1328
+ /**
1329
+ * Register a borrowed readonly runtime skill into the calling context's
1330
+ * layer. Project entries outrank runtime entries, which outrank user
1331
+ * entries, within one layer. Same-name runtime entries in one layer are
1332
+ * first-wins; a duplicate logs a warning and receives a no-op disposer so
1333
+ * it cannot remove the winner.
1334
+ * @param skill - the skill definition input; omitted invocation and provider fields receive defaults.
1335
+ * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches.
1336
+ */
1337
+ register(skill) {
1338
+ validateRuntimeSkill(skill);
1339
+ const scope = scopeOf(this.ctx);
1340
+ const existingLayer = scope === void 0 ? this.layers.global : this.layers.peek(scope);
1341
+ if (existingLayer !== void 0 && existingLayer.runtime.has(skill.name)) {
1342
+ this.ctx.logger.warn(`runtime skill "${skill.name}" ignored because it is already registered`);
1343
+ return () => {};
1344
+ }
1345
+ const definition = {
1346
+ ...skill,
1347
+ invocation: skill.invocation ?? {
1348
+ modelInvocable: true,
1349
+ userInvocable: true
1350
+ },
1351
+ provider: skill.provider ?? RUNTIME_PROVIDER
1352
+ };
1353
+ return this.layers.effect(this.ctx, (layer) => {
1354
+ layer.runtime.set(definition.name, definition);
1355
+ return () => {
1356
+ layer.runtime.delete(definition.name);
1357
+ };
1358
+ }, { label: "skills.register()" });
1359
+ }
1360
+ /**
1361
+ * List invocation-neutral skill summaries for a workspace. Consumers apply
1362
+ * model or user invocation policy at their operational boundary. Lookup
1363
+ * options and provider candidates are readonly same-process values borrowed
1364
+ * throughout discovery.
1365
+ * @param options - view options; `scope` selects the viewing agent's layers, `cwd` selects project roots, and `signal` cancels discovery.
1366
+ * @returns all sorted winning summaries.
1367
+ */
1368
+ async list(options = {}) {
1369
+ return (await this.snapshot(options)).skills;
1370
+ }
1371
+ /**
1372
+ * Observe the current invocation-neutral catalog and whether discovery completed within a stable revision.
1373
+ * Incomplete observations are never cached, allowing consumers to retain last-good state and
1374
+ * retry on their next request boundary.
1375
+ * @param options - view options; `scope` selects the viewing agent's layers, `cwd` selects project roots, and `signal` cancels discovery.
1376
+ * @returns sorted summaries plus discovery-completeness state.
1377
+ */
1378
+ async snapshot(options = {}) {
1379
+ const collected = await this.collect(options);
1380
+ return {
1381
+ skills: [...collected.entries.values()].map((entry) => toSummary(entry.candidate)).sort(compareSkillSummary),
1382
+ complete: collected.cacheable
1383
+ };
1384
+ }
1385
+ /**
1386
+ * Load and validate the winning candidate, passing its opaque discovery locator back to the
1387
+ * provider. Cancellation is rechecked after selection, including cache hits, and raced against
1388
+ * loading so an uncooperative provider cannot hang the caller.
1389
+ * @param name - kebab-case skill name.
1390
+ * @param options - view options; `scope` selects the viewing agent's layers,
1391
+ * `cwd` selects workspace-sensitive skills, and `signal` cancels work.
1392
+ * @returns the full skill, including body content, or `undefined`.
1393
+ */
1394
+ async get(name, options = {}) {
1395
+ if (!isSkillName(name)) return void 0;
1396
+ const collected = await this.collect(options);
1397
+ throwIfAborted(options.signal);
1398
+ const match = collected.entries.get(name);
1399
+ if (match === void 0) return void 0;
1400
+ const definition = await waitWithAbort(match.provider.get(match.candidate, options), options.signal);
1401
+ if (definition === void 0) return void 0;
1402
+ validateDefinition(definition);
1403
+ if (definition.name !== match.candidate.name) {
1404
+ this.invalidateEntry(match);
1405
+ return;
1406
+ }
1407
+ return definition;
1408
+ }
1409
+ async collect(options) {
1410
+ throwIfAborted(options.signal);
1411
+ let attempt = 1;
1412
+ while (true) {
1413
+ const revision = this.revision;
1414
+ const key = this.collectCacheKey(options.cwd, scopeChainOf(options.scope), revision);
1415
+ const cached = this.collectCache.get(key);
1416
+ if (cached !== void 0) return {
1417
+ entries: cached,
1418
+ cacheable: true
1419
+ };
1420
+ const result = await this.collectFresh(options);
1421
+ throwIfAborted(options.signal);
1422
+ if (revision !== this.revision) {
1423
+ if (attempt < MAX_COLLECT_ATTEMPTS) {
1424
+ attempt += 1;
1425
+ continue;
1426
+ }
1427
+ return {
1428
+ entries: result.entries,
1429
+ cacheable: false
1430
+ };
1431
+ }
1432
+ if (result.cacheable) {
1433
+ this.collectCache.set(key, result.entries);
1434
+ if (this.collectCache.size > this.collectCacheMaxEntries) {
1435
+ const oldest = this.collectCache.keys().next();
1436
+ this.collectCache.delete(oldest.value);
1437
+ }
1438
+ }
1439
+ return result;
1440
+ }
1441
+ }
1442
+ async collectFresh(options) {
1443
+ const layers = [this.layers.global, ...this.layers.chainLayers(options.scope)];
1444
+ const merged = /* @__PURE__ */ new Map();
1445
+ let cacheable = true;
1446
+ for (const layer of layers) {
1447
+ const collected = await this.collectLayer(layer, options);
1448
+ if (!collected.cacheable) cacheable = false;
1449
+ for (const entry of collected.entries) merged.set(entry.candidate.name, entry);
1450
+ }
1451
+ return {
1452
+ entries: merged,
1453
+ cacheable
1454
+ };
1455
+ }
1456
+ async collectLayer(layer, options) {
1457
+ const collected = await this.listLayerCandidates(layer, options);
1458
+ collected.entries.sort(compareIndexedCandidates);
1459
+ const seen = /* @__PURE__ */ new Set();
1460
+ const result = [];
1461
+ for (const entry of collected.entries) {
1462
+ const skill = entry.candidate;
1463
+ if (seen.has(skill.name)) {
1464
+ this.ctx.logger.warn(`skill "${skill.name}" from ${skill.source} ignored because a higher-priority skill already exists`);
1465
+ continue;
1466
+ }
1467
+ seen.add(skill.name);
1468
+ result.push(entry);
1469
+ }
1470
+ return {
1471
+ entries: result,
1472
+ cacheable: collected.cacheable
1473
+ };
1474
+ }
1475
+ async listLayerCandidates(layer, options) {
1476
+ throwIfAborted(options.signal);
1477
+ const candidates = [];
1478
+ let cacheable = true;
1479
+ let runtimeOrder = 0;
1480
+ for (const skill of [...layer.runtime.values()].sort((a, b) => compareCodePoints(a.name, b.name))) {
1481
+ candidates.push({
1482
+ candidate: runtimeCandidate(skill),
1483
+ provider: RUNTIME_SKILL_PROVIDER,
1484
+ providerOrder: -1,
1485
+ localOrder: runtimeOrder,
1486
+ layer
1487
+ });
1488
+ runtimeOrder += 1;
1489
+ }
1490
+ for (const { provider, order } of [...layer.providers.values()]) {
1491
+ let localOrder = 0;
1492
+ let output;
1493
+ try {
1494
+ output = await waitWithAbort(provider.list(options), options.signal);
1495
+ } catch (error) {
1496
+ if (options.signal?.aborted === true) throw toError(options.signal.reason);
1497
+ cacheable = false;
1498
+ this.ctx.logger.warn(`skill provider "${provider.name}" skipped: ${errorMessage(error)}`);
1499
+ }
1500
+ if (output === void 0) continue;
1501
+ const observation = normalizeProviderObservation(output, provider.name);
1502
+ if (!observation.complete) cacheable = false;
1503
+ for (const candidate of observation.candidates) {
1504
+ validateCandidate(candidate, provider.name);
1505
+ candidates.push({
1506
+ candidate,
1507
+ provider,
1508
+ providerOrder: order,
1509
+ localOrder,
1510
+ layer
1511
+ });
1512
+ localOrder += 1;
1513
+ }
1514
+ }
1515
+ return {
1516
+ entries: candidates,
1517
+ cacheable
1518
+ };
1519
+ }
1520
+ invalidateCache() {
1521
+ this.revision += 1;
1522
+ this.collectCache.clear();
1523
+ this.notifyChange();
1524
+ }
1525
+ /** Invalidate after a stale definition load, only while the exact registration that produced the entry is still live. */
1526
+ invalidateEntry(entry) {
1527
+ /* v8 ignore else -- A definition load can outlive the exact provider registration it selected. */
1528
+ if (entry.layer.providers.get(entry.provider.name)?.provider === entry.provider) this.invalidateCache();
1529
+ }
1530
+ scopeId(key) {
1531
+ let id = this.scopeIds.get(key);
1532
+ if (id === void 0) {
1533
+ id = this.nextScopeId;
1534
+ this.nextScopeId += 1;
1535
+ this.scopeIds.set(key, id);
1536
+ }
1537
+ return id;
1538
+ }
1539
+ collectCacheKey(cwd, chain, revision) {
1540
+ return JSON.stringify({
1541
+ cwd,
1542
+ scopes: chain.map((key) => this.scopeId(key)),
1543
+ revision
1544
+ });
1545
+ }
1546
+ /** Notify catalog observers without making their refresh work load-bearing. */
1547
+ notifyChange() {
1548
+ for (const callback of this.ctx.events.dispatch("emit", ["skills/change"])) try {
1549
+ const returned = callback();
1550
+ Promise.resolve(returned).catch((error) => {
1551
+ this.ctx.logger.warn(`skills/change listener rejected: ${errorMessage(error)}`);
1552
+ });
1553
+ } catch (error) {
1554
+ this.ctx.logger.warn(`skills/change listener threw: ${errorMessage(error)}`);
1555
+ }
1556
+ }
1557
+ });
1558
+ function normalizeProviderObservation(output, providerName) {
1559
+ if (Array.isArray(output)) return {
1560
+ candidates: output,
1561
+ complete: true
1562
+ };
1563
+ if (output === null || typeof output !== "object") throw invalidProviderObservation(providerName);
1564
+ const observation = output;
1565
+ if (!Array.isArray(observation.candidates) || typeof observation.complete !== "boolean") throw invalidProviderObservation(providerName);
1566
+ return observation;
1567
+ }
1568
+ function invalidProviderObservation(providerName) {
1569
+ return /* @__PURE__ */ new TypeError(`skill provider "${providerName}" list() must return an array or { candidates, complete } observation`);
1570
+ }
1571
+ const RUNTIME_SKILL_PROVIDER = {
1572
+ name: RUNTIME_PROVIDER,
1573
+ /* v8 ignore next -- Runtime skills are injected directly by the registry; this provider only owns `get()`. */
1574
+ list() {
1575
+ return Promise.resolve([]);
1576
+ },
1577
+ get(candidate) {
1578
+ return Promise.resolve(candidate.locator);
1579
+ }
1580
+ };
1581
+ function runtimeCandidate(skill) {
1582
+ return {
1583
+ name: skill.name,
1584
+ description: skill.description,
1585
+ ...skill.whenToUse !== void 0 ? { whenToUse: skill.whenToUse } : {},
1586
+ invocation: skill.invocation,
1587
+ source: skill.source,
1588
+ provider: skill.provider,
1589
+ ...skill.resourceBase !== void 0 ? { resourceBase: skill.resourceBase } : {},
1590
+ rank: RUNTIME_RANK,
1591
+ locator: skill,
1592
+ ...skill.path !== void 0 ? { path: skill.path } : {},
1593
+ ...skill.metadata !== void 0 ? { metadata: skill.metadata } : {}
1594
+ };
1595
+ }
1596
+ function validateCandidate(candidate, providerName) {
1597
+ if (typeof candidate.name !== "string") throw new TypeError(`skill provider "${providerName}" returned a non-string skill name`);
1598
+ if (!SKILL_NAME.test(candidate.name)) throw new Error(`skill provider "${providerName}" returned invalid skill name "${candidate.name}"`);
1599
+ if (typeof candidate.description !== "string") throw new TypeError(`skill provider "${providerName}" returned skill "${candidate.name}" with a non-string description`);
1600
+ if (candidate.description.length === 0) throw new Error(`skill provider "${providerName}" returned skill "${candidate.name}" without a description`);
1601
+ validateInvocation(candidate.invocation, `skill provider "${providerName}" returned skill "${candidate.name}"`);
1602
+ if (candidate.whenToUse !== void 0 && typeof candidate.whenToUse !== "string") throw new TypeError(`skill provider "${providerName}" returned skill "${candidate.name}" with a non-string whenToUse`);
1603
+ if (typeof candidate.source !== "string") throw new TypeError(`skill provider "${providerName}" returned skill "${candidate.name}" with a non-string source`);
1604
+ if (typeof candidate.rank !== "number" || !Number.isFinite(candidate.rank)) throw new Error(`skill provider "${providerName}" returned skill "${candidate.name}" with an invalid rank`);
1605
+ if (typeof candidate.provider !== "string") throw new TypeError(`skill provider "${providerName}" returned skill "${candidate.name}" with a non-string provider`);
1606
+ if (candidate.provider !== providerName) throw new Error(`skill provider "${providerName}" returned skill "${candidate.name}" for provider "${candidate.provider}"`);
1607
+ if (candidate.path !== void 0 && typeof candidate.path !== "string") throw new TypeError(`skill provider "${providerName}" returned skill "${candidate.name}" with a non-string path`);
1608
+ }
1609
+ function validateRuntimeSkill(skill) {
1610
+ if (!SKILL_NAME.test(skill.name)) throw new Error(`invalid skill name "${skill.name}"`);
1611
+ if (skill.description.length === 0) throw new Error(`skill "${skill.name}" requires a description`);
1612
+ validateInvocation(skill.invocation, `runtime skill "${skill.name}"`);
1613
+ }
1614
+ /** Validate a definition loaded from a provider-controlled parser or remote source. */
1615
+ function validateDefinition(skill) {
1616
+ const name = skill.name;
1617
+ const description = skill.description;
1618
+ const whenToUse = skill.whenToUse;
1619
+ const invocation = skill.invocation;
1620
+ const source = skill.source;
1621
+ const provider = skill.provider;
1622
+ const content = skill.content;
1623
+ const path = skill.path;
1624
+ if (typeof name !== "string") throw new TypeError("loaded skill name must be a string");
1625
+ if (!SKILL_NAME.test(name)) throw new Error(`loaded skill has invalid name "${name}"`);
1626
+ if (typeof description !== "string") throw new TypeError(`loaded skill "${name}" description must be a string`);
1627
+ if (description.length === 0) throw new Error(`loaded skill "${name}" requires a description`);
1628
+ validateInvocation(invocation, `loaded skill "${name}"`);
1629
+ if (whenToUse !== void 0 && typeof whenToUse !== "string") throw new TypeError(`loaded skill "${name}" whenToUse must be a string`);
1630
+ if (typeof source !== "string") throw new TypeError(`loaded skill "${name}" source must be a string`);
1631
+ if (typeof provider !== "string") throw new TypeError(`loaded skill "${name}" provider must be a string`);
1632
+ if (typeof content !== "string") throw new TypeError(`loaded skill "${name}" content must be a string`);
1633
+ if (path !== void 0 && typeof path !== "string") throw new TypeError(`loaded skill "${name}" path must be a string`);
1634
+ }
1635
+ function toSummary(skill) {
1636
+ const { name, description, whenToUse, invocation, source, provider, resourceBase } = skill;
1637
+ return {
1638
+ name,
1639
+ description,
1640
+ ...whenToUse !== void 0 ? { whenToUse } : {},
1641
+ invocation,
1642
+ source,
1643
+ provider,
1644
+ ...resourceBase !== void 0 ? { resourceBase } : {}
1645
+ };
1646
+ }
1647
+ function validateInvocation(invocation, subject) {
1648
+ if (invocation === void 0) return;
1649
+ if (typeof invocation !== "object" || invocation === null || Array.isArray(invocation)) throw new TypeError(`${subject} with a non-object invocation policy`);
1650
+ const policy = invocation;
1651
+ if (typeof policy.modelInvocable !== "boolean") throw new TypeError(`${subject} with a non-boolean invocation.modelInvocable`);
1652
+ if (typeof policy.userInvocable !== "boolean") throw new TypeError(`${subject} with a non-boolean invocation.userInvocable`);
1653
+ }
1654
+ function compareSkillSummary(left, right) {
1655
+ return compareCodePoints(left.name, right.name);
1656
+ }
1657
+ function compareCodePoints(left, right) {
1658
+ if (left < right) return -1;
1659
+ if (left > right) return 1;
1660
+ return 0;
1661
+ }
1662
+ function compareIndexedCandidates(left, right) {
1663
+ return left.candidate.rank - right.candidate.rank || left.providerOrder - right.providerOrder || left.localOrder - right.localOrder;
1664
+ }
1665
+ function assertPositiveInteger(name, value, minimum = 1) {
1666
+ if (!Number.isInteger(value) || value < minimum) throw new Error(`skill: ${name} must be an integer greater than or equal to ${minimum}`);
1667
+ }
1668
+ function waitWithAbort(promise, signal) {
1669
+ if (signal === void 0) return promise;
1670
+ throwIfAborted(signal);
1671
+ return new Promise((resolve, reject) => {
1672
+ const cleanup = () => {
1673
+ signal.removeEventListener("abort", onAbort);
1674
+ };
1675
+ const onAbort = () => {
1676
+ cleanup();
1677
+ reject(toError(signal.reason));
1678
+ };
1679
+ signal.addEventListener("abort", onAbort, { once: true });
1680
+ promise.then((value) => {
1681
+ cleanup();
1682
+ resolve(value);
1683
+ }, (error) => {
1684
+ cleanup();
1685
+ reject(toError(error));
1686
+ });
1687
+ });
1688
+ }
1689
+ /** Throw a total Error for an already-aborted lookup. */
1690
+ function throwIfAborted(signal) {
1691
+ if (signal?.aborted === true) throw toError(signal.reason);
1692
+ }
1693
+ /** Normalize an arbitrary abort or provider failure without trusting coercion. */
1694
+ function toError(error) {
1695
+ try {
1696
+ if (error instanceof Error) return error;
1697
+ } catch {}
1698
+ return new Error(errorMessage(error));
1699
+ }
1700
+ /** Render an arbitrary provider failure without letting coercion escape containment. */
1701
+ function errorMessage(error) {
1702
+ try {
1703
+ return String(error);
1704
+ } catch {
1705
+ return "[unrenderable thrown value]";
1706
+ }
1707
+ }
1708
+ //#endregion
1709
+ //#region lib/types/content.js
1710
+ /**
1711
+ * Ponytail skill bodies, ported from github.com/DietrichGebert/ponytail and
1712
+ * lightly adapted to the DeepSeek Harness surface (slash commands and the
1713
+ * `skill` tool). `ponytail` is the source the system-prompt ruleset is
1714
+ * filtered from; the other five ship verbatim as runtime skills.
1715
+ *
1716
+ * @module @mengyuly/dsh-ponytail
1717
+ */
1718
+ /** The always-on lazy-senior-dev ruleset: also registered as a loadable skill. */
1719
+ const PONYTAIL_SKILL_BODY = `
1720
+ You are a lazy senior developer. Lazy means efficient, not careless. You have
1721
+ seen every over-engineered codebase and been paged at 3am for one. The best
1722
+ code is the code never written.
1723
+
1724
+ ## Persistence
1725
+
1726
+ ACTIVE EVERY RESPONSE. No drift back to over-building. Still active if
1727
+ unsure. Off only: "stop ponytail" / "normal mode" / \`/ponytail off\`. Default:
1728
+ **full**. Switch: \`/ponytail lite|full|ultra\`.
1729
+
1730
+ ## The ladder
1731
+
1732
+ Stop at the first rung that holds:
1733
+
1734
+ 1. **Does this need to exist at all?** Speculative need = skip it, say so in one line. (YAGNI)
1735
+ 2. **Already in this codebase?** A helper, util, type, or pattern that already lives here → reuse it. Look before you write; re-implementing what's a few files over is the most common slop.
1736
+ 3. **Stdlib does it?** Use it.
1737
+ 4. **Native platform feature covers it?** \`<input type="date">\` over a picker lib, CSS over JS, DB constraint over app code.
1738
+ 5. **Already-installed dependency solves it?** Use it. Never add a new one for what a few lines can do.
1739
+ 6. **Can it be one line?** One line.
1740
+ 7. **Only then:** the minimum code that works.
1741
+
1742
+ The ladder is a reflex, not a research project — but it runs *after* you
1743
+ understand the problem, not instead of it. Read the task and the code it
1744
+ touches first, trace the real flow end to end, then climb. Two rungs work →
1745
+ take the higher one and move on. The first lazy solution that works is the
1746
+ right one — once you actually know what the change has to touch.
1747
+
1748
+ **Bug fix = root cause, not symptom.** A report names a symptom. Before you
1749
+ edit, grep every caller of the function you're about to touch. The lazy fix IS
1750
+ the root-cause fix: one guard in the shared function is a smaller diff than a
1751
+ guard in every caller — and patching only the path the ticket names leaves
1752
+ every sibling caller still broken. Fix it once, where all callers route through.
1753
+
1754
+ ## Rules
1755
+
1756
+ - No unrequested abstractions: no interface with one implementation, no factory for one product, no config for a value that never changes.
1757
+ - No boilerplate, no scaffolding "for later", later can scaffold for itself.
1758
+ - Deletion over addition. Boring over clever, clever is what someone decodes at 3am.
1759
+ - Fewest files possible. Shortest working diff wins — but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug.
1760
+ - Complex request? Ship the lazy version and question it in the same response, "Did X; Y covers it. Need full X? Say so." Never stall on an answer you can default.
1761
+ - Two stdlib options, same size? Take the one that's correct on edge cases. Lazy means writing less code, not picking the flimsier algorithm.
1762
+ - Mark deliberate simplifications that cut a real corner with a known ceiling (global lock, O(n²) scan, naive heuristic) with a \`ponytail:\` comment naming the ceiling and upgrade path (\`# ponytail: global lock, per-account locks if throughput matters\`).
1763
+
1764
+ ## Output
1765
+
1766
+ Code first. Then at most three short lines: what was skipped, when to add it.
1767
+ No essays, no feature tours, no design notes. If the explanation is longer
1768
+ than the code, delete the explanation, every paragraph defending a
1769
+ simplification is complexity smuggled back in as prose. Explanation the user
1770
+ explicitly asked for (a report, a walkthrough, per-phase notes) is not debt,
1771
+ give it in full, the rule is only against unrequested prose.
1772
+
1773
+ Pattern: \`[code] → skipped: [X], add when [Y].\`
1774
+
1775
+ ## Intensity
1776
+
1777
+ | Level | What change |
1778
+ |-------|------------|
1779
+ | **lite** | Build what's asked, but name the lazier alternative in one line. User picks. |
1780
+ | **full** | The ladder enforced. Stdlib and native first. Shortest diff, shortest explanation. Default. |
1781
+ | **ultra** | YAGNI extremist. Deletion before addition. Ship the one-liner and challenge the rest of the requirement in the same breath. |
1782
+
1783
+ Example: "Add a cache for these API responses."
1784
+ - lite: "Done, cache added. FYI: \`functools.lru_cache\` covers this in one line if you'd rather not own a cache class."
1785
+ - full: "\`@lru_cache(maxsize=1000)\` on the fetch function. Skipped custom cache class, add when lru_cache measurably falls short."
1786
+ - ultra: "No cache until a profiler says so. When it does: \`@lru_cache\`. A hand-rolled TTL cache class is a bug farm with a hit rate."
1787
+
1788
+ ## When NOT to be lazy
1789
+
1790
+ Never simplify away: input validation at trust boundaries, error handling
1791
+ that prevents data loss, security measures, accessibility basics, anything
1792
+ explicitly requested. User insists on the full version → build it, no
1793
+ re-arguing.
1794
+
1795
+ Never lazy about understanding the problem. The ladder shortens the
1796
+ solution, never the reading. Trace the whole thing first — every file the
1797
+ change touches, the actual flow — before picking a rung. Laziness that skips
1798
+ comprehension to ship a small diff is the dangerous kind: it dresses up as
1799
+ efficiency and ships a confident wrong fix. Read fully, then be lazy.
1800
+
1801
+ Hardware is never the ideal on paper: a real clock drifts, a real sensor
1802
+ reads off, a PCA9685 runs a few percent fast. Leave the calibration knob, not
1803
+ just less code, the physical world needs tuning a minimal model can't see.
1804
+
1805
+ Lazy code without its check is unfinished. Non-trivial logic (a branch, a
1806
+ loop, a parser, a money/security path) leaves ONE runnable check behind, the
1807
+ smallest thing that fails if the logic breaks: an \`assert\`-based
1808
+ \`demo()\`/\`__main__\` self-check or one small test file. No frameworks, no
1809
+ fixtures, no per-function suites unless asked. Trivial one-liners need no
1810
+ test, YAGNI applies to tests too.
1811
+
1812
+ ## Boundaries
1813
+
1814
+ Ponytail governs what you build, not how you talk. "stop ponytail" / "normal
1815
+ mode" / \`/ponytail off\`: revert. Level is session-scoped until changed; the
1816
+ configured default (env or config file) applies to new sessions.
1817
+
1818
+ The shortest path to done is the right path.
1819
+ `;
1820
+ const PONYTAIL_DESCRIPTION = "Force the laziest solution that actually works — simplest, shortest, most minimal. Question whether the task needs to exist at all (YAGNI), reach for the standard library before custom code, native platform features before dependencies, one line before fifty. Supports intensity levels lite, full (default), and ultra. Use on ANY coding task: writing, adding, refactoring, fixing, reviewing, or designing code, and choosing libraries or dependencies. Also use when the user says \"ponytail\", \"be lazy\", \"lazy mode\", \"simplest solution\", \"minimal solution\", \"yagni\", \"do less\", or \"shortest path\", or complains about over-engineering, bloat, boilerplate, or unnecessary dependencies. Do NOT use for non-coding requests (general knowledge, prose, translation, summaries, recipes).";
1821
+ const REVIEW_SKILL_BODY = `
1822
+ Review diffs for unnecessary complexity. One line per finding: location, what
1823
+ to cut, what replaces it. The diff's best outcome is getting shorter.
1824
+
1825
+ ## Format
1826
+
1827
+ \`L<line>: <tag> <what>. <replacement>.\`, or \`<file>:L<line>: ...\` for
1828
+ multi-file diffs.
1829
+
1830
+ Tags:
1831
+
1832
+ - \`delete:\` dead code, unused flexibility, speculative feature. Replacement: nothing.
1833
+ - \`stdlib:\` hand-rolled thing the standard library ships. Name the function.
1834
+ - \`native:\` dependency or code doing what the platform already does. Name the feature.
1835
+ - \`yagni:\` abstraction with one implementation, config nobody sets, layer with one caller.
1836
+ - \`shrink:\` same logic, fewer lines. Show the shorter form.
1837
+
1838
+ ## Examples
1839
+
1840
+ ❌ "This EmailValidator class might be more complex than necessary, have you
1841
+ considered whether all these validation rules are needed at this stage?"
1842
+
1843
+ ✅ \`L12-38: stdlib: 27-line validator class. "@" in email, 1 line, real validation is the confirmation mail.\`
1844
+
1845
+ ✅ \`L4: native: moment.js imported for one format call. Intl.DateTimeFormat, 0 deps.\`
1846
+
1847
+ ✅ \`repo.py:L88: yagni: AbstractRepository with one implementation. Inline it until a second one exists.\`
1848
+
1849
+ ✅ \`L52-71: delete: retry wrapper around an idempotent local call. Nothing replaces it.\`
1850
+
1851
+ ✅ \`L30-44: shrink: manual loop builds dict. dict(zip(keys, values)), 1 line.\`
1852
+
1853
+ ## Scoring
1854
+
1855
+ End with the only metric that matters: \`net: -<N> lines possible.\`
1856
+
1857
+ If there is nothing to cut, say \`Lean already. Ship.\` and stop.
1858
+
1859
+ ## Boundaries
1860
+
1861
+ Scope: over-engineering and complexity only. Correctness bugs, security holes,
1862
+ and performance are explicitly out of scope. Route them to a normal review
1863
+ pass, not this one. A single smoke test or \`assert\`-based
1864
+ self-check is the ponytail minimum, not bloat, never flag it for deletion.
1865
+ Does not apply the fixes, only lists them.
1866
+ "stop ponytail-review" or "normal mode": revert to verbose review style.
1867
+ `;
1868
+ const REVIEW_DESCRIPTION = "Code review focused exclusively on over-engineering. Finds what to delete: reinvented standard library, unneeded dependencies, speculative abstractions, dead flexibility. One line per finding: location, what to cut, what replaces it. Use when the user says \"review for over-engineering\", \"what can we delete\", \"is this over-engineered\", \"simplify review\", or invokes /ponytail-review. Complements correctness-focused review, this one only hunts complexity.";
1869
+ const AUDIT_SKILL_BODY = `
1870
+ ponytail-review, repo-wide. Scan the whole tree instead of a diff. Rank
1871
+ findings biggest cut first.
1872
+
1873
+ ## Tags
1874
+
1875
+ Same as ponytail-review:
1876
+
1877
+ - \`delete:\` dead code, unused flexibility, speculative feature. Replacement: nothing.
1878
+ - \`stdlib:\` hand-rolled thing the standard library ships. Name the function.
1879
+ - \`native:\` dependency or code doing what the platform already does. Name the feature.
1880
+ - \`yagni:\` abstraction with one implementation, config nobody sets, layer with one caller.
1881
+ - \`shrink:\` same logic, fewer lines. Show the shorter form.
1882
+
1883
+ ## Hunt
1884
+
1885
+ Deps the stdlib or platform already ships, single-implementation interfaces,
1886
+ factories with one product, wrappers that only delegate, files exporting one
1887
+ thing, dead flags and config, hand-rolled stdlib.
1888
+
1889
+ ## Output
1890
+
1891
+ One line per finding, ranked: \`<tag> <what to cut>. <replacement>. [path]\`.
1892
+ End with \`net: -<N> lines, -<M> deps possible.\` Nothing to cut: \`Lean already. Ship.\`
1893
+
1894
+ ## Boundaries
1895
+
1896
+ Scope: over-engineering and complexity only. Correctness bugs, security holes,
1897
+ and performance are explicitly out of scope. Route them to a normal review
1898
+ pass. Lists findings, applies nothing. One-shot.
1899
+ "stop ponytail-audit" or "normal mode" to revert.
1900
+ `;
1901
+ const AUDIT_DESCRIPTION = "Whole-repo audit for over-engineering. Like ponytail-review, but scans the entire codebase instead of a diff: a ranked list of what to delete, simplify, or replace with stdlib/native equivalents. Use when the user says \"audit this codebase\", \"audit for over-engineering\", \"what can I delete from this repo\", \"find bloat\", \"ponytail-audit\", or /ponytail-audit. One-shot report, does not apply fixes.";
1902
+ const DEBT_SKILL_BODY = `
1903
+ Every deliberate ponytail shortcut is marked with a \`ponytail:\` comment naming
1904
+ its ceiling and upgrade path. This collects them into one ledger so a deferral
1905
+ can't quietly become permanent.
1906
+
1907
+ ## Scan
1908
+
1909
+ Grep the repo for comment markers, skipping \`node_modules\`, \`.git\`, and build
1910
+ output:
1911
+
1912
+ \`grep -rnE '(#|//) ?ponytail:' .\` (add other comment prefixes if your stack uses them)
1913
+
1914
+ Each hit is one ledger row. The comment prefix keeps prose that merely mentions
1915
+ the convention out of the ledger.
1916
+
1917
+ ## Output
1918
+
1919
+ One row per marker, grouped by file:
1920
+
1921
+ \`<file>:<line>, <what was simplified>. ceiling: <the limit named>. upgrade: <the trigger to revisit>.\`
1922
+
1923
+ The convention is \`ponytail: <ceiling>, <upgrade path>\`, so pull the ceiling
1924
+ and the trigger straight from the comment. Want an owner per row too? add
1925
+ \`git blame -L<line>,<line>\`.
1926
+
1927
+ Flag the rot risk: any \`ponytail:\` comment that names no upgrade path or
1928
+ trigger gets a \`no-trigger\` tag, those are the ones that silently rot.
1929
+
1930
+ End with \`<N> markers, <M> with no trigger.\` Nothing found: \`No ponytail: debt. Clean ledger.\`
1931
+
1932
+ ## Boundaries
1933
+
1934
+ Reads and reports only, changes nothing. To persist it, ask and it writes the
1935
+ ledger to a file (e.g. \`PONYTAIL-DEBT.md\`). One-shot. "stop ponytail-debt" or
1936
+ "normal mode" to revert.
1937
+ `;
1938
+ const DEBT_DESCRIPTION = "Harvest every `ponytail:` comment in the codebase into a debt ledger, so the deliberate shortcuts and deferrals ponytail leaves behind get tracked instead of rotting into \"later means never\". Use when the user says \"ponytail debt\", \"/ponytail-debt\", \"what did ponytail defer\", \"list the shortcuts\", \"ponytail ledger\", or \"what did we mark to do later\". One-shot report, changes nothing.";
1939
+ const GAIN_SKILL_BODY = `
1940
+ Display this scoreboard when invoked. One-shot: do NOT change mode, write flag
1941
+ files, or persist anything.
1942
+
1943
+ The figures are the published benchmark medians (5 everyday tasks: email
1944
+ validator, debounce, CSV sum, countdown timer, rate limiter; three models:
1945
+ Haiku, Sonnet, Opus). They are measured, not computed from the current repo.
1946
+ Source: the upstream \`benchmarks/\` directory and README.
1947
+
1948
+ ## Scoreboard
1949
+
1950
+ Render plain ASCII bars. The bar length shows the measured range; the label
1951
+ carries the exact figure:
1952
+
1953
+ \`\`\`
1954
+ ponytail gain benchmark median · 5 tasks · 3 models
1955
+
1956
+ Lines of code no-skill ████████████████████ 100%
1957
+ ponytail ██▌················· 6–20% ▼ 80–94%
1958
+ Cost no-skill ████████████████████ 100%
1959
+ ponytail █████▌·············· 23–53% ▼ 47–77%
1960
+ Speed ponytail ▸ 3–6× faster
1961
+
1962
+ This repo: /ponytail-debt (shortcuts you deferred)
1963
+ /ponytail-audit (what's still cuttable)
1964
+ \`\`\`
1965
+
1966
+ ## Honesty boundary
1967
+
1968
+ These are benchmark medians, not this repo. NEVER print a per-repo savings
1969
+ number ("you saved X lines/tokens here"): the unbuilt version was never
1970
+ written, so there is no real baseline to subtract from in a live repo. The
1971
+ only real per-repo figures come from \`/ponytail-debt\` (a counted ledger), and
1972
+ this card points there instead of inventing one.
1973
+
1974
+ ## Boundaries
1975
+
1976
+ One-shot display. Edits nothing, changes no mode.
1977
+ "stop ponytail" or "normal mode": revert.
1978
+ `;
1979
+ const GAIN_DESCRIPTION = "Show ponytail's measured impact as a compact scoreboard: less code, less cost, more speed, from the benchmark medians. One-shot display, not a persistent mode, and not a per-repo number. Trigger: /ponytail-gain, \"ponytail gain\", \"what does ponytail save\", \"show ponytail impact\", \"ponytail scoreboard\".";
1980
+ const HELP_SKILL_BODY = `
1981
+ Display this reference card when invoked. One-shot, do NOT change mode,
1982
+ write flag files, or persist anything.
1983
+
1984
+ ## Levels
1985
+
1986
+ | Level | Trigger | What change |
1987
+ |-------|---------|-------------|
1988
+ | **Lite** | \`/ponytail lite\` | Build what's asked, name the lazier alternative in one line. |
1989
+ | **Full** | \`/ponytail\` | The ladder enforced: YAGNI → stdlib → native → one line → minimum. Default. |
1990
+ | **Ultra** | \`/ponytail ultra\` | YAGNI extremist. Deletion before addition. Challenges requirements before building. |
1991
+ | **Off** | \`/ponytail off\` | Ponytail stops injecting its ruleset for this session. |
1992
+
1993
+ Level is session-scoped until changed.
1994
+
1995
+ ## Skills
1996
+
1997
+ | Skill | Trigger | What it does |
1998
+ |-------|---------|--------------|
1999
+ | **ponytail** | \`/ponytail\` | Lazy mode itself. Simplest solution that works. |
2000
+ | **ponytail-review** | \`/ponytail-review\` | Over-engineering review: \`L42: yagni: factory, one product. Inline.\` |
2001
+ | **ponytail-audit** | \`/ponytail-audit\` | Whole-repo over-engineering audit: ranked list of what to delete. |
2002
+ | **ponytail-debt** | \`/ponytail-debt\` | Harvest \`ponytail:\` shortcut comments into a tracked ledger. |
2003
+ | **ponytail-gain** | \`/ponytail-gain\` | Measured-impact scoreboard: less code, less cost, more speed. |
2004
+ | **ponytail-help** | \`/ponytail-help\` | This card. |
2005
+
2006
+ You can also load any of these with the \`skill\` tool.
2007
+
2008
+ ## Deactivate
2009
+
2010
+ Say "stop ponytail" or "normal mode". Resume anytime with \`/ponytail\`.
2011
+ \`/ponytail off\` also works. Level is session-scoped; a new session starts
2012
+ from the configured default.
2013
+
2014
+ ## Configure Default Mode
2015
+
2016
+ Default mode = \`full\`, auto-active every session. Change it:
2017
+
2018
+ **Environment variable** (highest priority):
2019
+ \`\`\`bash
2020
+ export PONYTAIL_DEFAULT_MODE=ultra
2021
+ \`\`\`
2022
+
2023
+ **Config file** (\`~/.config/ponytail/config.json\`, Windows: \`%APPDATA%\\ponytail\\config.json\`):
2024
+ \`\`\`json
2025
+ { "defaultMode": "lite" }
2026
+ \`\`\`
2027
+
2028
+ Set \`"off"\` to disable auto-activation on session start, activate manually
2029
+ with \`/ponytail\` when wanted. \`/ponytail default <mode>\` persists a new
2030
+ default from inside a session.
2031
+
2032
+ Resolution: env var > config file > \`full\`.
2033
+
2034
+ ## More
2035
+
2036
+ Full docs + examples: https://github.com/DietrichGebert/ponytail
2037
+ `;
2038
+ const HELP_DESCRIPTION = "Quick-reference card for all ponytail modes, skills, and commands. One-shot display, not a persistent mode. Trigger: /ponytail-help, \"ponytail help\", \"what ponytail commands\", \"how do I use ponytail\".";
2039
+ /** Ordered set of runtime skills surfaced to the model catalog and `/` menu. */
2040
+ function ponytailSkills() {
2041
+ return [
2042
+ {
2043
+ name: "ponytail",
2044
+ source: "runtime",
2045
+ description: PONYTAIL_DESCRIPTION,
2046
+ whenToUse: "Any coding task where the user wants the simplest, shortest, most minimal working solution.",
2047
+ content: PONYTAIL_SKILL_BODY,
2048
+ invocation: {
2049
+ modelInvocable: true,
2050
+ userInvocable: true
2051
+ }
2052
+ },
2053
+ {
2054
+ name: "ponytail-review",
2055
+ source: "runtime",
2056
+ description: REVIEW_DESCRIPTION,
2057
+ content: REVIEW_SKILL_BODY,
2058
+ invocation: {
2059
+ modelInvocable: true,
2060
+ userInvocable: true
2061
+ }
2062
+ },
2063
+ {
2064
+ name: "ponytail-audit",
2065
+ source: "runtime",
2066
+ description: AUDIT_DESCRIPTION,
2067
+ content: AUDIT_SKILL_BODY,
2068
+ invocation: {
2069
+ modelInvocable: true,
2070
+ userInvocable: true
2071
+ }
2072
+ },
2073
+ {
2074
+ name: "ponytail-debt",
2075
+ source: "runtime",
2076
+ description: DEBT_DESCRIPTION,
2077
+ content: DEBT_SKILL_BODY,
2078
+ invocation: {
2079
+ modelInvocable: true,
2080
+ userInvocable: true
2081
+ }
2082
+ },
2083
+ {
2084
+ name: "ponytail-gain",
2085
+ source: "runtime",
2086
+ description: GAIN_DESCRIPTION,
2087
+ content: GAIN_SKILL_BODY,
2088
+ invocation: {
2089
+ modelInvocable: true,
2090
+ userInvocable: true
2091
+ }
2092
+ },
2093
+ {
2094
+ name: "ponytail-help",
2095
+ source: "runtime",
2096
+ description: HELP_DESCRIPTION,
2097
+ content: HELP_SKILL_BODY,
2098
+ invocation: {
2099
+ modelInvocable: true,
2100
+ userInvocable: true
2101
+ }
2102
+ }
2103
+ ];
2104
+ }
2105
+ //#endregion
2106
+ //#region lib/types/modes.js
2107
+ /**
2108
+ * Ponytail mode resolution: the default level comes from the
2109
+ * `PONYTAIL_DEFAULT_MODE` environment variable, then the optional config file
2110
+ * `~/.config/ponytail/config.json` (`defaultMode`), then `full`. Setting a
2111
+ * level via the `/ponytail` command is session-scoped and lives in an
2112
+ * in-memory, per-agent {@link ModeStore}.
2113
+ *
2114
+ * @module @mengyuly/dsh-ponytail
2115
+ */
2116
+ const DEFAULT_MODE = "full";
2117
+ const RUNTIME_MODES = [
2118
+ "off",
2119
+ "lite",
2120
+ "full",
2121
+ "ultra"
2122
+ ];
2123
+ /** Strip a UTF-8 BOM that Windows editors prepend before JSON.parse. */
2124
+ function stripBom(text) {
2125
+ return text.replace(/^\uFEFF/, "");
2126
+ }
2127
+ /**
2128
+ * Normalize free-form input to a runtime intensity. `null` for anything that
2129
+ * is not exactly `off`, `lite`, `full`, or `ultra`.
2130
+ */
2131
+ function normalizeRuntimeMode(mode) {
2132
+ if (typeof mode !== "string") return null;
2133
+ const normalized = mode.trim().toLowerCase();
2134
+ return RUNTIME_MODES.includes(normalized) ? normalized : null;
2135
+ }
2136
+ /**
2137
+ * Deactivation commands only match when the whole message is the command,
2138
+ * ignoring case and trailing punctuation. Matching the phrase anywhere would
2139
+ * turn ponytail off mid-task for ordinary requests like "add a normal mode
2140
+ * toggle".
2141
+ */
2142
+ function isDeactivationCommand(text) {
2143
+ const normalized = (typeof text === "string" ? text : "").trim().toLowerCase().replace(/[.!?\s]+$/, "");
2144
+ return normalized === "stop ponytail" || normalized === "normal mode";
2145
+ }
2146
+ /** Config directory: `$XDG_CONFIG_HOME/ponytail`, `%APPDATA%\ponytail`, else `~/.config/ponytail`. */
2147
+ function configDir(env = process.env) {
2148
+ if (env.XDG_CONFIG_HOME) return join(env.XDG_CONFIG_HOME, "ponytail");
2149
+ if (process.platform === "win32") return join(env.APPDATA || join(homedir(), "AppData", "Roaming"), "ponytail");
2150
+ return join(homedir(), ".config", "ponytail");
2151
+ }
2152
+ /** Absolute path of the optional `config.json`. */
2153
+ function configPath(env = process.env) {
2154
+ return join(configDir(env), "config.json");
2155
+ }
2156
+ /**
2157
+ * Resolve a default from the environment value, then a parsed config document,
2158
+ * then {@link DEFAULT_MODE}. Pure so callers can supply fixtures.
2159
+ */
2160
+ function resolveDefaultMode(envMode, configText) {
2161
+ const fromEnv = normalizeRuntimeMode(envMode);
2162
+ if (fromEnv) return fromEnv;
2163
+ if (configText !== void 0) try {
2164
+ const config = JSON.parse(stripBom(configText));
2165
+ if (config && typeof config === "object" && !Array.isArray(config)) {
2166
+ const fromConfig = normalizeRuntimeMode(config.defaultMode);
2167
+ if (fromConfig) return fromConfig;
2168
+ }
2169
+ } catch {}
2170
+ return DEFAULT_MODE;
2171
+ }
2172
+ /**
2173
+ * Read the configured default for this host: environment variable first, then
2174
+ * the config file, then `full`.
2175
+ */
2176
+ function readDefaultMode(env = process.env) {
2177
+ const path = configPath(env);
2178
+ let configText;
2179
+ try {
2180
+ configText = readFileSync(path, "utf8");
2181
+ } catch {
2182
+ configText = void 0;
2183
+ }
2184
+ return resolveDefaultMode(env.PONYTAIL_DEFAULT_MODE, configText);
2185
+ }
2186
+ /**
2187
+ * Persist a new default level to the config file, preserving other fields.
2188
+ * Returns the normalized mode, or `null` when the value is not a runtime mode.
2189
+ */
2190
+ function writeDefaultMode(mode, env = process.env) {
2191
+ const normalized = normalizeRuntimeMode(mode);
2192
+ if (!normalized) return null;
2193
+ const path = configPath(env);
2194
+ let config = {};
2195
+ try {
2196
+ const parsed = JSON.parse(stripBom(readFileSync(path, "utf8")));
2197
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) config = parsed;
2198
+ } catch {}
2199
+ config.defaultMode = normalized;
2200
+ mkdirSync(dirname(path), { recursive: true });
2201
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
2202
+ return normalized;
2203
+ }
2204
+ /**
2205
+ * Session-scoped live mode. The absence of an entry means "use the configured
2206
+ * default", which matches the upstream behavior where each session starts from
2207
+ * the default until the user switches it.
2208
+ */
2209
+ var ModeStore = class {
2210
+ modes = /* @__PURE__ */ new Map();
2211
+ /** The mode in force for one agent, or the configured default. */
2212
+ modeFor(agentId, fallback) {
2213
+ return this.modes.get(agentId) ?? fallback;
2214
+ }
2215
+ /** Set the mode for one agent's session (session-scoped, survives until changed or disposal). */
2216
+ set(agentId, mode) {
2217
+ this.modes.set(agentId, mode);
2218
+ }
2219
+ /** Forget a session-scoped override so the next lookup returns the default. */
2220
+ clear(agentId) {
2221
+ this.modes.delete(agentId);
2222
+ }
2223
+ };
2224
+ /**
2225
+ * Compile `PONYTAIL_SUBAGENT_MATCHER` into a case-insensitive regex, or `null`
2226
+ * — for "no matcher" and for invalid patterns, both of which mean the ruleset
2227
+ * applies to every agent (fail open, like upstream).
2228
+ */
2229
+ function compileSubagentMatcher(raw) {
2230
+ if (!raw) return null;
2231
+ try {
2232
+ return new RegExp(raw, "i");
2233
+ } catch {
2234
+ return null;
2235
+ }
2236
+ }
2237
+ /** Whether a session is a subagent child (origin, or any delegation depth with no origin). */
2238
+ function isSubagentSession(header) {
2239
+ return header.origin === "subagent" || (header.delegationDepth ?? 0) > 0;
2240
+ }
2241
+ //#endregion
2242
+ //#region lib/types/instructions.js
2243
+ /**
2244
+ * Build the mode-filtered ponytail ruleset. Ported from the upstream
2245
+ * `hooks/ponytail-instructions.js`, so the injected text is byte-for-byte the
2246
+ * same ruleset every other host emits, filtered to the active intensity.
2247
+ *
2248
+ * @module @mengyuly/dsh-ponytail
2249
+ */
2250
+ /**
2251
+ * Keep a line of the skill body only when it belongs to every mode or to the
2252
+ * active one. Both shape-sensitive spots (the intensity table rows and the
2253
+ * quoted worked examples) are keyed by a mode name; ordinary rules survive
2254
+ * verbatim, even ones whose prose starts with a mode-looking word.
2255
+ */
2256
+ function filterSkillBodyForMode(body, mode) {
2257
+ const effective = normalizeRuntimeMode(mode) ?? "full";
2258
+ return body.split(/\r?\n/).filter((line) => {
2259
+ const tableLabel = line.match(/^\|\s*\*\*(.+?)\*\*\s*\|/);
2260
+ if (tableLabel) {
2261
+ const labelMode = normalizeRuntimeMode(tableLabel[1]);
2262
+ if (labelMode) return labelMode === effective;
2263
+ }
2264
+ const exampleLabel = line.match(/^-\s*([^:]+):\s*"/);
2265
+ if (exampleLabel) {
2266
+ const labelMode = normalizeRuntimeMode(exampleLabel[1]);
2267
+ if (labelMode) return labelMode === effective;
2268
+ }
2269
+ return true;
2270
+ }).join("\n");
2271
+ }
2272
+ /** Minimal instruction set if the skill body can't be read (parity fallback). */
2273
+ function fallbackInstructions(mode) {
2274
+ return "PONYTAIL MODE ACTIVE — level: " + mode + "\n\nYou are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.\n\n## Persistence\n\nACTIVE EVERY RESPONSE. No drift back to over-building. Still active if unsure. Off only: \"stop ponytail\" / \"normal mode\".\n\nCurrent level: **" + mode + "**. Switch: `/ponytail lite|full|ultra`.\n\n## The ladder\n\nBefore any code, stop at the first rung that holds (the ladder runs after you understand the problem, not instead of it — read the code it touches and trace the real flow first):\n1. Does this need to be built at all? (YAGNI)\n2. Does it already exist in this codebase? Reuse what is already here, do not re-write it.\n3. Does the standard library do this? Use it.\n4. Does a native platform feature cover it? Use it.\n5. Does an already-installed dependency solve it? Use it.\n6. Can this be one line? Make it one line.\n7. Only then: write the minimum code that works.\n\nBug fix = root cause, not symptom: grep every caller of the function you touch and fix the shared function once (a smaller diff than one guard per caller); patching only the path the ticket names leaves a sibling caller broken.\n\n## Rules\n\nNo abstractions that were not requested. No avoidable dependencies. No boilerplate nobody asked for. Deletion over addition. Boring over clever. Fewest files possible. Ship the lazy version and question the complex request in the same response — never stall. Between two same-size stdlib options, pick the one correct on edge cases. Mark deliberate simplifications that cut a real corner with a known ceiling, using a `ponytail:` comment that names the ceiling and upgrade path.\n\n## Output\n\nCode first. Then at most three short lines: what was skipped, when to add it. If the explanation is longer than the code, delete the explanation. Explanation the user explicitly asked for is not debt, give it in full.\n\n## When NOT to be lazy\n\nNever simplify away: understanding the problem (read it fully and trace the real flow before picking a rung — a small diff you do not understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security measures, accessibility basics, the calibration real hardware needs (the platform is never the spec ideal), anything the user explicitly asked to keep. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind (assert-based demo/self-check or one small test file; no frameworks). Trivial one-liners need no test.\n\n## Boundaries\n\nPonytail governs what you build, not how you talk. \"stop ponytail\" or \"normal mode\": revert. Level persists until changed.";
2275
+ }
2276
+ /**
2277
+ * The full injected ruleset for one intensity: the "PONYTAIL MODE ACTIVE"
2278
+ * header plus the body filtered down to that mode's rows and examples.
2279
+ * Returns an empty string for `off` (ponytail contributes nothing).
2280
+ */
2281
+ function getPonytailInstructions(mode) {
2282
+ const effective = normalizeRuntimeMode(mode) ?? "full";
2283
+ if (effective === "off") return "";
2284
+ const cached = instructionCache.get(effective);
2285
+ if (cached !== void 0) return cached;
2286
+ let body;
2287
+ try {
2288
+ body = filterSkillBodyForMode(PONYTAIL_SKILL_BODY, effective);
2289
+ } catch {
2290
+ return fallbackInstructions(effective);
2291
+ }
2292
+ const rendered = "PONYTAIL MODE ACTIVE — level: " + effective + "\n\n" + body;
2293
+ instructionCache.set(effective, rendered);
2294
+ return rendered;
2295
+ }
2296
+ /** Rendered rulesets are pure per mode; cache to keep every turn's bytes identical. */
2297
+ const instructionCache = /* @__PURE__ */ new Map();
2298
+ //#endregion
2299
+ //#region lib/types/index.js
2300
+ /**
2301
+ * Ponytail: the "lazy senior developer" persona as a DeepSeek Harness plugin.
2302
+ *
2303
+ * One system-prompt section injects the mode-filtered ruleset every turn (the
2304
+ * always-on adapter), six runtime skills surface the review/audit/debt/gain/
2305
+ * help one-shots, six slash commands drive them from the command plane, and an
2306
+ * `agent/pre-step` listener honors the plain-text deactivation phrases.
2307
+ *
2308
+ * Mode is session-scoped and held in memory; the configured default resolves
2309
+ * from `PONYTAIL_DEFAULT_MODE` then `~/.config/ponytail/config.json` (see
2310
+ * {@link readDefaultMode}).
2311
+ *
2312
+ * @module @mengyuly/dsh-ponytail
2313
+ */
2314
+ const name = "ponytail";
2315
+ const inject = ["systemPrompt", "skills"];
2316
+ /** Prompt-section order: after the deployment persona (0), before tool guidance (100–199). */
2317
+ const SECTION_ORDER = 40;
2318
+ /** Build the one text-line notification a mode switch leaves for the model. */
2319
+ function modeNotice(mode) {
2320
+ return mode === "off" ? "PONYTAIL MODE OFF" : `PONYTAIL MODE CHANGED — level: ${mode}`;
2321
+ }
2322
+ /** Extract the plain text of one user message (only its text blocks). */
2323
+ function messageText(message) {
2324
+ const parts = [];
2325
+ for (const block of message.content) if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
2326
+ return parts.join("\n");
2327
+ }
2328
+ /** Whether any message in a claimed batch is exactly a deactivation command. */
2329
+ function containsDeactivation(messages) {
2330
+ return messages.some((message) => isDeactivationCommand(messageText(message)));
2331
+ }
2332
+ /** Mode visible to one agent: its session override, else the configured default. */
2333
+ function modeFor(deps, agent) {
2334
+ return deps.store.modeFor(String(agent.id), deps.defaultMode());
2335
+ }
2336
+ /**
2337
+ * Queue one skill's full `<skill_content>` rendering as the model's next
2338
+ * ordinary turn, with the same user-explicit `skill-invocation` source the
2339
+ * built-in gesture boundary uses.
2340
+ */
2341
+ async function queueSkill(deps, invocation, skill) {
2342
+ const loaded = await deps.ctx.skills.get(skill, {
2343
+ cwd: invocation.agent.session.header.cwd,
2344
+ signal: invocation.signal
2345
+ });
2346
+ if (loaded === void 0) return {
2347
+ kind: "error",
2348
+ text: `skill "${skill}" is not available`
2349
+ };
2350
+ const notes = invocation.rawInput.trim();
2351
+ const text = renderSkillContent(loaded) + (notes === "" ? "" : `\n\n${notes}`);
2352
+ invocation.agent.followup(createUserMessage({
2353
+ content: [{
2354
+ type: "text",
2355
+ text
2356
+ }],
2357
+ source: {
2358
+ kind: "skill-invocation",
2359
+ name: skill,
2360
+ form: "instructions"
2361
+ }
2362
+ }));
2363
+ return {
2364
+ kind: "success",
2365
+ text: `Queued ${skill} for the agent.`
2366
+ };
2367
+ }
2368
+ function registerCommands(deps, commandCtx) {
2369
+ commandCtx.commands.register({
2370
+ name: "ponytail",
2371
+ description: "Set or show Ponytail lazy senior dev intensity",
2372
+ input: { hint: "[lite|full|ultra|off|default <mode>]" },
2373
+ handler: ({ agent, rawInput }) => {
2374
+ const input = rawInput.trim().toLowerCase();
2375
+ const [head, ...rest] = input.split(/\s+/).filter(Boolean);
2376
+ if ((head ?? "") === "default") {
2377
+ const written = writeDefaultMode(rest[0]);
2378
+ if (!written) return {
2379
+ kind: "error",
2380
+ text: "Usage: /ponytail default [lite|full|ultra|off]"
2381
+ };
2382
+ deps.setDefault(written);
2383
+ agent.steer(createUserMessage({
2384
+ content: [{
2385
+ type: "text",
2386
+ text: `PONYTAIL DEFAULT SET — new sessions start in ${written}.`
2387
+ }],
2388
+ source: {
2389
+ kind: "plugin",
2390
+ plugin: name
2391
+ }
2392
+ }));
2393
+ return {
2394
+ kind: "success",
2395
+ text: `Ponyytail default set — new sessions start in ${written}.`
2396
+ };
2397
+ }
2398
+ if (input === "") {
2399
+ const current = modeFor(deps, agent);
2400
+ agent.steer(createUserMessage({
2401
+ content: [{
2402
+ type: "text",
2403
+ text: `PONYTAIL MODE ACTIVE — level: ${current}`
2404
+ }],
2405
+ source: {
2406
+ kind: "plugin",
2407
+ plugin: name
2408
+ }
2409
+ }));
2410
+ return {
2411
+ kind: "success",
2412
+ text: `Ponytail mode: ${current}. Use /ponytail lite|full|ultra|off.`
2413
+ };
2414
+ }
2415
+ const mode = normalizeRuntimeMode(input);
2416
+ if (!mode) return {
2417
+ kind: "error",
2418
+ text: "Usage: /ponytail [lite|full|ultra|off]"
2419
+ };
2420
+ deps.store.set(String(agent.id), mode);
2421
+ agent.steer(createUserMessage({
2422
+ content: [{
2423
+ type: "text",
2424
+ text: modeNotice(mode)
2425
+ }],
2426
+ source: {
2427
+ kind: "plugin",
2428
+ plugin: name
2429
+ }
2430
+ }));
2431
+ return {
2432
+ kind: "success",
2433
+ text: mode === "off" ? "Ponytail mode off." : `Ponytail mode set to ${mode}.`
2434
+ };
2435
+ }
2436
+ });
2437
+ for (const skill of [
2438
+ "ponytail-review",
2439
+ "ponytail-audit",
2440
+ "ponytail-debt",
2441
+ "ponytail-gain",
2442
+ "ponytail-help"
2443
+ ]) commandCtx.commands.register({
2444
+ name: skill,
2445
+ description: descriptionFor(skill),
2446
+ input: { hint: "[notes]" },
2447
+ handler: (invocation) => queueSkill(deps, invocation, skill)
2448
+ });
2449
+ }
2450
+ /** One-line command catalog copy, kept beside the skills for discovery parity. */
2451
+ function descriptionFor(skill) {
2452
+ switch (skill) {
2453
+ case "ponytail-review": return "Over-engineering review of the current changes";
2454
+ case "ponytail-audit": return "Whole-repo over-engineering audit (what can be deleted)";
2455
+ case "ponytail-debt": return "Harvest ponytail: comments into a tracked debt ledger";
2456
+ case "ponytail-gain": return "Show ponytail measured-impact scoreboard (less code, cost, time)";
2457
+ case "ponytail-help": return "Quick reference for ponytail levels, skills, and commands";
2458
+ default: return `Run the ${skill} skill`;
2459
+ }
2460
+ }
2461
+ /**
2462
+ * Register the always-on ruleset section, the runtime skills, the slash
2463
+ * commands, and the plain-text deactivation listener.
2464
+ */
2465
+ function apply(ctx) {
2466
+ let defaultMode = null;
2467
+ const readDefault = () => defaultMode ??= readDefaultMode();
2468
+ const setDefault = (mode) => {
2469
+ defaultMode = mode;
2470
+ };
2471
+ const store = new ModeStore();
2472
+ const matcher = compileSubagentMatcher(process.env.PONYTAIL_SUBAGENT_MATCHER);
2473
+ const configFile = configPath();
2474
+ const onConfigChange = () => {
2475
+ defaultMode = readDefaultMode();
2476
+ };
2477
+ watchFile(configFile, { interval: 1e3 }, onConfigChange).unref();
2478
+ ctx.effect(() => () => unwatchFile(configFile, onConfigChange), "ponytail: config hot reload");
2479
+ ctx.systemPrompt.section({
2480
+ name: "ponytail",
2481
+ order: SECTION_ORDER,
2482
+ text: ({ agent }) => {
2483
+ if (agent && matcher && isSubagentSession(agent.session.header)) {
2484
+ const preset = agent.session.header.agentPreset;
2485
+ if (preset && !matcher.test(preset)) return "";
2486
+ }
2487
+ return getPonytailInstructions(agent ? store.modeFor(String(agent.id), readDefault()) : readDefault());
2488
+ }
2489
+ });
2490
+ for (const skill of ponytailSkills()) ctx.skills.register(skill);
2491
+ ctx.inject(["commands"], (commandCtx) => {
2492
+ registerCommands({
2493
+ ctx,
2494
+ store,
2495
+ defaultMode: readDefault,
2496
+ setDefault
2497
+ }, commandCtx);
2498
+ });
2499
+ ctx.on("agent/pre-step", async (payload, next) => {
2500
+ const deactivated = containsDeactivation(payload.messages);
2501
+ if (deactivated) store.set(String(payload.agent.id), "off");
2502
+ const decision = await next();
2503
+ if (deactivated && decision.kind === "enter") return {
2504
+ kind: "enter",
2505
+ messages: [...decision.messages, createUserMessage({
2506
+ content: [{
2507
+ type: "text",
2508
+ text: "PONYTAIL MODE OFF"
2509
+ }],
2510
+ source: {
2511
+ kind: "plugin",
2512
+ plugin: name
2513
+ }
2514
+ })]
2515
+ };
2516
+ return decision;
2517
+ });
2518
+ }
2519
+ //#endregion
2520
+ export { apply, containsDeactivation, inject, messageText, name };