@systemfsoftware/omp-agent-discipline 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.js +953 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ryan Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region ../../../node_modules/.pnpm/@jsr+std__collections@1.3.0/node_modules/@jsr/std__collections/deep_merge.js
|
|
4
|
+
/** Default merging options - cached to avoid object allocation on each call */ const DEFAULT_OPTIONS = {
|
|
5
|
+
arrays: "merge",
|
|
6
|
+
sets: "merge",
|
|
7
|
+
maps: "merge"
|
|
8
|
+
};
|
|
9
|
+
function deepMerge(record, other, options) {
|
|
10
|
+
return deepMergeInternal(record, other, /* @__PURE__ */ new Set(), options ?? DEFAULT_OPTIONS);
|
|
11
|
+
}
|
|
12
|
+
function deepMergeInternal(record, other, seen, options) {
|
|
13
|
+
const result = {};
|
|
14
|
+
const keys = /* @__PURE__ */ new Set([...getKeys(record), ...getKeys(other)]);
|
|
15
|
+
for (const key of keys) {
|
|
16
|
+
if (key === "__proto__") continue;
|
|
17
|
+
const a = record[key];
|
|
18
|
+
if (!Object.hasOwn(other, key)) {
|
|
19
|
+
result[key] = a;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const b = other[key];
|
|
23
|
+
if (isNonNullObject(a) && isNonNullObject(b) && !seen.has(a) && !seen.has(b)) {
|
|
24
|
+
seen.add(a);
|
|
25
|
+
seen.add(b);
|
|
26
|
+
result[key] = mergeObjects(a, b, seen, options);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
result[key] = b;
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function mergeObjects(left, right, seen, options) {
|
|
34
|
+
if (isMergeable(left) && isMergeable(right)) return deepMergeInternal(left, right, seen, options);
|
|
35
|
+
if (isIterable(left) && isIterable(right)) {
|
|
36
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
37
|
+
if (options.arrays === "merge") return left.concat(right);
|
|
38
|
+
return right;
|
|
39
|
+
}
|
|
40
|
+
if (left instanceof Map && right instanceof Map) {
|
|
41
|
+
if (options.maps === "merge") {
|
|
42
|
+
const result = new Map(left);
|
|
43
|
+
for (const [k, v] of right) result.set(k, v);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
return right;
|
|
47
|
+
}
|
|
48
|
+
if (left instanceof Set && right instanceof Set) {
|
|
49
|
+
if (options.sets === "merge") {
|
|
50
|
+
const result = new Set(left);
|
|
51
|
+
for (const v of right) result.add(v);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
return right;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return right;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Test whether a value is mergeable or not
|
|
61
|
+
* Builtins that look like objects, null and user defined classes
|
|
62
|
+
* are not considered mergeable (it means that reference will be copied)
|
|
63
|
+
*/ function isMergeable(value) {
|
|
64
|
+
return Object.getPrototypeOf(value) === Object.prototype;
|
|
65
|
+
}
|
|
66
|
+
function isIterable(value) {
|
|
67
|
+
return typeof value[Symbol.iterator] === "function";
|
|
68
|
+
}
|
|
69
|
+
function isNonNullObject(value) {
|
|
70
|
+
return value !== null && typeof value === "object";
|
|
71
|
+
}
|
|
72
|
+
function getKeys(record) {
|
|
73
|
+
const keys = Object.keys(record);
|
|
74
|
+
const symbols = Object.getOwnPropertySymbols(record);
|
|
75
|
+
if (symbols.length === 0) return keys;
|
|
76
|
+
for (const sym of symbols) if (Object.prototype.propertyIsEnumerable.call(record, sym)) keys.push(sym);
|
|
77
|
+
return keys;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region ../../../node_modules/.pnpm/@jsr+std__toml@1.0.11/node_modules/@jsr/std__toml/_parser.js
|
|
81
|
+
/**
|
|
82
|
+
* Copy of `import { isLeap } from "@std/datetime";` because it cannot be impoted as long as it is unstable.
|
|
83
|
+
*/ function isLeap(yearNumber) {
|
|
84
|
+
return yearNumber % 4 === 0 && yearNumber % 100 !== 0 || yearNumber % 400 === 0;
|
|
85
|
+
}
|
|
86
|
+
var Scanner = class {
|
|
87
|
+
#whitespace = /[ \t]/;
|
|
88
|
+
#position = 0;
|
|
89
|
+
#source;
|
|
90
|
+
constructor(source) {
|
|
91
|
+
this.#source = source;
|
|
92
|
+
}
|
|
93
|
+
get position() {
|
|
94
|
+
return this.#position;
|
|
95
|
+
}
|
|
96
|
+
get source() {
|
|
97
|
+
return this.#source;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get current character
|
|
101
|
+
* @param index - relative index from current position
|
|
102
|
+
*/ char(index = 0) {
|
|
103
|
+
return this.#source[this.#position + index] ?? "";
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get sliced string
|
|
107
|
+
* @param start - start position relative from current position
|
|
108
|
+
* @param end - end position relative from current position
|
|
109
|
+
*/ slice(start, end) {
|
|
110
|
+
return this.#source.slice(this.#position + start, this.#position + end);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Move position to next
|
|
114
|
+
*/ next(count = 1) {
|
|
115
|
+
this.#position += count;
|
|
116
|
+
}
|
|
117
|
+
skipWhitespaces() {
|
|
118
|
+
while (this.#whitespace.test(this.char()) && !this.eof()) this.next();
|
|
119
|
+
if (!this.isCurrentCharEOL() && /\s/.test(this.char())) {
|
|
120
|
+
const escaped = "\\u" + this.char().charCodeAt(0).toString(16);
|
|
121
|
+
const position = this.#position;
|
|
122
|
+
throw new SyntaxError(`Cannot parse the TOML: It contains invalid whitespace at position '${position}': \`${escaped}\``);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
nextUntilChar(options = { skipComments: true }) {
|
|
126
|
+
while (!this.eof()) {
|
|
127
|
+
const char = this.char();
|
|
128
|
+
if (this.#whitespace.test(char) || this.isCurrentCharEOL()) this.next();
|
|
129
|
+
else if (options.skipComments && this.char() === "#") while (!this.isCurrentCharEOL() && !this.eof()) this.next();
|
|
130
|
+
else break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Position reached EOF or not
|
|
135
|
+
*/ eof() {
|
|
136
|
+
return this.#position >= this.#source.length;
|
|
137
|
+
}
|
|
138
|
+
isCurrentCharEOL() {
|
|
139
|
+
return this.char() === "\n" || this.startsWith("\r\n");
|
|
140
|
+
}
|
|
141
|
+
startsWith(searchString) {
|
|
142
|
+
return this.#source.startsWith(searchString, this.#position);
|
|
143
|
+
}
|
|
144
|
+
match(regExp) {
|
|
145
|
+
if (!regExp.sticky) throw new Error(`RegExp ${regExp} does not have a sticky 'y' flag`);
|
|
146
|
+
regExp.lastIndex = this.#position;
|
|
147
|
+
return this.#source.match(regExp);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function success(body) {
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
body
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function failure() {
|
|
157
|
+
return { ok: false };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Creates a nested object from the keys and values.
|
|
161
|
+
*
|
|
162
|
+
* e.g. `unflat(["a", "b", "c"], 1)` returns `{ a: { b: { c: 1 } } }`
|
|
163
|
+
*/ function unflat(keys, values = { __proto__: null }) {
|
|
164
|
+
return keys.reduceRight((acc, key) => ({ [key]: acc }), values);
|
|
165
|
+
}
|
|
166
|
+
function isObject(value) {
|
|
167
|
+
return typeof value === "object" && value !== null;
|
|
168
|
+
}
|
|
169
|
+
function getTargetValue(target, keys) {
|
|
170
|
+
const key = keys[0];
|
|
171
|
+
if (!key) throw new Error("Cannot parse the TOML: key length is not a positive number");
|
|
172
|
+
return target[key];
|
|
173
|
+
}
|
|
174
|
+
function deepAssignTable(target, table) {
|
|
175
|
+
const { keys, type, value } = table;
|
|
176
|
+
const currentValue = getTargetValue(target, keys);
|
|
177
|
+
if (currentValue === void 0) return Object.assign(target, unflat(keys, value));
|
|
178
|
+
if (Array.isArray(currentValue)) {
|
|
179
|
+
deepAssign(currentValue.at(-1), {
|
|
180
|
+
type,
|
|
181
|
+
keys: keys.slice(1),
|
|
182
|
+
value
|
|
183
|
+
});
|
|
184
|
+
return target;
|
|
185
|
+
}
|
|
186
|
+
if (isObject(currentValue)) {
|
|
187
|
+
deepAssign(currentValue, {
|
|
188
|
+
type,
|
|
189
|
+
keys: keys.slice(1),
|
|
190
|
+
value
|
|
191
|
+
});
|
|
192
|
+
return target;
|
|
193
|
+
}
|
|
194
|
+
throw new Error("Unexpected assign");
|
|
195
|
+
}
|
|
196
|
+
function deepAssignTableArray(target, table) {
|
|
197
|
+
const { type, keys, value } = table;
|
|
198
|
+
const currentValue = getTargetValue(target, keys);
|
|
199
|
+
if (currentValue === void 0) return Object.assign(target, unflat(keys, [value]));
|
|
200
|
+
if (Array.isArray(currentValue)) {
|
|
201
|
+
if (table.keys.length === 1) currentValue.push(value);
|
|
202
|
+
else deepAssign(currentValue.at(-1), {
|
|
203
|
+
type: table.type,
|
|
204
|
+
keys: table.keys.slice(1),
|
|
205
|
+
value: table.value
|
|
206
|
+
});
|
|
207
|
+
return target;
|
|
208
|
+
}
|
|
209
|
+
if (isObject(currentValue)) {
|
|
210
|
+
deepAssign(currentValue, {
|
|
211
|
+
type,
|
|
212
|
+
keys: keys.slice(1),
|
|
213
|
+
value
|
|
214
|
+
});
|
|
215
|
+
return target;
|
|
216
|
+
}
|
|
217
|
+
throw new Error("Unexpected assign");
|
|
218
|
+
}
|
|
219
|
+
function deepAssign(target, body) {
|
|
220
|
+
switch (body.type) {
|
|
221
|
+
case "Block": return deepMerge(target, body.value);
|
|
222
|
+
case "Table": return deepAssignTable(target, body);
|
|
223
|
+
case "TableArray": return deepAssignTableArray(target, body);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function or(parsers) {
|
|
227
|
+
return (scanner) => {
|
|
228
|
+
for (const parse of parsers) {
|
|
229
|
+
const result = parse(scanner);
|
|
230
|
+
if (result.ok) return result;
|
|
231
|
+
}
|
|
232
|
+
return failure();
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** Join the parse results of the given parser into an array.
|
|
236
|
+
*
|
|
237
|
+
* If the parser fails at the first attempt, it will return an empty array.
|
|
238
|
+
*/ function join$1(parser, separator) {
|
|
239
|
+
const Separator = character(separator);
|
|
240
|
+
return (scanner) => {
|
|
241
|
+
const out = [];
|
|
242
|
+
const first = parser(scanner);
|
|
243
|
+
if (!first.ok) return success(out);
|
|
244
|
+
out.push(first.body);
|
|
245
|
+
while (!scanner.eof()) {
|
|
246
|
+
if (!Separator(scanner).ok) break;
|
|
247
|
+
const result = parser(scanner);
|
|
248
|
+
if (!result.ok) throw new SyntaxError(`Invalid token after "${separator}"`);
|
|
249
|
+
out.push(result.body);
|
|
250
|
+
}
|
|
251
|
+
return success(out);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** Join the parse results of the given parser into an array.
|
|
255
|
+
*
|
|
256
|
+
* This requires the parser to succeed at least once.
|
|
257
|
+
*/ function join1(parser, separator) {
|
|
258
|
+
const Separator = character(separator);
|
|
259
|
+
return (scanner) => {
|
|
260
|
+
const first = parser(scanner);
|
|
261
|
+
if (!first.ok) return failure();
|
|
262
|
+
const out = [first.body];
|
|
263
|
+
while (!scanner.eof()) {
|
|
264
|
+
if (!Separator(scanner).ok) break;
|
|
265
|
+
const result = parser(scanner);
|
|
266
|
+
if (!result.ok) throw new SyntaxError(`Invalid token after "${separator}"`);
|
|
267
|
+
out.push(result.body);
|
|
268
|
+
}
|
|
269
|
+
return success(out);
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function kv(keyParser, separator, valueParser) {
|
|
273
|
+
const Separator = character(separator);
|
|
274
|
+
return (scanner) => {
|
|
275
|
+
const position = scanner.position;
|
|
276
|
+
const key = keyParser(scanner);
|
|
277
|
+
if (!key.ok) return failure();
|
|
278
|
+
if (!Separator(scanner).ok) throw new SyntaxError(`key/value pair doesn't have "${separator}"`);
|
|
279
|
+
const value = valueParser(scanner);
|
|
280
|
+
if (!value.ok) {
|
|
281
|
+
const lineEndIndex = scanner.source.indexOf("\n", scanner.position);
|
|
282
|
+
const endPosition = lineEndIndex > 0 ? lineEndIndex : scanner.source.length;
|
|
283
|
+
const line = scanner.source.slice(position, endPosition);
|
|
284
|
+
throw new SyntaxError(`Cannot parse value on line '${line}'`);
|
|
285
|
+
}
|
|
286
|
+
return success(unflat(key.body, value.body));
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function merge(parser) {
|
|
290
|
+
return (scanner) => {
|
|
291
|
+
const result = parser(scanner);
|
|
292
|
+
if (!result.ok) return failure();
|
|
293
|
+
let body = { __proto__: null };
|
|
294
|
+
for (const record of result.body) if (typeof record === "object" && record !== null) body = deepMerge(body, record);
|
|
295
|
+
return success(body);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function repeat(parser) {
|
|
299
|
+
return (scanner) => {
|
|
300
|
+
const body = [];
|
|
301
|
+
while (!scanner.eof()) {
|
|
302
|
+
const result = parser(scanner);
|
|
303
|
+
if (!result.ok) break;
|
|
304
|
+
body.push(result.body);
|
|
305
|
+
scanner.nextUntilChar();
|
|
306
|
+
}
|
|
307
|
+
if (body.length === 0) return failure();
|
|
308
|
+
return success(body);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function surround(left, parser, right) {
|
|
312
|
+
const Left = character(left);
|
|
313
|
+
const Right = character(right);
|
|
314
|
+
return (scanner) => {
|
|
315
|
+
if (!Left(scanner).ok) return failure();
|
|
316
|
+
const result = parser(scanner);
|
|
317
|
+
if (!result.ok) throw new SyntaxError(`Invalid token after "${left}"`);
|
|
318
|
+
if (!Right(scanner).ok) throw new SyntaxError(`Not closed by "${right}" after started with "${left}"`);
|
|
319
|
+
return success(result.body);
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function character(str) {
|
|
323
|
+
return (scanner) => {
|
|
324
|
+
scanner.skipWhitespaces();
|
|
325
|
+
if (!scanner.startsWith(str)) return failure();
|
|
326
|
+
scanner.next(str.length);
|
|
327
|
+
scanner.skipWhitespaces();
|
|
328
|
+
return success(void 0);
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const BARE_KEY_REGEXP = /[A-Za-z0-9_-]+/y;
|
|
332
|
+
function bareKey(scanner) {
|
|
333
|
+
scanner.skipWhitespaces();
|
|
334
|
+
const key = scanner.match(BARE_KEY_REGEXP)?.[0];
|
|
335
|
+
if (!key) return failure();
|
|
336
|
+
scanner.next(key.length);
|
|
337
|
+
return success(key);
|
|
338
|
+
}
|
|
339
|
+
function escapeSequence(scanner) {
|
|
340
|
+
if (scanner.char() !== "\\") return failure();
|
|
341
|
+
scanner.next();
|
|
342
|
+
switch (scanner.char()) {
|
|
343
|
+
case "b":
|
|
344
|
+
scanner.next();
|
|
345
|
+
return success("\b");
|
|
346
|
+
case "t":
|
|
347
|
+
scanner.next();
|
|
348
|
+
return success(" ");
|
|
349
|
+
case "n":
|
|
350
|
+
scanner.next();
|
|
351
|
+
return success("\n");
|
|
352
|
+
case "f":
|
|
353
|
+
scanner.next();
|
|
354
|
+
return success("\f");
|
|
355
|
+
case "r":
|
|
356
|
+
scanner.next();
|
|
357
|
+
return success("\r");
|
|
358
|
+
case "u":
|
|
359
|
+
case "U": {
|
|
360
|
+
const codePointLen = scanner.char() === "u" ? 4 : 6;
|
|
361
|
+
const codePoint = parseInt("0x" + scanner.slice(1, 1 + codePointLen), 16);
|
|
362
|
+
const str = String.fromCodePoint(codePoint);
|
|
363
|
+
scanner.next(codePointLen + 1);
|
|
364
|
+
return success(str);
|
|
365
|
+
}
|
|
366
|
+
case "\"":
|
|
367
|
+
scanner.next();
|
|
368
|
+
return success("\"");
|
|
369
|
+
case "\\":
|
|
370
|
+
scanner.next();
|
|
371
|
+
return success("\\");
|
|
372
|
+
default: throw new SyntaxError(`Invalid escape sequence: \\${scanner.char()}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function basicString(scanner) {
|
|
376
|
+
scanner.skipWhitespaces();
|
|
377
|
+
if (scanner.char() !== "\"") return failure();
|
|
378
|
+
scanner.next();
|
|
379
|
+
const acc = [];
|
|
380
|
+
while (scanner.char() !== "\"" && !scanner.eof()) {
|
|
381
|
+
if (scanner.char() === "\n") throw new SyntaxError("Single-line string cannot contain EOL");
|
|
382
|
+
const escapedChar = escapeSequence(scanner);
|
|
383
|
+
if (escapedChar.ok) acc.push(escapedChar.body);
|
|
384
|
+
else {
|
|
385
|
+
acc.push(scanner.char());
|
|
386
|
+
scanner.next();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (scanner.eof()) throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`);
|
|
390
|
+
scanner.next();
|
|
391
|
+
return success(acc.join(""));
|
|
392
|
+
}
|
|
393
|
+
function literalString(scanner) {
|
|
394
|
+
scanner.skipWhitespaces();
|
|
395
|
+
if (scanner.char() !== "'") return failure();
|
|
396
|
+
scanner.next();
|
|
397
|
+
const acc = [];
|
|
398
|
+
while (scanner.char() !== "'" && !scanner.eof()) {
|
|
399
|
+
if (scanner.char() === "\n") throw new SyntaxError("Single-line string cannot contain EOL");
|
|
400
|
+
acc.push(scanner.char());
|
|
401
|
+
scanner.next();
|
|
402
|
+
}
|
|
403
|
+
if (scanner.eof()) throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`);
|
|
404
|
+
scanner.next();
|
|
405
|
+
return success(acc.join(""));
|
|
406
|
+
}
|
|
407
|
+
function multilineBasicString(scanner) {
|
|
408
|
+
scanner.skipWhitespaces();
|
|
409
|
+
if (!scanner.startsWith("\"\"\"")) return failure();
|
|
410
|
+
scanner.next(3);
|
|
411
|
+
if (scanner.char() === "\n") scanner.next();
|
|
412
|
+
else if (scanner.startsWith("\r\n")) scanner.next(2);
|
|
413
|
+
const acc = [];
|
|
414
|
+
while (!scanner.startsWith("\"\"\"") && !scanner.eof()) {
|
|
415
|
+
if (scanner.startsWith("\\\n")) {
|
|
416
|
+
scanner.next();
|
|
417
|
+
scanner.nextUntilChar({ skipComments: false });
|
|
418
|
+
continue;
|
|
419
|
+
} else if (scanner.startsWith("\\\r\n")) {
|
|
420
|
+
scanner.next();
|
|
421
|
+
scanner.nextUntilChar({ skipComments: false });
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const escapedChar = escapeSequence(scanner);
|
|
425
|
+
if (escapedChar.ok) acc.push(escapedChar.body);
|
|
426
|
+
else {
|
|
427
|
+
acc.push(scanner.char());
|
|
428
|
+
scanner.next();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (scanner.eof()) throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`);
|
|
432
|
+
if (scanner.char(3) === "\"") {
|
|
433
|
+
acc.push("\"");
|
|
434
|
+
scanner.next();
|
|
435
|
+
}
|
|
436
|
+
scanner.next(3);
|
|
437
|
+
return success(acc.join(""));
|
|
438
|
+
}
|
|
439
|
+
function multilineLiteralString(scanner) {
|
|
440
|
+
scanner.skipWhitespaces();
|
|
441
|
+
if (!scanner.startsWith("'''")) return failure();
|
|
442
|
+
scanner.next(3);
|
|
443
|
+
if (scanner.char() === "\n") scanner.next();
|
|
444
|
+
else if (scanner.startsWith("\r\n")) scanner.next(2);
|
|
445
|
+
const acc = [];
|
|
446
|
+
while (!scanner.startsWith("'''") && !scanner.eof()) {
|
|
447
|
+
acc.push(scanner.char());
|
|
448
|
+
scanner.next();
|
|
449
|
+
}
|
|
450
|
+
if (scanner.eof()) throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`);
|
|
451
|
+
if (scanner.char(3) === "'") {
|
|
452
|
+
acc.push("'");
|
|
453
|
+
scanner.next();
|
|
454
|
+
}
|
|
455
|
+
scanner.next(3);
|
|
456
|
+
return success(acc.join(""));
|
|
457
|
+
}
|
|
458
|
+
const BOOLEAN_REGEXP = /(?:true|false)\b/y;
|
|
459
|
+
function boolean(scanner) {
|
|
460
|
+
scanner.skipWhitespaces();
|
|
461
|
+
const match = scanner.match(BOOLEAN_REGEXP);
|
|
462
|
+
if (!match) return failure();
|
|
463
|
+
const string = match[0];
|
|
464
|
+
scanner.next(string.length);
|
|
465
|
+
return success(string === "true");
|
|
466
|
+
}
|
|
467
|
+
const INFINITY_MAP = /* @__PURE__ */ new Map([
|
|
468
|
+
["inf", Infinity],
|
|
469
|
+
["+inf", Infinity],
|
|
470
|
+
["-inf", -Infinity]
|
|
471
|
+
]);
|
|
472
|
+
const INFINITY_REGEXP = /[+-]?inf\b/y;
|
|
473
|
+
function infinity(scanner) {
|
|
474
|
+
scanner.skipWhitespaces();
|
|
475
|
+
const match = scanner.match(INFINITY_REGEXP);
|
|
476
|
+
if (!match) return failure();
|
|
477
|
+
const string = match[0];
|
|
478
|
+
scanner.next(string.length);
|
|
479
|
+
return success(INFINITY_MAP.get(string));
|
|
480
|
+
}
|
|
481
|
+
const NAN_REGEXP = /[+-]?nan\b/y;
|
|
482
|
+
function nan(scanner) {
|
|
483
|
+
scanner.skipWhitespaces();
|
|
484
|
+
const match = scanner.match(NAN_REGEXP);
|
|
485
|
+
if (!match) return failure();
|
|
486
|
+
const string = match[0];
|
|
487
|
+
scanner.next(string.length);
|
|
488
|
+
return success(NaN);
|
|
489
|
+
}
|
|
490
|
+
const dottedKey = join1(or([
|
|
491
|
+
bareKey,
|
|
492
|
+
basicString,
|
|
493
|
+
literalString
|
|
494
|
+
]), ".");
|
|
495
|
+
const BINARY_REGEXP = /0b[01]+(?:_[01]+)*\b/y;
|
|
496
|
+
function binary(scanner) {
|
|
497
|
+
scanner.skipWhitespaces();
|
|
498
|
+
const match = scanner.match(BINARY_REGEXP)?.[0];
|
|
499
|
+
if (!match) return failure();
|
|
500
|
+
scanner.next(match.length);
|
|
501
|
+
const value = match.slice(2).replaceAll("_", "");
|
|
502
|
+
const number = parseInt(value, 2);
|
|
503
|
+
return isNaN(number) ? failure() : success(number);
|
|
504
|
+
}
|
|
505
|
+
const OCTAL_REGEXP = /0o[0-7]+(?:_[0-7]+)*\b/y;
|
|
506
|
+
function octal(scanner) {
|
|
507
|
+
scanner.skipWhitespaces();
|
|
508
|
+
const match = scanner.match(OCTAL_REGEXP)?.[0];
|
|
509
|
+
if (!match) return failure();
|
|
510
|
+
scanner.next(match.length);
|
|
511
|
+
const value = match.slice(2).replaceAll("_", "");
|
|
512
|
+
const number = parseInt(value, 8);
|
|
513
|
+
return isNaN(number) ? failure() : success(number);
|
|
514
|
+
}
|
|
515
|
+
const HEX_REGEXP = /0x[0-9a-f]+(?:_[0-9a-f]+)*\b/iy;
|
|
516
|
+
function hex(scanner) {
|
|
517
|
+
scanner.skipWhitespaces();
|
|
518
|
+
const match = scanner.match(HEX_REGEXP)?.[0];
|
|
519
|
+
if (!match) return failure();
|
|
520
|
+
scanner.next(match.length);
|
|
521
|
+
const value = match.slice(2).replaceAll("_", "");
|
|
522
|
+
const number = parseInt(value, 16);
|
|
523
|
+
return isNaN(number) ? failure() : success(number);
|
|
524
|
+
}
|
|
525
|
+
const INTEGER_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)\b/y;
|
|
526
|
+
function integer(scanner) {
|
|
527
|
+
scanner.skipWhitespaces();
|
|
528
|
+
const match = scanner.match(INTEGER_REGEXP)?.[0];
|
|
529
|
+
if (!match) return failure();
|
|
530
|
+
scanner.next(match.length);
|
|
531
|
+
const value = match.replaceAll("_", "");
|
|
532
|
+
return success(parseInt(value, 10));
|
|
533
|
+
}
|
|
534
|
+
const FLOAT_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)(?:\.[0-9]+(?:_[0-9]+)*)?(?:e[+-]?[0-9]+(?:_[0-9]+)*)?\b/iy;
|
|
535
|
+
function float(scanner) {
|
|
536
|
+
scanner.skipWhitespaces();
|
|
537
|
+
const match = scanner.match(FLOAT_REGEXP)?.[0];
|
|
538
|
+
if (!match) return failure();
|
|
539
|
+
scanner.next(match.length);
|
|
540
|
+
const value = match.replaceAll("_", "");
|
|
541
|
+
const float = parseFloat(value);
|
|
542
|
+
if (isNaN(float)) return failure();
|
|
543
|
+
return success(float);
|
|
544
|
+
}
|
|
545
|
+
const DATE_TIME_REGEXP = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?:[ 0-9TZ.:+-]+)?\b/y;
|
|
546
|
+
function dateTime(scanner) {
|
|
547
|
+
scanner.skipWhitespaces();
|
|
548
|
+
const match = scanner.match(DATE_TIME_REGEXP);
|
|
549
|
+
if (!match) return failure();
|
|
550
|
+
const string = match[0];
|
|
551
|
+
scanner.next(string.length);
|
|
552
|
+
const groups = match.groups;
|
|
553
|
+
if (groups.month == "02") {
|
|
554
|
+
const days = parseInt(groups.day);
|
|
555
|
+
if (days > 29) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
556
|
+
const year = parseInt(groups.year);
|
|
557
|
+
if (days > 28 && !isLeap(year)) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
558
|
+
}
|
|
559
|
+
const date = new Date(string.trim());
|
|
560
|
+
if (isNaN(date.getTime())) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
561
|
+
return success(date);
|
|
562
|
+
}
|
|
563
|
+
const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?\b/y;
|
|
564
|
+
function localTime(scanner) {
|
|
565
|
+
scanner.skipWhitespaces();
|
|
566
|
+
const match = scanner.match(LOCAL_TIME_REGEXP)?.[0];
|
|
567
|
+
if (!match) return failure();
|
|
568
|
+
scanner.next(match.length);
|
|
569
|
+
return success(match);
|
|
570
|
+
}
|
|
571
|
+
function arrayValue(scanner) {
|
|
572
|
+
scanner.skipWhitespaces();
|
|
573
|
+
if (scanner.char() !== "[") return failure();
|
|
574
|
+
scanner.next();
|
|
575
|
+
const array = [];
|
|
576
|
+
while (!scanner.eof()) {
|
|
577
|
+
scanner.nextUntilChar();
|
|
578
|
+
const result = value(scanner);
|
|
579
|
+
if (!result.ok) break;
|
|
580
|
+
array.push(result.body);
|
|
581
|
+
scanner.skipWhitespaces();
|
|
582
|
+
if (scanner.char() !== ",") break;
|
|
583
|
+
scanner.next();
|
|
584
|
+
}
|
|
585
|
+
scanner.nextUntilChar();
|
|
586
|
+
if (scanner.char() !== "]") throw new SyntaxError("Array is not closed");
|
|
587
|
+
scanner.next();
|
|
588
|
+
return success(array);
|
|
589
|
+
}
|
|
590
|
+
function inlineTable(scanner) {
|
|
591
|
+
scanner.nextUntilChar();
|
|
592
|
+
if (scanner.char(1) === "}") {
|
|
593
|
+
scanner.next(2);
|
|
594
|
+
return success({ __proto__: null });
|
|
595
|
+
}
|
|
596
|
+
const pairs = surround("{", join$1(pair, ","), "}")(scanner);
|
|
597
|
+
if (!pairs.ok) return failure();
|
|
598
|
+
let table = { __proto__: null };
|
|
599
|
+
for (const pair of pairs.body) table = deepMerge(table, pair);
|
|
600
|
+
return success(table);
|
|
601
|
+
}
|
|
602
|
+
const value = or([
|
|
603
|
+
multilineBasicString,
|
|
604
|
+
multilineLiteralString,
|
|
605
|
+
basicString,
|
|
606
|
+
literalString,
|
|
607
|
+
boolean,
|
|
608
|
+
infinity,
|
|
609
|
+
nan,
|
|
610
|
+
dateTime,
|
|
611
|
+
localTime,
|
|
612
|
+
binary,
|
|
613
|
+
octal,
|
|
614
|
+
hex,
|
|
615
|
+
float,
|
|
616
|
+
integer,
|
|
617
|
+
arrayValue,
|
|
618
|
+
inlineTable
|
|
619
|
+
]);
|
|
620
|
+
const pair = kv(dottedKey, "=", value);
|
|
621
|
+
function block(scanner) {
|
|
622
|
+
scanner.nextUntilChar();
|
|
623
|
+
const result = merge(repeat(pair))(scanner);
|
|
624
|
+
if (result.ok) return success({
|
|
625
|
+
type: "Block",
|
|
626
|
+
value: result.body
|
|
627
|
+
});
|
|
628
|
+
return failure();
|
|
629
|
+
}
|
|
630
|
+
const tableHeader = surround("[", dottedKey, "]");
|
|
631
|
+
function table(scanner) {
|
|
632
|
+
scanner.nextUntilChar();
|
|
633
|
+
const header = tableHeader(scanner);
|
|
634
|
+
if (!header.ok) return failure();
|
|
635
|
+
scanner.nextUntilChar();
|
|
636
|
+
const b = block(scanner);
|
|
637
|
+
return success({
|
|
638
|
+
type: "Table",
|
|
639
|
+
keys: header.body,
|
|
640
|
+
value: b.ok ? b.body.value : { __proto__: null }
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
const tableArrayHeader = surround("[[", dottedKey, "]]");
|
|
644
|
+
function tableArray(scanner) {
|
|
645
|
+
scanner.nextUntilChar();
|
|
646
|
+
const header = tableArrayHeader(scanner);
|
|
647
|
+
if (!header.ok) return failure();
|
|
648
|
+
scanner.nextUntilChar();
|
|
649
|
+
const b = block(scanner);
|
|
650
|
+
return success({
|
|
651
|
+
type: "TableArray",
|
|
652
|
+
keys: header.body,
|
|
653
|
+
value: b.ok ? b.body.value : { __proto__: null }
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function toml(scanner) {
|
|
657
|
+
const blocks = repeat(or([
|
|
658
|
+
block,
|
|
659
|
+
tableArray,
|
|
660
|
+
table
|
|
661
|
+
]))(scanner);
|
|
662
|
+
if (!blocks.ok) return success({ __proto__: null });
|
|
663
|
+
return success(blocks.body.reduce(deepAssign, { __proto__: null }));
|
|
664
|
+
}
|
|
665
|
+
function createParseErrorMessage(scanner, message) {
|
|
666
|
+
const lines = scanner.source.slice(0, scanner.position).split("\n");
|
|
667
|
+
return `Parse error on line ${lines.length}, column ${lines.at(-1)?.length ?? 0}: ${message}`;
|
|
668
|
+
}
|
|
669
|
+
function parserFactory(parser) {
|
|
670
|
+
return (tomlString) => {
|
|
671
|
+
const scanner = new Scanner(tomlString);
|
|
672
|
+
try {
|
|
673
|
+
const result = parser(scanner);
|
|
674
|
+
if (result.ok && scanner.eof()) return result.body;
|
|
675
|
+
const message = `Unexpected character: "${scanner.char()}"`;
|
|
676
|
+
throw new SyntaxError(createParseErrorMessage(scanner, message));
|
|
677
|
+
} catch (error) {
|
|
678
|
+
if (error instanceof Error) throw new SyntaxError(createParseErrorMessage(scanner, error.message));
|
|
679
|
+
throw new SyntaxError(createParseErrorMessage(scanner, "Invalid error type caught"));
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region ../../../node_modules/.pnpm/@jsr+std__toml@1.0.11/node_modules/@jsr/std__toml/parse.js
|
|
685
|
+
/**
|
|
686
|
+
* Parses a {@link https://toml.io | TOML} string into an object.
|
|
687
|
+
*
|
|
688
|
+
* @example Usage
|
|
689
|
+
* ```ts
|
|
690
|
+
* import { parse } from "@std/toml/parse";
|
|
691
|
+
* import { assertEquals } from "@std/assert";
|
|
692
|
+
*
|
|
693
|
+
* const tomlString = `title = "TOML Example"
|
|
694
|
+
* [owner]
|
|
695
|
+
* name = "Alice"
|
|
696
|
+
* bio = "Alice is a programmer."`;
|
|
697
|
+
*
|
|
698
|
+
* const obj = parse(tomlString);
|
|
699
|
+
* assertEquals(obj, { title: "TOML Example", owner: { name: "Alice", bio: "Alice is a programmer." } });
|
|
700
|
+
* ```
|
|
701
|
+
* @param tomlString TOML string to be parsed.
|
|
702
|
+
* @returns The parsed JS object.
|
|
703
|
+
*/ function parse(tomlString) {
|
|
704
|
+
return parserFactory(toml)(tomlString);
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region ../omp-utils/dist/index.js
|
|
708
|
+
/**
|
|
709
|
+
* Create a telemetry emitter for a plugin.
|
|
710
|
+
*
|
|
711
|
+
* @param plugin - The event prefix (e.g. `claude_compat`, `agent_discipline`).
|
|
712
|
+
* @param logger - The `pi.logger` instance injected by the host at factory time.
|
|
713
|
+
*/
|
|
714
|
+
function createTelemetry(plugin, logger) {
|
|
715
|
+
return (eventName, fields) => {
|
|
716
|
+
try {
|
|
717
|
+
logger.info(eventName, {
|
|
718
|
+
plugin,
|
|
719
|
+
event: eventName,
|
|
720
|
+
...fields
|
|
721
|
+
});
|
|
722
|
+
} catch {}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* `systemfsoftware.toml` loader — unified config for all systemfsoftware OMP extensions.
|
|
727
|
+
*
|
|
728
|
+
* Parsed with @std/toml. Cached per cwd. Missing file → `{}` (no config is fine).
|
|
729
|
+
* Malformed TOML → fail open (`{}`) + one warn via the injected logger (never throws:
|
|
730
|
+
* a config typo must not freeze extension behavior).
|
|
731
|
+
*/
|
|
732
|
+
const CONFIG_FILE = "systemfsoftware.toml";
|
|
733
|
+
const cache = /* @__PURE__ */ new Map();
|
|
734
|
+
const warnedFiles = /* @__PURE__ */ new Set();
|
|
735
|
+
/**
|
|
736
|
+
* Load `systemfsoftware.toml` from `cwd`. Cached per cwd.
|
|
737
|
+
* `warn` receives at most one message per malformed file (defaults to a no-op).
|
|
738
|
+
*/
|
|
739
|
+
function loadToml(cwd, warn = () => {}) {
|
|
740
|
+
const cached = cache.get(cwd);
|
|
741
|
+
if (cached !== void 0) return cached;
|
|
742
|
+
const configPath = join(cwd, CONFIG_FILE);
|
|
743
|
+
let config = {};
|
|
744
|
+
if (existsSync(configPath)) try {
|
|
745
|
+
const parsed = parse(readFileSync(configPath, "utf-8"));
|
|
746
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) config = Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, Array.isArray(value) ? value.filter((v) => typeof v === "string") : []]));
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (!warnedFiles.has(configPath)) {
|
|
749
|
+
warnedFiles.add(configPath);
|
|
750
|
+
warn(`[toml-loader] malformed ${CONFIG_FILE} at ${configPath} — failing open (no config)`);
|
|
751
|
+
warn(error instanceof Error ? error.message : "unknown parse error");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
cache.set(cwd, config);
|
|
755
|
+
return config;
|
|
756
|
+
}
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/no-skill-delegation.ts
|
|
759
|
+
/** Module-scoped telemetry emitter, initialized in the default export. */
|
|
760
|
+
let tel$1 = () => {};
|
|
761
|
+
const compiledCache = /* @__PURE__ */ new Map();
|
|
762
|
+
function escapeRegex(value) {
|
|
763
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
764
|
+
}
|
|
765
|
+
function compileGuard(names) {
|
|
766
|
+
if (names.length === 0) return null;
|
|
767
|
+
const nameGroup = "(?:" + names.map(escapeRegex).join("|") + ")";
|
|
768
|
+
return {
|
|
769
|
+
protectedSkills: new Set(names),
|
|
770
|
+
delegationVerbs: [
|
|
771
|
+
new RegExp("\\binvoke\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
772
|
+
new RegExp("\\bdispatch\\s+(?:to\\s+)?(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
773
|
+
new RegExp("\\bwrap\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\s+in\\s+(?:a\\s+)?(?:task|agent|subagent)\\b", "i"),
|
|
774
|
+
new RegExp("\\bdelegate\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
775
|
+
new RegExp("\\brun\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\s+(?:via|in)\\s+(?:a\\s+)?(?:subagent|task|agent)\\b", "i"),
|
|
776
|
+
new RegExp("\\b(?:run|execute|launch)\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
777
|
+
new RegExp("\\bskill:\\s*[`/]?" + nameGroup + "\\b", "i"),
|
|
778
|
+
new RegExp("\\bskill:\\/\\/" + nameGroup + "\\b", "i"),
|
|
779
|
+
new RegExp("(?:^|\\W)[`/]" + nameGroup + "(?=$|\\b|\\W)", "i"),
|
|
780
|
+
new RegExp("(?:^|\\W)/" + nameGroup + "(?=$|\\b|\\W)", "i"),
|
|
781
|
+
new RegExp("\\buse\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
782
|
+
new RegExp("\\bload\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
783
|
+
new RegExp("\\bspawn\\s+(?:a\\s+)?(?:task|agent|subagent|worker)\\s+(?:with|using)\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
784
|
+
new RegExp("\\bcall\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
785
|
+
new RegExp("\\bsend\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
786
|
+
new RegExp("\\bcreate\\s+(?:a\\s+)?(?:task|agent|subagent)\\s+(?:with|using)\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
787
|
+
new RegExp("\\bstart\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i")
|
|
788
|
+
],
|
|
789
|
+
referenceVerbs: [
|
|
790
|
+
new RegExp("\\bsee\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
791
|
+
new RegExp("\\bper\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
792
|
+
new RegExp("\\bread\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i"),
|
|
793
|
+
new RegExp("\\baccording\\s+to\\s+(?:the\\s+)?[`/]?" + nameGroup + "\\b", "i")
|
|
794
|
+
],
|
|
795
|
+
mentionPatterns: new Map(names.map((name) => [name, new RegExp("(?:^|[\\s/.`\"])" + escapeRegex(name) + "(?=$|[\\s/.`\"]|\\b)", "i")]))
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function getCompiledGuard(cwd) {
|
|
799
|
+
const cached = compiledCache.get(cwd);
|
|
800
|
+
if (cached !== void 0) return cached;
|
|
801
|
+
const compiled = compileGuard(loadToml(cwd)["no_delegate_skills"] ?? []);
|
|
802
|
+
if (compiled !== null) compiledCache.set(cwd, compiled);
|
|
803
|
+
return compiled;
|
|
804
|
+
}
|
|
805
|
+
function readString(input, ...keys) {
|
|
806
|
+
for (const key of keys) {
|
|
807
|
+
const value = input[key];
|
|
808
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
809
|
+
}
|
|
810
|
+
return "";
|
|
811
|
+
}
|
|
812
|
+
function denyMessage(skill, how, excerpt) {
|
|
813
|
+
return [
|
|
814
|
+
`⛔ BLOCKED: "${skill}" must not be delegated to a subagent.`,
|
|
815
|
+
`Detected in ${how}: ${excerpt}`,
|
|
816
|
+
"",
|
|
817
|
+
`REQUIRED: invoke ${skill} directly in THIS session via the host Skill / Tool call,`,
|
|
818
|
+
"then pass its return envelope to the next step. Do NOT wrap it in a task / Agent dispatch.",
|
|
819
|
+
"",
|
|
820
|
+
"WHY: a subagent reproduces the shape but loses the skill protocol — plan-path gate,",
|
|
821
|
+
"headless review contract, and pipeline-vs-chat mode. The contract does not survive the hop.",
|
|
822
|
+
"",
|
|
823
|
+
"RULE: root AGENTS.md §\"Skill invocations\" (SK1/SK2)."
|
|
824
|
+
].join("\n");
|
|
825
|
+
}
|
|
826
|
+
function noSkillDelegationExtension(pi) {
|
|
827
|
+
tel$1 = createTelemetry("agent_discipline", pi.logger);
|
|
828
|
+
pi.on("tool_call", (event, ctx) => {
|
|
829
|
+
const guard = getCompiledGuard(ctx.cwd);
|
|
830
|
+
if (guard === null) return void 0;
|
|
831
|
+
const toolName = event.toolName.toLowerCase();
|
|
832
|
+
if (toolName !== "task" && toolName !== "agent") return void 0;
|
|
833
|
+
const input = event.input;
|
|
834
|
+
const subagentType = readString(input, "subagent_type", "agent");
|
|
835
|
+
if (subagentType !== "" && guard.protectedSkills.has(subagentType)) {
|
|
836
|
+
tel$1("delegation.blocked", {
|
|
837
|
+
skill: subagentType,
|
|
838
|
+
how: "subagent_type"
|
|
839
|
+
});
|
|
840
|
+
return {
|
|
841
|
+
block: true,
|
|
842
|
+
reason: denyMessage(subagentType, "subagent_type", subagentType)
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
const prompt = readString(input, "prompt", "task", "description");
|
|
846
|
+
if (prompt !== "") {
|
|
847
|
+
const mentioned = [...guard.mentionPatterns.entries()].filter(([, pattern]) => pattern.test(prompt)).map(([name]) => name);
|
|
848
|
+
if (mentioned.length > 0 && guard.referenceVerbs.every((re) => !re.test(prompt)) && guard.delegationVerbs.some((re) => re.test(prompt))) {
|
|
849
|
+
const skill = mentioned[0];
|
|
850
|
+
const matched = guard.delegationVerbs.map((re) => re.exec(prompt)).find((m) => m !== null);
|
|
851
|
+
const excerpt = matched !== void 0 ? matched[0] : prompt.slice(0, 120);
|
|
852
|
+
tel$1("delegation.blocked", {
|
|
853
|
+
skill,
|
|
854
|
+
how: "prompt"
|
|
855
|
+
});
|
|
856
|
+
return {
|
|
857
|
+
block: true,
|
|
858
|
+
reason: denyMessage(skill, "prompt", excerpt)
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/xd-retry-guard.ts
|
|
866
|
+
const NOT_FOUND_RE = /Tool ([A-Za-z0-9_:-]+) not found/i;
|
|
867
|
+
const XD_PREFIX = "xd://";
|
|
868
|
+
const ledger = /* @__PURE__ */ new Map();
|
|
869
|
+
const LEDGER_MAX_SIZE = 50;
|
|
870
|
+
/** Module-scoped telemetry emitter, initialized in the default export. */
|
|
871
|
+
let tel = () => {};
|
|
872
|
+
function isTextBlock(value) {
|
|
873
|
+
return typeof value === "object" && value !== null && "text" in value && typeof value.text === "string";
|
|
874
|
+
}
|
|
875
|
+
function resultText(result) {
|
|
876
|
+
if (typeof result !== "object" || result === null || !("content" in result)) return "";
|
|
877
|
+
const content = result.content;
|
|
878
|
+
if (!Array.isArray(content)) return "";
|
|
879
|
+
return content.filter(isTextBlock).map((block) => block.text).join("\n");
|
|
880
|
+
}
|
|
881
|
+
function buildReminder(entries) {
|
|
882
|
+
return `<system-reminder>
|
|
883
|
+
A previous tool call failed with "Tool X not found". These tools are listed under "# xd:// Tool Devices" in the system prompt — they are NOT function-call tools. Invoking them by name fails; invoking them via the write tool to their xd:// URL executes them.
|
|
884
|
+
|
|
885
|
+
Unresolved failures:
|
|
886
|
+
${entries.map((entry) => `- "${entry.tool}": retry NOW by calling the write tool with path "xd://${entry.tool}" and content = the JSON args object (read "xd://${entry.tool}" first for its schema).`).join("\n")}
|
|
887
|
+
|
|
888
|
+
Do NOT concede unavailability and do NOT continue other work leaving these unretried. This reminder re-injects on every model call until each retry executes (a write to the xd:// URL clears its entry).
|
|
889
|
+
</system-reminder>`;
|
|
890
|
+
}
|
|
891
|
+
function xdRetryGuardExtension(pi) {
|
|
892
|
+
tel = createTelemetry("agent_discipline", pi.logger);
|
|
893
|
+
pi.on("tool_execution_end", (event, _ctx) => {
|
|
894
|
+
if (!event.isError) return;
|
|
895
|
+
const match = NOT_FOUND_RE.exec(resultText(event.result));
|
|
896
|
+
if (!match) return;
|
|
897
|
+
const tool = match[1];
|
|
898
|
+
if (tool === void 0) return;
|
|
899
|
+
const existing = ledger.get(tool);
|
|
900
|
+
if (existing) existing.failures += 1;
|
|
901
|
+
else {
|
|
902
|
+
if (ledger.size >= LEDGER_MAX_SIZE) {
|
|
903
|
+
const firstKey = ledger.keys().next().value;
|
|
904
|
+
if (firstKey !== void 0) ledger.delete(firstKey);
|
|
905
|
+
}
|
|
906
|
+
ledger.set(tool, {
|
|
907
|
+
tool,
|
|
908
|
+
failures: 1,
|
|
909
|
+
remindedAtFailure: 0
|
|
910
|
+
});
|
|
911
|
+
tel("guard.fired", {
|
|
912
|
+
tool,
|
|
913
|
+
count: ledger.size
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
pi.on("tool_execution_start", (event, _ctx) => {
|
|
918
|
+
if (event.toolName !== "write") return;
|
|
919
|
+
if (typeof event.args !== "object" || event.args === null || !("path" in event.args)) return;
|
|
920
|
+
const path = event.args.path;
|
|
921
|
+
if (typeof path !== "string" || !path.startsWith(XD_PREFIX)) return;
|
|
922
|
+
const device = path.slice(5).split(/[/?#]/)[0];
|
|
923
|
+
if (device === void 0 || device.length === 0) return;
|
|
924
|
+
if (ledger.delete(device)) tel("guard.cleared", {
|
|
925
|
+
tool: device,
|
|
926
|
+
count: ledger.size
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
pi.on("context", (event) => {
|
|
930
|
+
if (ledger.size === 0) return void 0;
|
|
931
|
+
const unresolved = [...ledger.values()].filter((e) => e.failures > e.remindedAtFailure);
|
|
932
|
+
if (unresolved.length === 0) return void 0;
|
|
933
|
+
tel("guard.reminded", { count: unresolved.length });
|
|
934
|
+
const reminder = buildReminder(unresolved);
|
|
935
|
+
for (const entry of unresolved) entry.remindedAtFailure = entry.failures;
|
|
936
|
+
return { messages: [...event.messages, {
|
|
937
|
+
role: "user",
|
|
938
|
+
content: [{
|
|
939
|
+
type: "text",
|
|
940
|
+
text: reminder
|
|
941
|
+
}],
|
|
942
|
+
timestamp: Date.now()
|
|
943
|
+
}] };
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region src/index.ts
|
|
948
|
+
function agentDisciplineExtension(pi) {
|
|
949
|
+
xdRetryGuardExtension(pi);
|
|
950
|
+
noSkillDelegationExtension(pi);
|
|
951
|
+
}
|
|
952
|
+
//#endregion
|
|
953
|
+
export { agentDisciplineExtension as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@systemfsoftware/omp-agent-discipline",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"author": "Ryan Lee <drdgvhbh@gmail.com>",
|
|
6
|
+
"description": "OMP agent discipline: mechanical enforcement of rules prose can't hold (xd:// retry guard, ...)",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@oh-my-pi/pi-coding-agent": "^17.0.5"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@oh-my-pi/pi-coding-agent": "^17.0.5",
|
|
20
|
+
"@types/node": "^24",
|
|
21
|
+
"rimraf": "^6.1.3",
|
|
22
|
+
"tsdown": "^0.22.9",
|
|
23
|
+
"typescript": "^7",
|
|
24
|
+
"vitest": "^4",
|
|
25
|
+
"@systemfsoftware/omp-utils": "^0.0.0",
|
|
26
|
+
"@systemfsoftware/tsconfig": "^1.1.0",
|
|
27
|
+
"@systemfsoftware/oxlint-config": "^0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"omp": {
|
|
30
|
+
"extensions": [
|
|
31
|
+
"./dist/index.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"inlinedDependencies": {
|
|
35
|
+
"@jsr/std__collections": "1.3.0",
|
|
36
|
+
"@jsr/std__toml": "1.0.11"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"clean": "rimraf dist",
|
|
40
|
+
"build": "rimraf dist && tsdown",
|
|
41
|
+
"verify-dist": "node ../../scripts/check-dist-builtins.mjs --dist dist/index.js --expect node:fs node:path",
|
|
42
|
+
"postbuild": "pnpm verify-dist",
|
|
43
|
+
"typecheck": "tsc --noEmit --incremental",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"lint": "oxlint . ${AGENT:+--format=unix --quiet}"
|
|
46
|
+
}
|
|
47
|
+
}
|