@systemfsoftware/omp-claude-compat 1.1.4 → 1.1.6
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/dist/CommandExecutor-DYErWuMr.js +1 -0
- package/dist/dist-DO_QG0qW.js +18 -0
- package/dist/esm-DBTIrgAu.js +55 -0
- package/dist/hook-dispatcher.executor-B6Jon79H.js +3 -0
- package/dist/index.js +1 -1493
- package/dist/inject-instructions.executor-C0s4_7XL.js +3 -0
- package/dist/runtime-CMq7fttJ.js +1 -0
- package/package.json +17 -11
package/dist/index.js
CHANGED
|
@@ -1,1493 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { CommandExecutor } from "@effect/platform/CommandExecutor";
|
|
3
|
-
import { FileSystem } from "@effect/platform/FileSystem";
|
|
4
|
-
import * as PathModule from "@effect/platform/Path";
|
|
5
|
-
import { Path } from "@effect/platform/Path";
|
|
6
|
-
import { Command } from "@effect/platform";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { NodeCommandExecutor, NodeFileSystem } from "@effect/platform-node";
|
|
9
|
-
//#region ../../../node_modules/.pnpm/@jsr+std__collections@1.3.0/node_modules/@jsr/std__collections/deep_merge.js
|
|
10
|
-
/** Default merging options - cached to avoid object allocation on each call */ const DEFAULT_OPTIONS = {
|
|
11
|
-
arrays: "merge",
|
|
12
|
-
sets: "merge",
|
|
13
|
-
maps: "merge"
|
|
14
|
-
};
|
|
15
|
-
function deepMerge(record, other, options) {
|
|
16
|
-
return deepMergeInternal(record, other, /* @__PURE__ */ new Set(), options ?? DEFAULT_OPTIONS);
|
|
17
|
-
}
|
|
18
|
-
function deepMergeInternal(record, other, seen, options) {
|
|
19
|
-
const result = {};
|
|
20
|
-
const keys = /* @__PURE__ */ new Set([...getKeys(record), ...getKeys(other)]);
|
|
21
|
-
for (const key of keys) {
|
|
22
|
-
if (key === "__proto__") continue;
|
|
23
|
-
const a = record[key];
|
|
24
|
-
if (!Object.hasOwn(other, key)) {
|
|
25
|
-
result[key] = a;
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
const b = other[key];
|
|
29
|
-
if (isNonNullObject(a) && isNonNullObject(b) && !seen.has(a) && !seen.has(b)) {
|
|
30
|
-
seen.add(a);
|
|
31
|
-
seen.add(b);
|
|
32
|
-
result[key] = mergeObjects(a, b, seen, options);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
result[key] = b;
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
function mergeObjects(left, right, seen, options) {
|
|
40
|
-
if (isMergeable(left) && isMergeable(right)) return deepMergeInternal(left, right, seen, options);
|
|
41
|
-
if (isIterable(left) && isIterable(right)) {
|
|
42
|
-
if (Array.isArray(left) && Array.isArray(right)) {
|
|
43
|
-
if (options.arrays === "merge") return left.concat(right);
|
|
44
|
-
return right;
|
|
45
|
-
}
|
|
46
|
-
if (left instanceof Map && right instanceof Map) {
|
|
47
|
-
if (options.maps === "merge") {
|
|
48
|
-
const result = new Map(left);
|
|
49
|
-
for (const [k, v] of right) result.set(k, v);
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
return right;
|
|
53
|
-
}
|
|
54
|
-
if (left instanceof Set && right instanceof Set) {
|
|
55
|
-
if (options.sets === "merge") {
|
|
56
|
-
const result = new Set(left);
|
|
57
|
-
for (const v of right) result.add(v);
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
return right;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return right;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Test whether a value is mergeable or not
|
|
67
|
-
* Builtins that look like objects, null and user defined classes
|
|
68
|
-
* are not considered mergeable (it means that reference will be copied)
|
|
69
|
-
*/ function isMergeable(value) {
|
|
70
|
-
return Object.getPrototypeOf(value) === Object.prototype;
|
|
71
|
-
}
|
|
72
|
-
function isIterable(value) {
|
|
73
|
-
return typeof value[Symbol.iterator] === "function";
|
|
74
|
-
}
|
|
75
|
-
function isNonNullObject(value) {
|
|
76
|
-
return value !== null && typeof value === "object";
|
|
77
|
-
}
|
|
78
|
-
function getKeys(record) {
|
|
79
|
-
const keys = Object.keys(record);
|
|
80
|
-
const symbols = Object.getOwnPropertySymbols(record);
|
|
81
|
-
if (symbols.length === 0) return keys;
|
|
82
|
-
for (const sym of symbols) if (Object.prototype.propertyIsEnumerable.call(record, sym)) keys.push(sym);
|
|
83
|
-
return keys;
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
//#region ../../../node_modules/.pnpm/@jsr+std__toml@1.0.11/node_modules/@jsr/std__toml/_parser.js
|
|
87
|
-
/**
|
|
88
|
-
* Copy of `import { isLeap } from "@std/datetime";` because it cannot be impoted as long as it is unstable.
|
|
89
|
-
*/ function isLeap(yearNumber) {
|
|
90
|
-
return yearNumber % 4 === 0 && yearNumber % 100 !== 0 || yearNumber % 400 === 0;
|
|
91
|
-
}
|
|
92
|
-
var Scanner = class {
|
|
93
|
-
#whitespace = /[ \t]/;
|
|
94
|
-
#position = 0;
|
|
95
|
-
#source;
|
|
96
|
-
constructor(source) {
|
|
97
|
-
this.#source = source;
|
|
98
|
-
}
|
|
99
|
-
get position() {
|
|
100
|
-
return this.#position;
|
|
101
|
-
}
|
|
102
|
-
get source() {
|
|
103
|
-
return this.#source;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get current character
|
|
107
|
-
* @param index - relative index from current position
|
|
108
|
-
*/ char(index = 0) {
|
|
109
|
-
return this.#source[this.#position + index] ?? "";
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Get sliced string
|
|
113
|
-
* @param start - start position relative from current position
|
|
114
|
-
* @param end - end position relative from current position
|
|
115
|
-
*/ slice(start, end) {
|
|
116
|
-
return this.#source.slice(this.#position + start, this.#position + end);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Move position to next
|
|
120
|
-
*/ next(count = 1) {
|
|
121
|
-
this.#position += count;
|
|
122
|
-
}
|
|
123
|
-
skipWhitespaces() {
|
|
124
|
-
while (this.#whitespace.test(this.char()) && !this.eof()) this.next();
|
|
125
|
-
if (!this.isCurrentCharEOL() && /\s/.test(this.char())) {
|
|
126
|
-
const escaped = "\\u" + this.char().charCodeAt(0).toString(16);
|
|
127
|
-
const position = this.#position;
|
|
128
|
-
throw new SyntaxError(`Cannot parse the TOML: It contains invalid whitespace at position '${position}': \`${escaped}\``);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
nextUntilChar(options = { skipComments: true }) {
|
|
132
|
-
while (!this.eof()) {
|
|
133
|
-
const char = this.char();
|
|
134
|
-
if (this.#whitespace.test(char) || this.isCurrentCharEOL()) this.next();
|
|
135
|
-
else if (options.skipComments && this.char() === "#") while (!this.isCurrentCharEOL() && !this.eof()) this.next();
|
|
136
|
-
else break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Position reached EOF or not
|
|
141
|
-
*/ eof() {
|
|
142
|
-
return this.#position >= this.#source.length;
|
|
143
|
-
}
|
|
144
|
-
isCurrentCharEOL() {
|
|
145
|
-
return this.char() === "\n" || this.startsWith("\r\n");
|
|
146
|
-
}
|
|
147
|
-
startsWith(searchString) {
|
|
148
|
-
return this.#source.startsWith(searchString, this.#position);
|
|
149
|
-
}
|
|
150
|
-
match(regExp) {
|
|
151
|
-
if (!regExp.sticky) throw new Error(`RegExp ${regExp} does not have a sticky 'y' flag`);
|
|
152
|
-
regExp.lastIndex = this.#position;
|
|
153
|
-
return this.#source.match(regExp);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
function success(body) {
|
|
157
|
-
return {
|
|
158
|
-
ok: true,
|
|
159
|
-
body
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function failure() {
|
|
163
|
-
return { ok: false };
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Creates a nested object from the keys and values.
|
|
167
|
-
*
|
|
168
|
-
* e.g. `unflat(["a", "b", "c"], 1)` returns `{ a: { b: { c: 1 } } }`
|
|
169
|
-
*/ function unflat(keys, values = { __proto__: null }) {
|
|
170
|
-
return keys.reduceRight((acc, key) => ({ [key]: acc }), values);
|
|
171
|
-
}
|
|
172
|
-
function isObject(value) {
|
|
173
|
-
return typeof value === "object" && value !== null;
|
|
174
|
-
}
|
|
175
|
-
function getTargetValue(target, keys) {
|
|
176
|
-
const key = keys[0];
|
|
177
|
-
if (!key) throw new Error("Cannot parse the TOML: key length is not a positive number");
|
|
178
|
-
return target[key];
|
|
179
|
-
}
|
|
180
|
-
function deepAssignTable(target, table) {
|
|
181
|
-
const { keys, type, value } = table;
|
|
182
|
-
const currentValue = getTargetValue(target, keys);
|
|
183
|
-
if (currentValue === void 0) return Object.assign(target, unflat(keys, value));
|
|
184
|
-
if (Array.isArray(currentValue)) {
|
|
185
|
-
deepAssign(currentValue.at(-1), {
|
|
186
|
-
type,
|
|
187
|
-
keys: keys.slice(1),
|
|
188
|
-
value
|
|
189
|
-
});
|
|
190
|
-
return target;
|
|
191
|
-
}
|
|
192
|
-
if (isObject(currentValue)) {
|
|
193
|
-
deepAssign(currentValue, {
|
|
194
|
-
type,
|
|
195
|
-
keys: keys.slice(1),
|
|
196
|
-
value
|
|
197
|
-
});
|
|
198
|
-
return target;
|
|
199
|
-
}
|
|
200
|
-
throw new Error("Unexpected assign");
|
|
201
|
-
}
|
|
202
|
-
function deepAssignTableArray(target, table) {
|
|
203
|
-
const { type, keys, value } = table;
|
|
204
|
-
const currentValue = getTargetValue(target, keys);
|
|
205
|
-
if (currentValue === void 0) return Object.assign(target, unflat(keys, [value]));
|
|
206
|
-
if (Array.isArray(currentValue)) {
|
|
207
|
-
if (table.keys.length === 1) currentValue.push(value);
|
|
208
|
-
else deepAssign(currentValue.at(-1), {
|
|
209
|
-
type: table.type,
|
|
210
|
-
keys: table.keys.slice(1),
|
|
211
|
-
value: table.value
|
|
212
|
-
});
|
|
213
|
-
return target;
|
|
214
|
-
}
|
|
215
|
-
if (isObject(currentValue)) {
|
|
216
|
-
deepAssign(currentValue, {
|
|
217
|
-
type,
|
|
218
|
-
keys: keys.slice(1),
|
|
219
|
-
value
|
|
220
|
-
});
|
|
221
|
-
return target;
|
|
222
|
-
}
|
|
223
|
-
throw new Error("Unexpected assign");
|
|
224
|
-
}
|
|
225
|
-
function deepAssign(target, body) {
|
|
226
|
-
switch (body.type) {
|
|
227
|
-
case "Block": return deepMerge(target, body.value);
|
|
228
|
-
case "Table": return deepAssignTable(target, body);
|
|
229
|
-
case "TableArray": return deepAssignTableArray(target, body);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function or(parsers) {
|
|
233
|
-
return (scanner) => {
|
|
234
|
-
for (const parse of parsers) {
|
|
235
|
-
const result = parse(scanner);
|
|
236
|
-
if (result.ok) return result;
|
|
237
|
-
}
|
|
238
|
-
return failure();
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
/** Join the parse results of the given parser into an array.
|
|
242
|
-
*
|
|
243
|
-
* If the parser fails at the first attempt, it will return an empty array.
|
|
244
|
-
*/ function join(parser, separator) {
|
|
245
|
-
const Separator = character(separator);
|
|
246
|
-
return (scanner) => {
|
|
247
|
-
const out = [];
|
|
248
|
-
const first = parser(scanner);
|
|
249
|
-
if (!first.ok) return success(out);
|
|
250
|
-
out.push(first.body);
|
|
251
|
-
while (!scanner.eof()) {
|
|
252
|
-
if (!Separator(scanner).ok) break;
|
|
253
|
-
const result = parser(scanner);
|
|
254
|
-
if (!result.ok) throw new SyntaxError(`Invalid token after "${separator}"`);
|
|
255
|
-
out.push(result.body);
|
|
256
|
-
}
|
|
257
|
-
return success(out);
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
/** Join the parse results of the given parser into an array.
|
|
261
|
-
*
|
|
262
|
-
* This requires the parser to succeed at least once.
|
|
263
|
-
*/ function join1(parser, separator) {
|
|
264
|
-
const Separator = character(separator);
|
|
265
|
-
return (scanner) => {
|
|
266
|
-
const first = parser(scanner);
|
|
267
|
-
if (!first.ok) return failure();
|
|
268
|
-
const out = [first.body];
|
|
269
|
-
while (!scanner.eof()) {
|
|
270
|
-
if (!Separator(scanner).ok) break;
|
|
271
|
-
const result = parser(scanner);
|
|
272
|
-
if (!result.ok) throw new SyntaxError(`Invalid token after "${separator}"`);
|
|
273
|
-
out.push(result.body);
|
|
274
|
-
}
|
|
275
|
-
return success(out);
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function kv(keyParser, separator, valueParser) {
|
|
279
|
-
const Separator = character(separator);
|
|
280
|
-
return (scanner) => {
|
|
281
|
-
const position = scanner.position;
|
|
282
|
-
const key = keyParser(scanner);
|
|
283
|
-
if (!key.ok) return failure();
|
|
284
|
-
if (!Separator(scanner).ok) throw new SyntaxError(`key/value pair doesn't have "${separator}"`);
|
|
285
|
-
const value = valueParser(scanner);
|
|
286
|
-
if (!value.ok) {
|
|
287
|
-
const lineEndIndex = scanner.source.indexOf("\n", scanner.position);
|
|
288
|
-
const endPosition = lineEndIndex > 0 ? lineEndIndex : scanner.source.length;
|
|
289
|
-
const line = scanner.source.slice(position, endPosition);
|
|
290
|
-
throw new SyntaxError(`Cannot parse value on line '${line}'`);
|
|
291
|
-
}
|
|
292
|
-
return success(unflat(key.body, value.body));
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
function merge(parser) {
|
|
296
|
-
return (scanner) => {
|
|
297
|
-
const result = parser(scanner);
|
|
298
|
-
if (!result.ok) return failure();
|
|
299
|
-
let body = { __proto__: null };
|
|
300
|
-
for (const record of result.body) if (typeof record === "object" && record !== null) body = deepMerge(body, record);
|
|
301
|
-
return success(body);
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
function repeat(parser) {
|
|
305
|
-
return (scanner) => {
|
|
306
|
-
const body = [];
|
|
307
|
-
while (!scanner.eof()) {
|
|
308
|
-
const result = parser(scanner);
|
|
309
|
-
if (!result.ok) break;
|
|
310
|
-
body.push(result.body);
|
|
311
|
-
scanner.nextUntilChar();
|
|
312
|
-
}
|
|
313
|
-
if (body.length === 0) return failure();
|
|
314
|
-
return success(body);
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function surround(left, parser, right) {
|
|
318
|
-
const Left = character(left);
|
|
319
|
-
const Right = character(right);
|
|
320
|
-
return (scanner) => {
|
|
321
|
-
if (!Left(scanner).ok) return failure();
|
|
322
|
-
const result = parser(scanner);
|
|
323
|
-
if (!result.ok) throw new SyntaxError(`Invalid token after "${left}"`);
|
|
324
|
-
if (!Right(scanner).ok) throw new SyntaxError(`Not closed by "${right}" after started with "${left}"`);
|
|
325
|
-
return success(result.body);
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
function character(str) {
|
|
329
|
-
return (scanner) => {
|
|
330
|
-
scanner.skipWhitespaces();
|
|
331
|
-
if (!scanner.startsWith(str)) return failure();
|
|
332
|
-
scanner.next(str.length);
|
|
333
|
-
scanner.skipWhitespaces();
|
|
334
|
-
return success(void 0);
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
const BARE_KEY_REGEXP = /[A-Za-z0-9_-]+/y;
|
|
338
|
-
function bareKey(scanner) {
|
|
339
|
-
scanner.skipWhitespaces();
|
|
340
|
-
const key = scanner.match(BARE_KEY_REGEXP)?.[0];
|
|
341
|
-
if (!key) return failure();
|
|
342
|
-
scanner.next(key.length);
|
|
343
|
-
return success(key);
|
|
344
|
-
}
|
|
345
|
-
function escapeSequence(scanner) {
|
|
346
|
-
if (scanner.char() !== "\\") return failure();
|
|
347
|
-
scanner.next();
|
|
348
|
-
switch (scanner.char()) {
|
|
349
|
-
case "b":
|
|
350
|
-
scanner.next();
|
|
351
|
-
return success("\b");
|
|
352
|
-
case "t":
|
|
353
|
-
scanner.next();
|
|
354
|
-
return success(" ");
|
|
355
|
-
case "n":
|
|
356
|
-
scanner.next();
|
|
357
|
-
return success("\n");
|
|
358
|
-
case "f":
|
|
359
|
-
scanner.next();
|
|
360
|
-
return success("\f");
|
|
361
|
-
case "r":
|
|
362
|
-
scanner.next();
|
|
363
|
-
return success("\r");
|
|
364
|
-
case "u":
|
|
365
|
-
case "U": {
|
|
366
|
-
const codePointLen = scanner.char() === "u" ? 4 : 6;
|
|
367
|
-
const codePoint = parseInt("0x" + scanner.slice(1, 1 + codePointLen), 16);
|
|
368
|
-
const str = String.fromCodePoint(codePoint);
|
|
369
|
-
scanner.next(codePointLen + 1);
|
|
370
|
-
return success(str);
|
|
371
|
-
}
|
|
372
|
-
case "\"":
|
|
373
|
-
scanner.next();
|
|
374
|
-
return success("\"");
|
|
375
|
-
case "\\":
|
|
376
|
-
scanner.next();
|
|
377
|
-
return success("\\");
|
|
378
|
-
default: throw new SyntaxError(`Invalid escape sequence: \\${scanner.char()}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
function basicString(scanner) {
|
|
382
|
-
scanner.skipWhitespaces();
|
|
383
|
-
if (scanner.char() !== "\"") return failure();
|
|
384
|
-
scanner.next();
|
|
385
|
-
const acc = [];
|
|
386
|
-
while (scanner.char() !== "\"" && !scanner.eof()) {
|
|
387
|
-
if (scanner.char() === "\n") throw new SyntaxError("Single-line string cannot contain EOL");
|
|
388
|
-
const escapedChar = escapeSequence(scanner);
|
|
389
|
-
if (escapedChar.ok) acc.push(escapedChar.body);
|
|
390
|
-
else {
|
|
391
|
-
acc.push(scanner.char());
|
|
392
|
-
scanner.next();
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if (scanner.eof()) throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`);
|
|
396
|
-
scanner.next();
|
|
397
|
-
return success(acc.join(""));
|
|
398
|
-
}
|
|
399
|
-
function literalString(scanner) {
|
|
400
|
-
scanner.skipWhitespaces();
|
|
401
|
-
if (scanner.char() !== "'") return failure();
|
|
402
|
-
scanner.next();
|
|
403
|
-
const acc = [];
|
|
404
|
-
while (scanner.char() !== "'" && !scanner.eof()) {
|
|
405
|
-
if (scanner.char() === "\n") throw new SyntaxError("Single-line string cannot contain EOL");
|
|
406
|
-
acc.push(scanner.char());
|
|
407
|
-
scanner.next();
|
|
408
|
-
}
|
|
409
|
-
if (scanner.eof()) throw new SyntaxError(`Single-line string is not closed:\n${acc.join("")}`);
|
|
410
|
-
scanner.next();
|
|
411
|
-
return success(acc.join(""));
|
|
412
|
-
}
|
|
413
|
-
function multilineBasicString(scanner) {
|
|
414
|
-
scanner.skipWhitespaces();
|
|
415
|
-
if (!scanner.startsWith("\"\"\"")) return failure();
|
|
416
|
-
scanner.next(3);
|
|
417
|
-
if (scanner.char() === "\n") scanner.next();
|
|
418
|
-
else if (scanner.startsWith("\r\n")) scanner.next(2);
|
|
419
|
-
const acc = [];
|
|
420
|
-
while (!scanner.startsWith("\"\"\"") && !scanner.eof()) {
|
|
421
|
-
if (scanner.startsWith("\\\n")) {
|
|
422
|
-
scanner.next();
|
|
423
|
-
scanner.nextUntilChar({ skipComments: false });
|
|
424
|
-
continue;
|
|
425
|
-
} else if (scanner.startsWith("\\\r\n")) {
|
|
426
|
-
scanner.next();
|
|
427
|
-
scanner.nextUntilChar({ skipComments: false });
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
const escapedChar = escapeSequence(scanner);
|
|
431
|
-
if (escapedChar.ok) acc.push(escapedChar.body);
|
|
432
|
-
else {
|
|
433
|
-
acc.push(scanner.char());
|
|
434
|
-
scanner.next();
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (scanner.eof()) throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`);
|
|
438
|
-
if (scanner.char(3) === "\"") {
|
|
439
|
-
acc.push("\"");
|
|
440
|
-
scanner.next();
|
|
441
|
-
}
|
|
442
|
-
scanner.next(3);
|
|
443
|
-
return success(acc.join(""));
|
|
444
|
-
}
|
|
445
|
-
function multilineLiteralString(scanner) {
|
|
446
|
-
scanner.skipWhitespaces();
|
|
447
|
-
if (!scanner.startsWith("'''")) return failure();
|
|
448
|
-
scanner.next(3);
|
|
449
|
-
if (scanner.char() === "\n") scanner.next();
|
|
450
|
-
else if (scanner.startsWith("\r\n")) scanner.next(2);
|
|
451
|
-
const acc = [];
|
|
452
|
-
while (!scanner.startsWith("'''") && !scanner.eof()) {
|
|
453
|
-
acc.push(scanner.char());
|
|
454
|
-
scanner.next();
|
|
455
|
-
}
|
|
456
|
-
if (scanner.eof()) throw new SyntaxError(`Multi-line string is not closed:\n${acc.join("")}`);
|
|
457
|
-
if (scanner.char(3) === "'") {
|
|
458
|
-
acc.push("'");
|
|
459
|
-
scanner.next();
|
|
460
|
-
}
|
|
461
|
-
scanner.next(3);
|
|
462
|
-
return success(acc.join(""));
|
|
463
|
-
}
|
|
464
|
-
const BOOLEAN_REGEXP = /(?:true|false)\b/y;
|
|
465
|
-
function boolean(scanner) {
|
|
466
|
-
scanner.skipWhitespaces();
|
|
467
|
-
const match = scanner.match(BOOLEAN_REGEXP);
|
|
468
|
-
if (!match) return failure();
|
|
469
|
-
const string = match[0];
|
|
470
|
-
scanner.next(string.length);
|
|
471
|
-
return success(string === "true");
|
|
472
|
-
}
|
|
473
|
-
const INFINITY_MAP = /* @__PURE__ */ new Map([
|
|
474
|
-
["inf", Infinity],
|
|
475
|
-
["+inf", Infinity],
|
|
476
|
-
["-inf", -Infinity]
|
|
477
|
-
]);
|
|
478
|
-
const INFINITY_REGEXP = /[+-]?inf\b/y;
|
|
479
|
-
function infinity(scanner) {
|
|
480
|
-
scanner.skipWhitespaces();
|
|
481
|
-
const match = scanner.match(INFINITY_REGEXP);
|
|
482
|
-
if (!match) return failure();
|
|
483
|
-
const string = match[0];
|
|
484
|
-
scanner.next(string.length);
|
|
485
|
-
return success(INFINITY_MAP.get(string));
|
|
486
|
-
}
|
|
487
|
-
const NAN_REGEXP = /[+-]?nan\b/y;
|
|
488
|
-
function nan(scanner) {
|
|
489
|
-
scanner.skipWhitespaces();
|
|
490
|
-
const match = scanner.match(NAN_REGEXP);
|
|
491
|
-
if (!match) return failure();
|
|
492
|
-
const string = match[0];
|
|
493
|
-
scanner.next(string.length);
|
|
494
|
-
return success(NaN);
|
|
495
|
-
}
|
|
496
|
-
const dottedKey = join1(or([
|
|
497
|
-
bareKey,
|
|
498
|
-
basicString,
|
|
499
|
-
literalString
|
|
500
|
-
]), ".");
|
|
501
|
-
const BINARY_REGEXP = /0b[01]+(?:_[01]+)*\b/y;
|
|
502
|
-
function binary(scanner) {
|
|
503
|
-
scanner.skipWhitespaces();
|
|
504
|
-
const match = scanner.match(BINARY_REGEXP)?.[0];
|
|
505
|
-
if (!match) return failure();
|
|
506
|
-
scanner.next(match.length);
|
|
507
|
-
const value = match.slice(2).replaceAll("_", "");
|
|
508
|
-
const number = parseInt(value, 2);
|
|
509
|
-
return isNaN(number) ? failure() : success(number);
|
|
510
|
-
}
|
|
511
|
-
const OCTAL_REGEXP = /0o[0-7]+(?:_[0-7]+)*\b/y;
|
|
512
|
-
function octal(scanner) {
|
|
513
|
-
scanner.skipWhitespaces();
|
|
514
|
-
const match = scanner.match(OCTAL_REGEXP)?.[0];
|
|
515
|
-
if (!match) return failure();
|
|
516
|
-
scanner.next(match.length);
|
|
517
|
-
const value = match.slice(2).replaceAll("_", "");
|
|
518
|
-
const number = parseInt(value, 8);
|
|
519
|
-
return isNaN(number) ? failure() : success(number);
|
|
520
|
-
}
|
|
521
|
-
const HEX_REGEXP = /0x[0-9a-f]+(?:_[0-9a-f]+)*\b/iy;
|
|
522
|
-
function hex(scanner) {
|
|
523
|
-
scanner.skipWhitespaces();
|
|
524
|
-
const match = scanner.match(HEX_REGEXP)?.[0];
|
|
525
|
-
if (!match) return failure();
|
|
526
|
-
scanner.next(match.length);
|
|
527
|
-
const value = match.slice(2).replaceAll("_", "");
|
|
528
|
-
const number = parseInt(value, 16);
|
|
529
|
-
return isNaN(number) ? failure() : success(number);
|
|
530
|
-
}
|
|
531
|
-
const INTEGER_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)\b/y;
|
|
532
|
-
function integer(scanner) {
|
|
533
|
-
scanner.skipWhitespaces();
|
|
534
|
-
const match = scanner.match(INTEGER_REGEXP)?.[0];
|
|
535
|
-
if (!match) return failure();
|
|
536
|
-
scanner.next(match.length);
|
|
537
|
-
const value = match.replaceAll("_", "");
|
|
538
|
-
return success(parseInt(value, 10));
|
|
539
|
-
}
|
|
540
|
-
const FLOAT_REGEXP = /[+-]?(?:0|[1-9][0-9]*(?:_[0-9]+)*)(?:\.[0-9]+(?:_[0-9]+)*)?(?:e[+-]?[0-9]+(?:_[0-9]+)*)?\b/iy;
|
|
541
|
-
function float(scanner) {
|
|
542
|
-
scanner.skipWhitespaces();
|
|
543
|
-
const match = scanner.match(FLOAT_REGEXP)?.[0];
|
|
544
|
-
if (!match) return failure();
|
|
545
|
-
scanner.next(match.length);
|
|
546
|
-
const value = match.replaceAll("_", "");
|
|
547
|
-
const float = parseFloat(value);
|
|
548
|
-
if (isNaN(float)) return failure();
|
|
549
|
-
return success(float);
|
|
550
|
-
}
|
|
551
|
-
const DATE_TIME_REGEXP = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})(?:[ 0-9TZ.:+-]+)?\b/y;
|
|
552
|
-
function dateTime(scanner) {
|
|
553
|
-
scanner.skipWhitespaces();
|
|
554
|
-
const match = scanner.match(DATE_TIME_REGEXP);
|
|
555
|
-
if (!match) return failure();
|
|
556
|
-
const string = match[0];
|
|
557
|
-
scanner.next(string.length);
|
|
558
|
-
const groups = match.groups;
|
|
559
|
-
if (groups.month == "02") {
|
|
560
|
-
const days = parseInt(groups.day);
|
|
561
|
-
if (days > 29) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
562
|
-
const year = parseInt(groups.year);
|
|
563
|
-
if (days > 28 && !isLeap(year)) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
564
|
-
}
|
|
565
|
-
const date = new Date(string.trim());
|
|
566
|
-
if (isNaN(date.getTime())) throw new SyntaxError(`Invalid date string "${match}"`);
|
|
567
|
-
return success(date);
|
|
568
|
-
}
|
|
569
|
-
const LOCAL_TIME_REGEXP = /(\d{2}):(\d{2}):(\d{2})(?:\.[0-9]+)?\b/y;
|
|
570
|
-
function localTime(scanner) {
|
|
571
|
-
scanner.skipWhitespaces();
|
|
572
|
-
const match = scanner.match(LOCAL_TIME_REGEXP)?.[0];
|
|
573
|
-
if (!match) return failure();
|
|
574
|
-
scanner.next(match.length);
|
|
575
|
-
return success(match);
|
|
576
|
-
}
|
|
577
|
-
function arrayValue(scanner) {
|
|
578
|
-
scanner.skipWhitespaces();
|
|
579
|
-
if (scanner.char() !== "[") return failure();
|
|
580
|
-
scanner.next();
|
|
581
|
-
const array = [];
|
|
582
|
-
while (!scanner.eof()) {
|
|
583
|
-
scanner.nextUntilChar();
|
|
584
|
-
const result = value(scanner);
|
|
585
|
-
if (!result.ok) break;
|
|
586
|
-
array.push(result.body);
|
|
587
|
-
scanner.skipWhitespaces();
|
|
588
|
-
if (scanner.char() !== ",") break;
|
|
589
|
-
scanner.next();
|
|
590
|
-
}
|
|
591
|
-
scanner.nextUntilChar();
|
|
592
|
-
if (scanner.char() !== "]") throw new SyntaxError("Array is not closed");
|
|
593
|
-
scanner.next();
|
|
594
|
-
return success(array);
|
|
595
|
-
}
|
|
596
|
-
function inlineTable(scanner) {
|
|
597
|
-
scanner.nextUntilChar();
|
|
598
|
-
if (scanner.char(1) === "}") {
|
|
599
|
-
scanner.next(2);
|
|
600
|
-
return success({ __proto__: null });
|
|
601
|
-
}
|
|
602
|
-
const pairs = surround("{", join(pair, ","), "}")(scanner);
|
|
603
|
-
if (!pairs.ok) return failure();
|
|
604
|
-
let table = { __proto__: null };
|
|
605
|
-
for (const pair of pairs.body) table = deepMerge(table, pair);
|
|
606
|
-
return success(table);
|
|
607
|
-
}
|
|
608
|
-
const value = or([
|
|
609
|
-
multilineBasicString,
|
|
610
|
-
multilineLiteralString,
|
|
611
|
-
basicString,
|
|
612
|
-
literalString,
|
|
613
|
-
boolean,
|
|
614
|
-
infinity,
|
|
615
|
-
nan,
|
|
616
|
-
dateTime,
|
|
617
|
-
localTime,
|
|
618
|
-
binary,
|
|
619
|
-
octal,
|
|
620
|
-
hex,
|
|
621
|
-
float,
|
|
622
|
-
integer,
|
|
623
|
-
arrayValue,
|
|
624
|
-
inlineTable
|
|
625
|
-
]);
|
|
626
|
-
const pair = kv(dottedKey, "=", value);
|
|
627
|
-
function block(scanner) {
|
|
628
|
-
scanner.nextUntilChar();
|
|
629
|
-
const result = merge(repeat(pair))(scanner);
|
|
630
|
-
if (result.ok) return success({
|
|
631
|
-
type: "Block",
|
|
632
|
-
value: result.body
|
|
633
|
-
});
|
|
634
|
-
return failure();
|
|
635
|
-
}
|
|
636
|
-
const tableHeader = surround("[", dottedKey, "]");
|
|
637
|
-
function table(scanner) {
|
|
638
|
-
scanner.nextUntilChar();
|
|
639
|
-
const header = tableHeader(scanner);
|
|
640
|
-
if (!header.ok) return failure();
|
|
641
|
-
scanner.nextUntilChar();
|
|
642
|
-
const b = block(scanner);
|
|
643
|
-
return success({
|
|
644
|
-
type: "Table",
|
|
645
|
-
keys: header.body,
|
|
646
|
-
value: b.ok ? b.body.value : { __proto__: null }
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
const tableArrayHeader = surround("[[", dottedKey, "]]");
|
|
650
|
-
function tableArray(scanner) {
|
|
651
|
-
scanner.nextUntilChar();
|
|
652
|
-
const header = tableArrayHeader(scanner);
|
|
653
|
-
if (!header.ok) return failure();
|
|
654
|
-
scanner.nextUntilChar();
|
|
655
|
-
const b = block(scanner);
|
|
656
|
-
return success({
|
|
657
|
-
type: "TableArray",
|
|
658
|
-
keys: header.body,
|
|
659
|
-
value: b.ok ? b.body.value : { __proto__: null }
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
function toml(scanner) {
|
|
663
|
-
const blocks = repeat(or([
|
|
664
|
-
block,
|
|
665
|
-
tableArray,
|
|
666
|
-
table
|
|
667
|
-
]))(scanner);
|
|
668
|
-
if (!blocks.ok) return success({ __proto__: null });
|
|
669
|
-
return success(blocks.body.reduce(deepAssign, { __proto__: null }));
|
|
670
|
-
}
|
|
671
|
-
function createParseErrorMessage(scanner, message) {
|
|
672
|
-
const lines = scanner.source.slice(0, scanner.position).split("\n");
|
|
673
|
-
return `Parse error on line ${lines.length}, column ${lines.at(-1)?.length ?? 0}: ${message}`;
|
|
674
|
-
}
|
|
675
|
-
function parserFactory(parser) {
|
|
676
|
-
return (tomlString) => {
|
|
677
|
-
const scanner = new Scanner(tomlString);
|
|
678
|
-
try {
|
|
679
|
-
const result = parser(scanner);
|
|
680
|
-
if (result.ok && scanner.eof()) return result.body;
|
|
681
|
-
const message = `Unexpected character: "${scanner.char()}"`;
|
|
682
|
-
throw new SyntaxError(createParseErrorMessage(scanner, message));
|
|
683
|
-
} catch (error) {
|
|
684
|
-
if (error instanceof Error) throw new SyntaxError(createParseErrorMessage(scanner, error.message));
|
|
685
|
-
throw new SyntaxError(createParseErrorMessage(scanner, "Invalid error type caught"));
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
//#endregion
|
|
690
|
-
//#region ../../../node_modules/.pnpm/@jsr+std__toml@1.0.11/node_modules/@jsr/std__toml/parse.js
|
|
691
|
-
/**
|
|
692
|
-
* Parses a {@link https://toml.io | TOML} string into an object.
|
|
693
|
-
*
|
|
694
|
-
* @example Usage
|
|
695
|
-
* ```ts
|
|
696
|
-
* import { parse } from "@std/toml/parse";
|
|
697
|
-
* import { assertEquals } from "@std/assert";
|
|
698
|
-
*
|
|
699
|
-
* const tomlString = `title = "TOML Example"
|
|
700
|
-
* [owner]
|
|
701
|
-
* name = "Alice"
|
|
702
|
-
* bio = "Alice is a programmer."`;
|
|
703
|
-
*
|
|
704
|
-
* const obj = parse(tomlString);
|
|
705
|
-
* assertEquals(obj, { title: "TOML Example", owner: { name: "Alice", bio: "Alice is a programmer." } });
|
|
706
|
-
* ```
|
|
707
|
-
* @param tomlString TOML string to be parsed.
|
|
708
|
-
* @returns The parsed JS object.
|
|
709
|
-
*/ function parse(tomlString) {
|
|
710
|
-
return parserFactory(toml)(tomlString);
|
|
711
|
-
}
|
|
712
|
-
//#endregion
|
|
713
|
-
//#region ../../packages/omp-utils/dist/index.js
|
|
714
|
-
/**
|
|
715
|
-
* ACL: detect and extract shell commands from context-mode tool invocations.
|
|
716
|
-
*
|
|
717
|
-
* Context-mode tools (ctx_execute, ctx_batch_execute) can execute shell
|
|
718
|
-
* commands that should be inspected by shell-guard hooks.
|
|
719
|
-
*/
|
|
720
|
-
const CONTEXT_MODE_SHELL_TOOLS = {
|
|
721
|
-
ctx_execute: true,
|
|
722
|
-
ctx_batch_execute: true
|
|
723
|
-
};
|
|
724
|
-
function isContextModeShellTool(toolName, input) {
|
|
725
|
-
if (!CONTEXT_MODE_SHELL_TOOLS[toolName]) return false;
|
|
726
|
-
if (toolName === "ctx_execute") return input["language"] === "shell";
|
|
727
|
-
return true;
|
|
728
|
-
}
|
|
729
|
-
function extractShellCommand(toolName, input) {
|
|
730
|
-
if (!isContextModeShellTool(toolName, input)) return void 0;
|
|
731
|
-
if (toolName === "ctx_execute") return typeof input["code"] === "string" ? input["code"] : void 0;
|
|
732
|
-
if (Array.isArray(input["commands"])) return input["commands"].map((entry) => typeof entry === "object" && entry !== null ? entry["command"] : "").filter((cmd) => typeof cmd === "string").join("\n");
|
|
733
|
-
}
|
|
734
|
-
/**
|
|
735
|
-
* ACL: match OMP tool names against Claude Code hook matcher patterns.
|
|
736
|
-
*
|
|
737
|
-
* Hook matchers are pipe-separated regex patterns (e.g. "Write|Edit").
|
|
738
|
-
* An empty/undefined matcher matches everything.
|
|
739
|
-
*/
|
|
740
|
-
const regexCache = /* @__PURE__ */ new Map();
|
|
741
|
-
function matchesMatcher(toolName, matcher) {
|
|
742
|
-
if (!matcher || matcher.length === 0) return true;
|
|
743
|
-
const pattern = matcher.split("|").map((part) => part.trim()).filter(Boolean).join("|");
|
|
744
|
-
if (pattern.length === 0) return true;
|
|
745
|
-
const cached = regexCache.get(pattern);
|
|
746
|
-
if (cached !== void 0) return cached.test(toolName);
|
|
747
|
-
const regex = new RegExp(`^(?:${pattern})$`);
|
|
748
|
-
regexCache.set(pattern, regex);
|
|
749
|
-
return regex.test(toolName);
|
|
750
|
-
}
|
|
751
|
-
function sessionIds(getSessionId) {
|
|
752
|
-
return {
|
|
753
|
-
session_id: getSessionId(),
|
|
754
|
-
agent_id: null
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Create a telemetry emitter for a plugin.
|
|
759
|
-
*
|
|
760
|
-
* @param plugin - The event prefix (e.g. `claude_compat`, `agent_discipline`).
|
|
761
|
-
* @param logger - The `pi.logger` instance injected by the host at factory time.
|
|
762
|
-
*/
|
|
763
|
-
function createTelemetry(plugin, logger) {
|
|
764
|
-
return (eventName, fields) => {
|
|
765
|
-
try {
|
|
766
|
-
logger.info(eventName, {
|
|
767
|
-
plugin,
|
|
768
|
-
event: eventName,
|
|
769
|
-
...fields
|
|
770
|
-
});
|
|
771
|
-
} catch {}
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Schema: TOML config — the inner vocabulary of `systemfsoftware.toml`.
|
|
776
|
-
*
|
|
777
|
-
* Shape: a record of string key to string array. The only consumer of the inner
|
|
778
|
-
* keys is the agent-discipline plugin (it reads `no_delegate_skills`); other
|
|
779
|
-
* plugins may add their own keys without this schema changing. Branding makes
|
|
780
|
-
* a parsed config distinguishable from a raw record and surfaces shape errors
|
|
781
|
-
* through the loader's parse path.
|
|
782
|
-
*
|
|
783
|
-
* Pure declaration. No behavior, no `@std/toml`, no I/O. The ACL owns the
|
|
784
|
-
* boundary crossing; the executor owns the I/O.
|
|
785
|
-
*/
|
|
786
|
-
const TomlConfig = Schema.Record({
|
|
787
|
-
key: Schema.String,
|
|
788
|
-
value: Schema.Array(Schema.String)
|
|
789
|
-
}).pipe(Schema.brand("TomlConfig"));
|
|
790
|
-
/**
|
|
791
|
-
* ACL: TOML text → `TomlConfig`.
|
|
792
|
-
*
|
|
793
|
-
* The only file in this package that imports `@std/toml`. Anything that
|
|
794
|
-
* decodes from outside the domain types routes through here so the foreign
|
|
795
|
-
* parser is contained at one boundary.
|
|
796
|
-
*
|
|
797
|
-
* `Schema.transformOrFail` with `strict: true` makes the decode go through
|
|
798
|
-
* Schema's identity contract — branding is earned by `ParseResult.decode`,
|
|
799
|
-
* never by a cast. Encode is `Forbidden` because the TOML format is the
|
|
800
|
-
* source, not the destination. Constitutes ACL1 (see `omp/AGENTS.md`).
|
|
801
|
-
*/
|
|
802
|
-
const TomlConfigFromText = Schema.transformOrFail(Schema.String, Schema.typeSchema(TomlConfig), {
|
|
803
|
-
strict: true,
|
|
804
|
-
decode: (raw) => ParseResult.try({
|
|
805
|
-
try: () => parse(raw),
|
|
806
|
-
catch: (e) => new ParseResult.Unexpected(e, "TOML parse error")
|
|
807
|
-
}).pipe(ParseResult.flatMap((parsed) => ParseResult.decodeUnknown(TomlConfig)(parsed))),
|
|
808
|
-
encode: (_, _d, ast) => ParseResult.fail(new ParseResult.Forbidden(ast, _, "TomlConfigFromText is decode-only"))
|
|
809
|
-
});
|
|
810
|
-
const CONFIG_FILE = "systemfsoftware.toml";
|
|
811
|
-
const EMPTY_CONFIG = Schema.decodeSync(TomlConfig)({});
|
|
812
|
-
var TomlLoader = class extends Context.Tag("TomlLoader")() {};
|
|
813
|
-
Layer.effect(TomlLoader, Effect.gen(function* () {
|
|
814
|
-
const cache = yield* Ref.make(MutableHashMap.empty());
|
|
815
|
-
const fs = yield* FileSystem;
|
|
816
|
-
const path = yield* Path;
|
|
817
|
-
return TomlLoader.of({ load: Effect.fn("TomlLoader.load")(function* (cwd) {
|
|
818
|
-
const cached = yield* Ref.get(cache);
|
|
819
|
-
const existing = MutableHashMap.get(cached, cwd);
|
|
820
|
-
if (Option.isSome(existing)) return existing.value;
|
|
821
|
-
const configPath = path.join(cwd, CONFIG_FILE);
|
|
822
|
-
if (!(yield* fs.exists(configPath))) {
|
|
823
|
-
yield* Ref.update(cache, (m) => (MutableHashMap.set(m, cwd, EMPTY_CONFIG), m));
|
|
824
|
-
return EMPTY_CONFIG;
|
|
825
|
-
}
|
|
826
|
-
const result = yield* fs.readFileString(configPath).pipe(Effect.flatMap(Schema.decodeUnknown(TomlConfigFromText)), Effect.tapError((error) => Effect.logWarning(`[toml-loader] malformed ${CONFIG_FILE} at ${configPath} — failing open (no config)`, error)), Effect.catchAll(() => Effect.succeed(EMPTY_CONFIG)));
|
|
827
|
-
yield* Ref.update(cache, (m) => (MutableHashMap.set(m, cwd, result), m));
|
|
828
|
-
return result;
|
|
829
|
-
}) });
|
|
830
|
-
}));
|
|
831
|
-
/**
|
|
832
|
-
* ACL: translate OMP tool input shapes to Claude Code hook input shapes.
|
|
833
|
-
*
|
|
834
|
-
* OMP sends `edits: [{ old_text, new_text }]` and `path`;
|
|
835
|
-
* Claude Code hooks expect `old_string`/`new_string` and `file_path`.
|
|
836
|
-
*/
|
|
837
|
-
const FILE_TOOLS = {
|
|
838
|
-
Write: true,
|
|
839
|
-
Edit: true,
|
|
840
|
-
Read: true,
|
|
841
|
-
MultiEdit: true,
|
|
842
|
-
Update: true,
|
|
843
|
-
Create: true
|
|
844
|
-
};
|
|
845
|
-
const EDIT_TOOLS = {
|
|
846
|
-
Edit: true,
|
|
847
|
-
MultiEdit: true,
|
|
848
|
-
Update: true
|
|
849
|
-
};
|
|
850
|
-
function isOmpEditArray(value) {
|
|
851
|
-
return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && ("new_text" in entry || "old_text" in entry));
|
|
852
|
-
}
|
|
853
|
-
function normalizeToolInput(toolName, input) {
|
|
854
|
-
let out = input;
|
|
855
|
-
if (FILE_TOOLS[toolName] === true && "path" in out && !("file_path" in out)) {
|
|
856
|
-
const { path, ...rest } = out;
|
|
857
|
-
out = {
|
|
858
|
-
file_path: path,
|
|
859
|
-
...rest
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
if (EDIT_TOOLS[toolName] === true && "edits" in out && isOmpEditArray(out["edits"]) && !("new_string" in out)) {
|
|
863
|
-
const claudeEdits = out["edits"].map((entry) => ({
|
|
864
|
-
old_string: typeof entry["old_text"] === "string" ? entry["old_text"] : "",
|
|
865
|
-
new_string: typeof entry["new_text"] === "string" ? entry["new_text"] : ""
|
|
866
|
-
}));
|
|
867
|
-
out = {
|
|
868
|
-
...out,
|
|
869
|
-
edits: claudeEdits,
|
|
870
|
-
old_string: claudeEdits.map((entry) => entry["old_string"]).join("\n"),
|
|
871
|
-
new_string: claudeEdits.map((entry) => entry["new_string"]).join("\n")
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
return out;
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* ACL: normalize OMP tool names to Claude Code convention.
|
|
878
|
-
*
|
|
879
|
-
* OMP emits lowercase tool names (write, bash, read);
|
|
880
|
-
* Claude Code hook system expects capitalized (Write, Bash, Read).
|
|
881
|
-
*/
|
|
882
|
-
function normalizeToolName(name) {
|
|
883
|
-
if (name.length === 0) return name;
|
|
884
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
885
|
-
}
|
|
886
|
-
Schema.Struct({
|
|
887
|
-
code: Schema.Number,
|
|
888
|
-
stdout: Schema.String,
|
|
889
|
-
stderr: Schema.String
|
|
890
|
-
});
|
|
891
|
-
var Block = class extends Schema.TaggedClass()("Block", { reason: Schema.String }) {};
|
|
892
|
-
var Allow = class extends Schema.TaggedClass()("Allow", {}) {};
|
|
893
|
-
var Warning = class extends Schema.TaggedClass()("Warning", { message: Schema.String }) {};
|
|
894
|
-
Schema.Union(Block, Allow, Warning);
|
|
895
|
-
var Blocked = class extends Schema.TaggedClass()("Blocked", { reason: Schema.String }) {};
|
|
896
|
-
var Continue = class extends Schema.TaggedClass()("Continue", {
|
|
897
|
-
warning: Schema.optional(Schema.String),
|
|
898
|
-
updatedInput: Schema.optional(Schema.Record({
|
|
899
|
-
key: Schema.String,
|
|
900
|
-
value: Schema.Unknown
|
|
901
|
-
}))
|
|
902
|
-
}) {};
|
|
903
|
-
Schema.Union(Blocked, Continue);
|
|
904
|
-
//#endregion
|
|
905
|
-
//#region src/hook-output.acl.ts
|
|
906
|
-
const ParsedHookOutputSchema = Schema.Struct({
|
|
907
|
-
decision: Schema.optional(Schema.String),
|
|
908
|
-
reason: Schema.optional(Schema.String),
|
|
909
|
-
hookSpecificOutput: Schema.optional(Schema.Struct({
|
|
910
|
-
permissionDecision: Schema.optional(Schema.String),
|
|
911
|
-
permissionDecisionReason: Schema.optional(Schema.String),
|
|
912
|
-
updatedInput: Schema.optional(Schema.Record({
|
|
913
|
-
key: Schema.String,
|
|
914
|
-
value: Schema.Unknown
|
|
915
|
-
}))
|
|
916
|
-
}))
|
|
917
|
-
});
|
|
918
|
-
const parseHookOutput = Schema.decodeUnknownEither(Schema.parseJson(ParsedHookOutputSchema));
|
|
919
|
-
//#endregion
|
|
920
|
-
//#region src/hook-settings.acl.ts
|
|
921
|
-
const HookCommand = Schema.Struct({
|
|
922
|
-
type: Schema.Literal("command"),
|
|
923
|
-
command: Schema.String,
|
|
924
|
-
async: Schema.optional(Schema.Boolean),
|
|
925
|
-
timeout: Schema.optional(Schema.Number)
|
|
926
|
-
});
|
|
927
|
-
const HookEntry = Schema.Struct({
|
|
928
|
-
matcher: Schema.optional(Schema.String),
|
|
929
|
-
hooks: Schema.Array(HookCommand)
|
|
930
|
-
});
|
|
931
|
-
const HookGroups = Schema.Struct({
|
|
932
|
-
PreToolUse: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
933
|
-
exact: true,
|
|
934
|
-
default: () => []
|
|
935
|
-
}),
|
|
936
|
-
PostToolUse: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
937
|
-
exact: true,
|
|
938
|
-
default: () => []
|
|
939
|
-
}),
|
|
940
|
-
UserPromptSubmit: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
941
|
-
exact: true,
|
|
942
|
-
default: () => []
|
|
943
|
-
}),
|
|
944
|
-
Stop: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
945
|
-
exact: true,
|
|
946
|
-
default: () => []
|
|
947
|
-
}),
|
|
948
|
-
SessionStart: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
949
|
-
exact: true,
|
|
950
|
-
default: () => []
|
|
951
|
-
}),
|
|
952
|
-
SessionEnd: Schema.optionalWith(Schema.Array(HookEntry), {
|
|
953
|
-
exact: true,
|
|
954
|
-
default: () => []
|
|
955
|
-
})
|
|
956
|
-
});
|
|
957
|
-
const SettingsWrapped = Schema.Struct({
|
|
958
|
-
hooks: HookGroups,
|
|
959
|
-
disableAllHooks: Schema.optional(Schema.Boolean)
|
|
960
|
-
});
|
|
961
|
-
const SettingsFlat = Schema.Struct({
|
|
962
|
-
...HookGroups.fields,
|
|
963
|
-
disableAllHooks: Schema.optional(Schema.Boolean)
|
|
964
|
-
});
|
|
965
|
-
const SettingsJSON = Schema.Union(SettingsWrapped, SettingsFlat);
|
|
966
|
-
const decodeSettings = Schema.decodeUnknownEither(SettingsJSON);
|
|
967
|
-
function parseSettings(json) {
|
|
968
|
-
return Either.map(decodeSettings(json), (s) => {
|
|
969
|
-
if ("hooks" in s) return s;
|
|
970
|
-
const { disableAllHooks: d, ...hookGroups } = s;
|
|
971
|
-
return {
|
|
972
|
-
hooks: hookGroups,
|
|
973
|
-
disableAllHooks: d
|
|
974
|
-
};
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
const ALL_HOOK_EVENTS = [
|
|
978
|
-
"PreToolUse",
|
|
979
|
-
"PostToolUse",
|
|
980
|
-
"UserPromptSubmit",
|
|
981
|
-
"SessionStart",
|
|
982
|
-
"SessionEnd",
|
|
983
|
-
"Stop"
|
|
984
|
-
];
|
|
985
|
-
function mergeSettings(settings) {
|
|
986
|
-
const merged = { hooks: {
|
|
987
|
-
PreToolUse: [],
|
|
988
|
-
PostToolUse: [],
|
|
989
|
-
UserPromptSubmit: [],
|
|
990
|
-
SessionStart: [],
|
|
991
|
-
SessionEnd: [],
|
|
992
|
-
Stop: []
|
|
993
|
-
} };
|
|
994
|
-
for (const s of settings) {
|
|
995
|
-
for (const event of ALL_HOOK_EVENTS) merged.hooks[event] = (merged.hooks[event] ?? []).concat(Array.from(s.hooks[event]));
|
|
996
|
-
if (s.disableAllHooks !== void 0) merged.disableAllHooks = s.disableAllHooks;
|
|
997
|
-
}
|
|
998
|
-
return merged;
|
|
999
|
-
}
|
|
1000
|
-
function isHooksDisabled(settings) {
|
|
1001
|
-
return settings.disableAllHooks === true;
|
|
1002
|
-
}
|
|
1003
|
-
//#endregion
|
|
1004
|
-
//#region src/hook-verdict.workflow.ts
|
|
1005
|
-
var HookVerdictError = class extends Schema.TaggedError()("HookVerdictError", { raw: Schema.String }) {};
|
|
1006
|
-
var ExitBlock = class extends Schema.TaggedClass()("ExitBlock", {}) {};
|
|
1007
|
-
var ExitParse = class extends Schema.TaggedClass()("ExitParse", {}) {};
|
|
1008
|
-
var ExitOther = class extends Schema.TaggedClass()("ExitOther", {}) {};
|
|
1009
|
-
Schema.Union(ExitBlock, ExitParse, ExitOther);
|
|
1010
|
-
const classifyExit = (code) => Match.value(code).pipe(Match.when(2, () => new ExitBlock({})), Match.when(0, () => new ExitParse({})), Match.orElse(() => new ExitOther({})));
|
|
1011
|
-
const blockReason = (stderr, event) => Match.value(stderr.trim()).pipe(Match.when("", () => `Blocked by ${event} hook`), Match.orElse((trimmed) => trimmed));
|
|
1012
|
-
const decideFromParsed = (parsed, event) => Match.value(parsed.hookSpecificOutput?.permissionDecision ?? parsed.decision).pipe(Match.when("deny", () => new Block({ reason: parsed.hookSpecificOutput?.permissionDecisionReason ?? `Blocked by ${event} hook` })), Match.when("block", () => new Block({ reason: parsed.reason ?? `Blocked by ${event} hook` })), Match.orElse(() => new Allow({})));
|
|
1013
|
-
const decideFromNonStandardExit = (stderr) => Match.value(stderr.trim()).pipe(Match.when("", () => new Allow({})), Match.orElse((message) => new Warning({ message })));
|
|
1014
|
-
const interpretHookResult = (result, event) => Match.value(classifyExit(result.code)).pipe(Match.tag("ExitBlock", () => Either.right(new Block({ reason: blockReason(result.stderr, event) }))), Match.tag("ExitParse", () => Either.match(parseHookOutput(result.stdout), {
|
|
1015
|
-
onLeft: () => Either.left(new HookVerdictError({ raw: result.stdout })),
|
|
1016
|
-
onRight: (parsed) => Either.right(decideFromParsed(parsed, event))
|
|
1017
|
-
})), Match.tag("ExitOther", () => Either.right(decideFromNonStandardExit(result.stderr))), Match.exhaustive);
|
|
1018
|
-
//#endregion
|
|
1019
|
-
//#region src/hook-dispatcher.executor.ts
|
|
1020
|
-
var HookDispatcherExecutorDeps = class extends Context.Tag("HookDispatcherExecutorDeps")() {};
|
|
1021
|
-
function resolveCommandPath(command, cwd) {
|
|
1022
|
-
const trimmed = command.replace(/"\$OMP_PROJECT_DIR"|'\$OMP_PROJECT_DIR'/g, JSON.stringify(cwd)).replace(/"\$\{OMP_PROJECT_DIR\}"|'\$\{OMP_PROJECT_DIR\}'/g, JSON.stringify(cwd)).replace(/"\$CLAUDE_PROJECT_DIR"|'\$CLAUDE_PROJECT_DIR'/g, JSON.stringify(cwd)).replace(/"\$\{CLAUDE_PROJECT_DIR\}"|'\$\{CLAUDE_PROJECT_DIR\}'/g, JSON.stringify(cwd)).replace(/\$OMP_PROJECT_DIR|\$\{OMP_PROJECT_DIR\}/g, JSON.stringify(cwd)).replace(/\$CLAUDE_PROJECT_DIR|\$\{CLAUDE_PROJECT_DIR\}/g, JSON.stringify(cwd)).trim();
|
|
1023
|
-
return {
|
|
1024
|
-
cmd: "sh",
|
|
1025
|
-
args: ["-c", trimmed.startsWith("\"") && trimmed.endsWith("\"") ? trimmed.slice(1, -1) : trimmed]
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
function hookNameFromCommand(command) {
|
|
1029
|
-
return command.split(/[\\/]/).pop() ?? command;
|
|
1030
|
-
}
|
|
1031
|
-
const loadSettingsFile = Effect.fn("loadSettingsFile")(function* (path) {
|
|
1032
|
-
const content = yield* (yield* FileSystem).readFileString(path).pipe(Effect.catchAll(() => Effect.succeed("")));
|
|
1033
|
-
if (content === "") return null;
|
|
1034
|
-
const jsonOrError = Schema.decodeUnknownEither(Schema.parseJson(Schema.Record({
|
|
1035
|
-
key: Schema.String,
|
|
1036
|
-
value: Schema.Unknown
|
|
1037
|
-
})))(content);
|
|
1038
|
-
if (Either.isLeft(jsonOrError)) return null;
|
|
1039
|
-
const json = jsonOrError.right;
|
|
1040
|
-
const either = parseSettings(json);
|
|
1041
|
-
return Either.isLeft(either) ? null : either.right;
|
|
1042
|
-
});
|
|
1043
|
-
const loadSettings = Effect.fn("loadSettings")(function* (cwd) {
|
|
1044
|
-
const paths = [
|
|
1045
|
-
`${homedir()}/.claude/settings.json`,
|
|
1046
|
-
`${cwd}/.claude/settings.json`,
|
|
1047
|
-
`${cwd}/.claude/settings.local.json`,
|
|
1048
|
-
"/etc/claude-code/managed-settings.json"
|
|
1049
|
-
];
|
|
1050
|
-
return yield* loadSettingsWithPaths(paths);
|
|
1051
|
-
});
|
|
1052
|
-
const loadSettingsWithPaths = Effect.fn("loadSettingsWithPaths")(function* (paths) {
|
|
1053
|
-
const results = [];
|
|
1054
|
-
for (const p of paths) {
|
|
1055
|
-
const s = yield* loadSettingsFile(p);
|
|
1056
|
-
if (s !== null) results.push(s);
|
|
1057
|
-
}
|
|
1058
|
-
if (results.length === 0) return null;
|
|
1059
|
-
return mergeSettings(results);
|
|
1060
|
-
});
|
|
1061
|
-
const runHookScript = Effect.fn("runHookScript")(function* (command, input, cwd, timeoutMs) {
|
|
1062
|
-
const executor = yield* CommandExecutor;
|
|
1063
|
-
const { cmd, args } = resolveCommandPath(command, cwd);
|
|
1064
|
-
const stdinText = JSON.stringify(input);
|
|
1065
|
-
const hookCommand = Command.make(cmd, ...args).pipe(Command.workingDirectory(cwd), Command.env({
|
|
1066
|
-
OMP_PROJECT_DIR: cwd,
|
|
1067
|
-
CLAUDE_PROJECT_DIR: cwd
|
|
1068
|
-
}), Command.feed(stdinText), Command.stdout("pipe"), Command.stderr("pipe"));
|
|
1069
|
-
return yield* Effect.scoped(Effect.gen(function* () {
|
|
1070
|
-
const process = yield* executor.start(hookCommand);
|
|
1071
|
-
const stdout = yield* process.stdout.pipe(Stream.decodeText(), Stream.mkString);
|
|
1072
|
-
const stderr = yield* process.stderr.pipe(Stream.decodeText(), Stream.mkString);
|
|
1073
|
-
const exitCode = yield* process.exitCode;
|
|
1074
|
-
return {
|
|
1075
|
-
code: typeof exitCode === "number" ? exitCode : Number(exitCode),
|
|
1076
|
-
stdout,
|
|
1077
|
-
stderr
|
|
1078
|
-
};
|
|
1079
|
-
})).pipe(Effect.timeout(timeoutMs), Effect.catchTag("TimeoutException", () => Effect.succeed({
|
|
1080
|
-
code: -1,
|
|
1081
|
-
stdout: "",
|
|
1082
|
-
stderr: `timeout after ${timeoutMs}ms`
|
|
1083
|
-
})));
|
|
1084
|
-
});
|
|
1085
|
-
const runHooksForEvent = Effect.fn("runHooksForEvent")(function* (entries, matchValue, input, ctx, event) {
|
|
1086
|
-
const cwd = ctx.cwd;
|
|
1087
|
-
const { tel } = yield* HookDispatcherExecutorDeps;
|
|
1088
|
-
let warning;
|
|
1089
|
-
let inputModified = false;
|
|
1090
|
-
let currentInput = input;
|
|
1091
|
-
for (const entry of entries) {
|
|
1092
|
-
if (!matchesMatcher(matchValue, entry.matcher)) continue;
|
|
1093
|
-
for (const hook of entry.hooks) {
|
|
1094
|
-
const hookName = hookNameFromCommand(hook.command);
|
|
1095
|
-
const hookStart = performance.now();
|
|
1096
|
-
const timeoutMs = (hook.timeout ?? 10) * 1e3;
|
|
1097
|
-
if (hook.async) {
|
|
1098
|
-
yield* Effect.forkDaemon(runHookScript(hook.command, currentInput, cwd, timeoutMs).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
1099
|
-
const durationMs = Math.round(performance.now() - hookStart);
|
|
1100
|
-
tel("hook.executed", {
|
|
1101
|
-
hook: hookName,
|
|
1102
|
-
duration_ms: durationMs,
|
|
1103
|
-
exit_code: result.code
|
|
1104
|
-
});
|
|
1105
|
-
})), Effect.catchAll(() => Effect.sync(() => {
|
|
1106
|
-
const durationMs = Math.round(performance.now() - hookStart);
|
|
1107
|
-
tel("hook.executed", {
|
|
1108
|
-
hook: hookName,
|
|
1109
|
-
duration_ms: durationMs,
|
|
1110
|
-
exit_code: null
|
|
1111
|
-
});
|
|
1112
|
-
}))));
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
const result = yield* runHookScript(hook.command, currentInput, cwd, timeoutMs).pipe(Effect.tap((r) => Effect.sync(() => {
|
|
1116
|
-
const durationMs = Math.round(performance.now() - hookStart);
|
|
1117
|
-
tel("hook.executed", {
|
|
1118
|
-
hook: hookName,
|
|
1119
|
-
duration_ms: durationMs,
|
|
1120
|
-
exit_code: r.code
|
|
1121
|
-
});
|
|
1122
|
-
})), Effect.tapError(() => Effect.sync(() => {
|
|
1123
|
-
const durationMs = Math.round(performance.now() - hookStart);
|
|
1124
|
-
tel("hook.executed", {
|
|
1125
|
-
hook: hookName,
|
|
1126
|
-
duration_ms: durationMs,
|
|
1127
|
-
exit_code: null
|
|
1128
|
-
});
|
|
1129
|
-
})));
|
|
1130
|
-
const verdict = interpretHookResult(result, event);
|
|
1131
|
-
const decision = Either.match(verdict, {
|
|
1132
|
-
onLeft: (err) => Match.value(err).pipe(Match.tag("HookVerdictError", (e) => new Warning({ message: `Hook exited 0 but produced invalid JSON: ${e.raw.slice(0, 200)}` })), Match.exhaustive),
|
|
1133
|
-
onRight: (d) => d
|
|
1134
|
-
});
|
|
1135
|
-
const outcome = Match.value(decision).pipe(Match.tag("Block", (d) => new Blocked({ reason: d.reason })), Match.tag("Warning", (d) => new Continue({ warning: d.message })), Match.tag("Allow", () => {
|
|
1136
|
-
const either = parseHookOutput(result.stdout);
|
|
1137
|
-
return new Continue({ updatedInput: Either.isRight(either) ? either.right.hookSpecificOutput?.updatedInput : void 0 });
|
|
1138
|
-
}), Match.exhaustive);
|
|
1139
|
-
const hookExit = Match.value(outcome).pipe(Match.tag("Blocked", (b) => Option.some({
|
|
1140
|
-
block: true,
|
|
1141
|
-
reason: b.reason
|
|
1142
|
-
})), Match.tag("Continue", (c) => {
|
|
1143
|
-
if (c.warning !== void 0 && warning === void 0) warning = c.warning;
|
|
1144
|
-
if (c.updatedInput !== void 0) {
|
|
1145
|
-
currentInput = {
|
|
1146
|
-
...currentInput,
|
|
1147
|
-
...c.updatedInput
|
|
1148
|
-
};
|
|
1149
|
-
inputModified = true;
|
|
1150
|
-
}
|
|
1151
|
-
return Option.none();
|
|
1152
|
-
}), Match.exhaustive);
|
|
1153
|
-
if (Option.isSome(hookExit)) return hookExit.value;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return {
|
|
1157
|
-
...inputModified ? { updatedInput: currentInput } : {},
|
|
1158
|
-
...warning !== void 0 ? { warning } : {}
|
|
1159
|
-
};
|
|
1160
|
-
});
|
|
1161
|
-
const runPreToolUseHooks = Effect.fn("runPreToolUseHooks")(function* (settings, event, ctx) {
|
|
1162
|
-
const { tel } = yield* HookDispatcherExecutorDeps;
|
|
1163
|
-
if (isHooksDisabled(settings)) return void 0;
|
|
1164
|
-
const claudeToolName = normalizeToolName(event.toolName);
|
|
1165
|
-
const sessionData = sessionIds(() => ctx.sessionManager.getSessionId());
|
|
1166
|
-
const input = {
|
|
1167
|
-
...sessionData,
|
|
1168
|
-
tool_name: claudeToolName,
|
|
1169
|
-
tool_input: normalizeToolInput(claudeToolName, event.input),
|
|
1170
|
-
tool_call_id: event.toolCallId
|
|
1171
|
-
};
|
|
1172
|
-
const shellCommand = extractShellCommand(event.toolName, event.input);
|
|
1173
|
-
if (shellCommand !== void 0 && shellCommand.length > 0) {
|
|
1174
|
-
const bashInput = {
|
|
1175
|
-
...sessionData,
|
|
1176
|
-
tool_name: "Bash",
|
|
1177
|
-
tool_input: { command: shellCommand },
|
|
1178
|
-
tool_call_id: event.toolCallId
|
|
1179
|
-
};
|
|
1180
|
-
const bashResult = yield* runHooksForEvent(settings.hooks.PreToolUse, "Bash", bashInput, ctx, "PreToolUse");
|
|
1181
|
-
if (bashResult.block) {
|
|
1182
|
-
tel("tool_call.decision", {
|
|
1183
|
-
tool_name: claudeToolName,
|
|
1184
|
-
decision: "block",
|
|
1185
|
-
reason: bashResult.reason ?? `Bash blocked for ${shellCommand}`
|
|
1186
|
-
});
|
|
1187
|
-
return bashResult.reason === void 0 ? { block: true } : {
|
|
1188
|
-
block: true,
|
|
1189
|
-
reason: bashResult.reason
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
const result = yield* runHooksForEvent(settings.hooks.PreToolUse, claudeToolName, input, ctx, "PreToolUse");
|
|
1194
|
-
if (result.block) {
|
|
1195
|
-
tel("tool_call.decision", {
|
|
1196
|
-
tool_name: claudeToolName,
|
|
1197
|
-
decision: "block",
|
|
1198
|
-
reason: result.reason ?? void 0
|
|
1199
|
-
});
|
|
1200
|
-
return result.reason === void 0 ? { block: true } : {
|
|
1201
|
-
block: true,
|
|
1202
|
-
reason: result.reason
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
if (result.updatedInput && typeof result.updatedInput === "object" && "tool_input" in result.updatedInput && result.updatedInput["tool_input"] && typeof result.updatedInput["tool_input"] === "object") {
|
|
1206
|
-
const updated = result.updatedInput["tool_input"];
|
|
1207
|
-
for (const [key, value] of Object.entries(updated)) event.input[key] = value;
|
|
1208
|
-
}
|
|
1209
|
-
tel("tool_call.decision", {
|
|
1210
|
-
tool_name: claudeToolName,
|
|
1211
|
-
decision: "allow"
|
|
1212
|
-
});
|
|
1213
|
-
});
|
|
1214
|
-
const runPostToolUseHooks = Effect.fn("runPostToolUseHooks")(function* (settings, event, ctx) {
|
|
1215
|
-
const claudeToolName = normalizeToolName(event.toolName);
|
|
1216
|
-
if (isHooksDisabled(settings)) return void 0;
|
|
1217
|
-
const input = {
|
|
1218
|
-
...sessionIds(() => ctx.sessionManager.getSessionId()),
|
|
1219
|
-
tool_name: claudeToolName,
|
|
1220
|
-
tool_input: normalizeToolInput(claudeToolName, event.input),
|
|
1221
|
-
tool_call_id: event.toolCallId,
|
|
1222
|
-
output: event.content,
|
|
1223
|
-
is_error: event.isError ?? false
|
|
1224
|
-
};
|
|
1225
|
-
return yield* runHooksForEvent(settings.hooks.PostToolUse, claudeToolName, input, ctx, "PostToolUse");
|
|
1226
|
-
});
|
|
1227
|
-
const runUserPromptSubmitHooks = Effect.fn("runUserPromptSubmitHooks")(function* (settings, event, ctx) {
|
|
1228
|
-
const entries = settings.hooks.UserPromptSubmit;
|
|
1229
|
-
if (isHooksDisabled(settings)) return void 0;
|
|
1230
|
-
if (entries.length === 0) return void 0;
|
|
1231
|
-
const cwd = ctx.cwd;
|
|
1232
|
-
let injected = "";
|
|
1233
|
-
const input = {
|
|
1234
|
-
...sessionIds(() => ctx.sessionManager.getSessionId()),
|
|
1235
|
-
prompt: event.text,
|
|
1236
|
-
source: event.source
|
|
1237
|
-
};
|
|
1238
|
-
for (const entry of entries) for (const hook of entry.hooks) {
|
|
1239
|
-
const result = yield* runHookScript(hook.command, input, cwd, (hook.timeout ?? 10) * 1e3);
|
|
1240
|
-
if (result.code !== 0) continue;
|
|
1241
|
-
const stdout = result.stdout.trim();
|
|
1242
|
-
if (stdout.length > 0) injected += (injected.length > 0 ? "\n\n" : "") + stdout;
|
|
1243
|
-
}
|
|
1244
|
-
if (injected.length === 0) return void 0;
|
|
1245
|
-
const result = { text: `${injected}\n\n${event.text}` };
|
|
1246
|
-
if (event.images !== void 0) result.images = event.images;
|
|
1247
|
-
return result;
|
|
1248
|
-
});
|
|
1249
|
-
const runSessionStartHooks = Effect.fn("runSessionStartHooks")(function* (settings, reason, ctx) {
|
|
1250
|
-
const { tel } = yield* HookDispatcherExecutorDeps;
|
|
1251
|
-
if (isHooksDisabled(settings)) return;
|
|
1252
|
-
const entries = settings.hooks.SessionStart;
|
|
1253
|
-
if (entries.length === 0) return;
|
|
1254
|
-
const cwd = ctx.cwd;
|
|
1255
|
-
const input = {
|
|
1256
|
-
...sessionIds(() => ctx.sessionManager.getSessionId()),
|
|
1257
|
-
reason
|
|
1258
|
-
};
|
|
1259
|
-
for (const entry of entries) {
|
|
1260
|
-
if (entry.matcher && !matchesMatcher(reason, entry.matcher)) continue;
|
|
1261
|
-
for (const hook of entry.hooks) {
|
|
1262
|
-
const hookName = hookNameFromCommand(hook.command);
|
|
1263
|
-
const timeoutMs = (hook.timeout ?? 10) * 1e3;
|
|
1264
|
-
if (hook.async) {
|
|
1265
|
-
yield* Effect.forkDaemon(runHookScript(hook.command, input, cwd, timeoutMs).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
1266
|
-
tel("hook.executed", {
|
|
1267
|
-
hook: hookName,
|
|
1268
|
-
exit_code: result.code
|
|
1269
|
-
});
|
|
1270
|
-
})), Effect.catchAll(() => Effect.sync(() => {
|
|
1271
|
-
tel("hook.executed", {
|
|
1272
|
-
hook: hookName,
|
|
1273
|
-
exit_code: null
|
|
1274
|
-
});
|
|
1275
|
-
}))));
|
|
1276
|
-
continue;
|
|
1277
|
-
}
|
|
1278
|
-
yield* runHookScript(hook.command, input, cwd, timeoutMs).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
1279
|
-
tel("hook.executed", {
|
|
1280
|
-
hook: hookName,
|
|
1281
|
-
exit_code: result.code
|
|
1282
|
-
});
|
|
1283
|
-
})), Effect.catchAll(() => Effect.sync(() => {
|
|
1284
|
-
tel("hook.executed", {
|
|
1285
|
-
hook: hookName,
|
|
1286
|
-
exit_code: null
|
|
1287
|
-
});
|
|
1288
|
-
})));
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
});
|
|
1292
|
-
const runLifecycleHooks = Effect.fn("runLifecycleHooks")(function* (entries, ctx) {
|
|
1293
|
-
if (entries.length === 0) return;
|
|
1294
|
-
const { tel } = yield* HookDispatcherExecutorDeps;
|
|
1295
|
-
const cwd = ctx.cwd;
|
|
1296
|
-
const input = { ...sessionIds(() => ctx.sessionManager.getSessionId()) };
|
|
1297
|
-
for (const entry of entries) for (const hook of entry.hooks) {
|
|
1298
|
-
const hookName = hookNameFromCommand(hook.command);
|
|
1299
|
-
const timeoutMs = (hook.timeout ?? 10) * 1e3;
|
|
1300
|
-
if (hook.async) yield* Effect.forkDaemon(runHookScript(hook.command, input, cwd, timeoutMs).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
1301
|
-
tel("hook.executed", {
|
|
1302
|
-
hook: hookName,
|
|
1303
|
-
exit_code: result.code
|
|
1304
|
-
});
|
|
1305
|
-
})), Effect.catchAll(() => Effect.sync(() => {
|
|
1306
|
-
tel("hook.executed", {
|
|
1307
|
-
hook: hookName,
|
|
1308
|
-
exit_code: null
|
|
1309
|
-
});
|
|
1310
|
-
}))));
|
|
1311
|
-
else yield* runHookScript(hook.command, input, cwd, timeoutMs).pipe(Effect.tap((result) => Effect.sync(() => {
|
|
1312
|
-
tel("hook.executed", {
|
|
1313
|
-
hook: hookName,
|
|
1314
|
-
exit_code: result.code
|
|
1315
|
-
});
|
|
1316
|
-
})), Effect.catchAll(() => Effect.sync(() => {
|
|
1317
|
-
tel("hook.executed", {
|
|
1318
|
-
hook: hookName,
|
|
1319
|
-
exit_code: null
|
|
1320
|
-
});
|
|
1321
|
-
})));
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
//#endregion
|
|
1325
|
-
//#region src/runtime.ts
|
|
1326
|
-
const nodeLayer = NodeCommandExecutor.layer.pipe(Layer.provideMerge(NodeFileSystem.layer), Layer.provideMerge(PathModule.layer));
|
|
1327
|
-
const runtime = ManagedRuntime.make(nodeLayer);
|
|
1328
|
-
//#endregion
|
|
1329
|
-
//#region src/hook-dispatcher.handler.ts
|
|
1330
|
-
const HookDispatcherTask = (pi) => Layer.effectDiscard(Effect.sync(() => {
|
|
1331
|
-
const tel = createTelemetry("claude_compat", pi.logger);
|
|
1332
|
-
const telLayer = Layer.succeed(HookDispatcherExecutorDeps, { tel });
|
|
1333
|
-
const runSafe = async (effect) => {
|
|
1334
|
-
const exit = await runtime.runPromise(effect.pipe(Effect.provide(telLayer), Effect.exit));
|
|
1335
|
-
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
1336
|
-
return exit.value;
|
|
1337
|
-
};
|
|
1338
|
-
pi.on("tool_call", (event, ctx) => runSafe(Effect.gen(function* () {
|
|
1339
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1340
|
-
if (!settings) return void 0;
|
|
1341
|
-
return yield* runPreToolUseHooks(settings, event, ctx);
|
|
1342
|
-
})));
|
|
1343
|
-
pi.on("tool_result", (event, ctx) => runSafe(Effect.gen(function* () {
|
|
1344
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1345
|
-
if (!settings) return void 0;
|
|
1346
|
-
const result = yield* runPostToolUseHooks(settings, event, ctx);
|
|
1347
|
-
if (result?.block) return {
|
|
1348
|
-
isError: true,
|
|
1349
|
-
content: [{
|
|
1350
|
-
type: "text",
|
|
1351
|
-
text: result.reason ?? "Blocked by PostToolUse hook"
|
|
1352
|
-
}]
|
|
1353
|
-
};
|
|
1354
|
-
if (result?.warning) return {
|
|
1355
|
-
content: [...event.content ?? [], {
|
|
1356
|
-
type: "text",
|
|
1357
|
-
text: result.warning
|
|
1358
|
-
}],
|
|
1359
|
-
isError: event.isError
|
|
1360
|
-
};
|
|
1361
|
-
})));
|
|
1362
|
-
pi.on("input", (event, ctx) => runSafe(Effect.gen(function* () {
|
|
1363
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1364
|
-
if (!settings) return void 0;
|
|
1365
|
-
return yield* runUserPromptSubmitHooks(settings, event, ctx);
|
|
1366
|
-
})));
|
|
1367
|
-
pi.on("session_start", (_event, ctx) => runSafe(Effect.gen(function* () {
|
|
1368
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1369
|
-
if (!settings) return void 0;
|
|
1370
|
-
yield* runSessionStartHooks(settings, "start", ctx);
|
|
1371
|
-
})));
|
|
1372
|
-
pi.on("session_compact", (_event, ctx) => runSafe(Effect.gen(function* () {
|
|
1373
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1374
|
-
if (!settings) return void 0;
|
|
1375
|
-
yield* runSessionStartHooks(settings, "compact", ctx);
|
|
1376
|
-
})));
|
|
1377
|
-
pi.on("agent_start", (_event, ctx) => runSafe(Effect.gen(function* () {
|
|
1378
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1379
|
-
if (!settings) return void 0;
|
|
1380
|
-
yield* runSessionStartHooks(settings, "resume", ctx);
|
|
1381
|
-
})));
|
|
1382
|
-
pi.on("session_shutdown", (_event, ctx) => runSafe(Effect.gen(function* () {
|
|
1383
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1384
|
-
if (!settings) return void 0;
|
|
1385
|
-
if (settings.disableAllHooks) return void 0;
|
|
1386
|
-
yield* runLifecycleHooks(settings.hooks.SessionEnd, ctx);
|
|
1387
|
-
})));
|
|
1388
|
-
pi.on("session_stop", (_event, ctx) => runSafe(Effect.gen(function* () {
|
|
1389
|
-
const settings = yield* loadSettings(ctx.cwd);
|
|
1390
|
-
if (!settings) return void 0;
|
|
1391
|
-
if (settings.disableAllHooks) return void 0;
|
|
1392
|
-
yield* runLifecycleHooks(settings.hooks.Stop, ctx);
|
|
1393
|
-
})));
|
|
1394
|
-
}));
|
|
1395
|
-
//#endregion
|
|
1396
|
-
//#region src/inject-instructions.executor.ts
|
|
1397
|
-
const extractRefs = Effect.fn("extractRefs")(function* (content, baseDir, projectDir) {
|
|
1398
|
-
const fs = yield* FileSystem;
|
|
1399
|
-
const path = yield* PathModule.Path;
|
|
1400
|
-
const refs = [];
|
|
1401
|
-
for (const rawLine of content.split("\n")) {
|
|
1402
|
-
const noMarker = rawLine.trim().replace(/^[-*+]\s+/, "");
|
|
1403
|
-
if (!noMarker.startsWith("@")) continue;
|
|
1404
|
-
const ref = noMarker.slice(1).trim();
|
|
1405
|
-
if (!ref || ref.includes(" ")) continue;
|
|
1406
|
-
if (path.isAbsolute(ref)) continue;
|
|
1407
|
-
const baseResolved = path.resolve(baseDir, ref);
|
|
1408
|
-
if (baseResolved.startsWith(projectDir + "/") || baseResolved === projectDir) {
|
|
1409
|
-
const baseExists = yield* Effect.either(fs.exists(baseResolved));
|
|
1410
|
-
if (Either.isRight(baseExists) && baseExists.right) {
|
|
1411
|
-
refs.push({
|
|
1412
|
-
sourcePath: baseDir,
|
|
1413
|
-
resolvedPath: baseResolved
|
|
1414
|
-
});
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
const rootResolved = path.resolve(projectDir, ref);
|
|
1419
|
-
if ((rootResolved.startsWith(projectDir + "/") || rootResolved === projectDir) && rootResolved !== baseResolved) refs.push({
|
|
1420
|
-
sourcePath: projectDir,
|
|
1421
|
-
resolvedPath: rootResolved
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
return refs;
|
|
1425
|
-
});
|
|
1426
|
-
const loadReferencedContent = Effect.fn("loadReferencedContent")(function* (projectDir) {
|
|
1427
|
-
const fs = yield* FileSystem;
|
|
1428
|
-
const path = yield* PathModule.Path;
|
|
1429
|
-
const claudeMdPaths = [path.resolve(projectDir, "CLAUDE.md"), path.resolve(projectDir, ".claude", "CLAUDE.md")];
|
|
1430
|
-
const allRefs = [];
|
|
1431
|
-
for (const filePath of claudeMdPaths) {
|
|
1432
|
-
const content = yield* Effect.either(fs.readFileString(filePath, "utf-8"));
|
|
1433
|
-
if (Either.isRight(content)) {
|
|
1434
|
-
const refs = yield* extractRefs(content.right, path.dirname(filePath), projectDir);
|
|
1435
|
-
allRefs.push(...refs);
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1439
|
-
const uniqueRefs = [];
|
|
1440
|
-
for (const ref of allRefs) if (!seen.has(ref.resolvedPath)) {
|
|
1441
|
-
seen.add(ref.resolvedPath);
|
|
1442
|
-
uniqueRefs.push(ref);
|
|
1443
|
-
}
|
|
1444
|
-
const validRefs = [];
|
|
1445
|
-
for (const ref of uniqueRefs) {
|
|
1446
|
-
const exists = yield* Effect.either(fs.exists(ref.resolvedPath));
|
|
1447
|
-
if (Either.isRight(exists) && exists.right) validRefs.push(ref);
|
|
1448
|
-
}
|
|
1449
|
-
if (validRefs.length === 0) return "";
|
|
1450
|
-
const parts = ["# Injected @-references from CLAUDE.md"];
|
|
1451
|
-
parts.push("The following files were @-imported by CLAUDE.md and contain project rules.");
|
|
1452
|
-
parts.push("");
|
|
1453
|
-
for (const ref of validRefs) {
|
|
1454
|
-
const relativePath = ref.resolvedPath.slice(projectDir.length + 1);
|
|
1455
|
-
parts.push(`## ${relativePath}`);
|
|
1456
|
-
const refContent = yield* Effect.either(fs.readFileString(ref.resolvedPath, "utf-8"));
|
|
1457
|
-
if (Either.isRight(refContent)) parts.push(refContent.right);
|
|
1458
|
-
else parts.push(`[error reading ${relativePath}]`);
|
|
1459
|
-
parts.push("");
|
|
1460
|
-
}
|
|
1461
|
-
return parts.join("\n");
|
|
1462
|
-
});
|
|
1463
|
-
//#endregion
|
|
1464
|
-
//#region src/inject-instructions.handler.ts
|
|
1465
|
-
const InjectInstructionsTask = (pi) => Layer.effectDiscard(Effect.sync(() => {
|
|
1466
|
-
pi.on("before_agent_start", async (event, _ctx) => {
|
|
1467
|
-
const exit = await runtime.runPromise(Effect.gen(function* () {
|
|
1468
|
-
return yield* loadReferencedContent(yield* Config.string("CLAUDE_PROJECT_DIR").pipe(Config.withDefault(process.cwd())));
|
|
1469
|
-
}).pipe(Effect.exit));
|
|
1470
|
-
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
1471
|
-
const injected = exit.value;
|
|
1472
|
-
if (injected === "") return void 0;
|
|
1473
|
-
return { systemPrompt: [
|
|
1474
|
-
...event.systemPrompt,
|
|
1475
|
-
"",
|
|
1476
|
-
injected
|
|
1477
|
-
] };
|
|
1478
|
-
});
|
|
1479
|
-
}));
|
|
1480
|
-
//#endregion
|
|
1481
|
-
//#region src/index.ts
|
|
1482
|
-
function claudeCompatExtension(pi) {
|
|
1483
|
-
Effect.runSync(Effect.scoped(Layer.build(Layer.mergeAll(InjectInstructionsTask(pi), HookDispatcherTask(pi)))));
|
|
1484
|
-
ManagedRuntime.make(Layer.mergeAll(InjectInstructionsTask(pi), HookDispatcherTask(pi)));
|
|
1485
|
-
process.on("SIGINT", () => {
|
|
1486
|
-
runtime.dispose();
|
|
1487
|
-
});
|
|
1488
|
-
process.on("SIGTERM", () => {
|
|
1489
|
-
runtime.dispose();
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
//#endregion
|
|
1493
|
-
export { claudeCompatExtension as default };
|
|
1
|
+
const e=(e,t)=>{e(e=>{e.setTimeout(()=>{t()},0)});let n=()=>{t().then(({default:e})=>e.dispose())};process.on(`SIGINT`,n),process.on(`SIGTERM`,n)},t=e=>{let t=async e=>{let[t,n]=await Promise.all([import(`./runtime-CMq7fttJ.js`).then(e=>e.default),import(`./esm-DBTIrgAu.js`)]),{Cause:r,Effect:i,Exit:a}=n,o=e.pipe(i.exit),s=await t.runPromise(o);if(a.isFailure(s))throw r.squash(s.cause);return s.value};e.on(`tool_call`,async(e,n)=>{let{loadSettings:r,runPreToolUseHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let t=yield*r(n.cwd);if(t)return yield*i(t,e,n)}))}),e.on(`tool_result`,async(e,n)=>{let{loadSettings:r,runPostToolUseHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let t=yield*r(n.cwd);if(!t)return;let a=yield*i(t,e,n);if(a?.block)return{isError:!0,content:[{type:`text`,text:a.reason??`Blocked by PostToolUse hook`}]};if(a?.warning)return{content:[...e.content??[],{type:`text`,text:a.warning}],isError:e.isError}}))}),e.on(`input`,async(e,n)=>{let{loadSettings:r,runUserPromptSubmitHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let t=yield*r(n.cwd);if(t)return yield*i(t,e,n)}))}),e.on(`session_start`,async(e,n)=>{let{loadSettings:r,runSessionStartHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let e=yield*r(n.cwd);e&&(yield*i(e,`start`,n))}))}),e.on(`session_compact`,async(e,n)=>{let{loadSettings:r,runSessionStartHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let e=yield*r(n.cwd);e&&(yield*i(e,`compact`,n))}))}),e.on(`agent_start`,async(e,n)=>{let{loadSettings:r,runSessionStartHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let e=yield*r(n.cwd);e&&(yield*i(e,`resume`,n))}))}),e.on(`session_shutdown`,async(e,n)=>{let{loadSettings:r,runLifecycleHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let e=yield*r(n.cwd);e&&(e.disableAllHooks||(yield*i(e.hooks.SessionEnd,n)))}))}),e.on(`session_stop`,async(e,n)=>{let{loadSettings:r,runLifecycleHooks:i}=await import(`./hook-dispatcher.executor-B6Jon79H.js`),{Effect:a}=await import(`./esm-DBTIrgAu.js`);return t(a.gen(function*(){let e=yield*r(n.cwd);e&&(e.disableAllHooks||(yield*i(e.hooks.Stop,n)))}))})},n=e=>{e.on(`before_agent_start`,async(e,t)=>{let n=await import(`./runtime-CMq7fttJ.js`).then(e=>e.default),{loadReferencedContent:r}=await import(`./inject-instructions.executor-C0s4_7XL.js`),{Cause:i,Config:a,Effect:o,Exit:s}=await import(`./esm-DBTIrgAu.js`),c=await n.runPromise(o.gen(function*(){let e=yield*a.string(`CLAUDE_PROJECT_DIR`).pipe(a.withDefault(process.cwd()));return yield*r(e)}).pipe(o.exit));if(s.isFailure(c))throw i.squash(c.cause);let l=c.value;if(l!==``)return{systemPrompt:[...e.systemPrompt,``,l]}})};function r(r){t(r),n(r),e(e=>r.on(`session_start`,(t,n)=>e(n)),()=>import(`./runtime-CMq7fttJ.js`))}export{r as default};
|