@karmaniverous/jeeves-server-openclaw 0.3.1 → 0.4.1

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/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
- import path, { join, dirname, resolve } from 'node:path';
2
+ import 'vm';
3
3
  import fs, { existsSync, mkdirSync, writeFileSync, readFileSync, renameSync, cpSync, rmSync } from 'node:fs';
4
+ import path, { join, dirname, resolve } from 'node:path';
4
5
  import require$$0$3 from 'path';
5
6
  import require$$0$2 from 'fs';
6
7
  import require$$0 from 'constants';
@@ -12,8 +13,1214 @@ import { fileURLToPath } from 'node:url';
12
13
  import 'node:fs/promises';
13
14
  import process$1 from 'node:process';
14
15
  import { execSync } from 'node:child_process';
15
- import { createHmac } from 'node:crypto';
16
16
  import { homedir } from 'node:os';
17
+ import { createHmac } from 'node:crypto';
18
+
19
+ /**
20
+ * @implements {IHooks}
21
+ */
22
+ class Hooks {
23
+ /**
24
+ * @callback HookCallback
25
+ * @this {*|Jsep} this
26
+ * @param {Jsep} env
27
+ * @returns: void
28
+ */
29
+ /**
30
+ * Adds the given callback to the list of callbacks for the given hook.
31
+ *
32
+ * The callback will be invoked when the hook it is registered for is run.
33
+ *
34
+ * One callback function can be registered to multiple hooks and the same hook multiple times.
35
+ *
36
+ * @param {string|object} name The name of the hook, or an object of callbacks keyed by name
37
+ * @param {HookCallback|boolean} callback The callback function which is given environment variables.
38
+ * @param {?boolean} [first=false] Will add the hook to the top of the list (defaults to the bottom)
39
+ * @public
40
+ */
41
+ add(name, callback, first) {
42
+ if (typeof arguments[0] != 'string') {
43
+ // Multiple hook callbacks, keyed by name
44
+ for (let name in arguments[0]) {
45
+ this.add(name, arguments[0][name], arguments[1]);
46
+ }
47
+ } else {
48
+ (Array.isArray(name) ? name : [name]).forEach(function (name) {
49
+ this[name] = this[name] || [];
50
+ if (callback) {
51
+ this[name][first ? 'unshift' : 'push'](callback);
52
+ }
53
+ }, this);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Runs a hook invoking all registered callbacks with the given environment variables.
59
+ *
60
+ * Callbacks will be invoked synchronously and in the order in which they were registered.
61
+ *
62
+ * @param {string} name The name of the hook.
63
+ * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.
64
+ * @public
65
+ */
66
+ run(name, env) {
67
+ this[name] = this[name] || [];
68
+ this[name].forEach(function (callback) {
69
+ callback.call(env && env.context ? env.context : env, env);
70
+ });
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @implements {IPlugins}
76
+ */
77
+ class Plugins {
78
+ constructor(jsep) {
79
+ this.jsep = jsep;
80
+ this.registered = {};
81
+ }
82
+
83
+ /**
84
+ * @callback PluginSetup
85
+ * @this {Jsep} jsep
86
+ * @returns: void
87
+ */
88
+ /**
89
+ * Adds the given plugin(s) to the registry
90
+ *
91
+ * @param {object} plugins
92
+ * @param {string} plugins.name The name of the plugin
93
+ * @param {PluginSetup} plugins.init The init function
94
+ * @public
95
+ */
96
+ register(...plugins) {
97
+ plugins.forEach(plugin => {
98
+ if (typeof plugin !== 'object' || !plugin.name || !plugin.init) {
99
+ throw new Error('Invalid JSEP plugin format');
100
+ }
101
+ if (this.registered[plugin.name]) {
102
+ // already registered. Ignore.
103
+ return;
104
+ }
105
+ plugin.init(this.jsep);
106
+ this.registered[plugin.name] = plugin;
107
+ });
108
+ }
109
+ }
110
+
111
+ // JavaScript Expression Parser (JSEP) 1.4.0
112
+
113
+ class Jsep {
114
+ /**
115
+ * @returns {string}
116
+ */
117
+ static get version() {
118
+ // To be filled in by the template
119
+ return '1.4.0';
120
+ }
121
+
122
+ /**
123
+ * @returns {string}
124
+ */
125
+ static toString() {
126
+ return 'JavaScript Expression Parser (JSEP) v' + Jsep.version;
127
+ }
128
+ // ==================== CONFIG ================================
129
+ /**
130
+ * @method addUnaryOp
131
+ * @param {string} op_name The name of the unary op to add
132
+ * @returns {Jsep}
133
+ */
134
+ static addUnaryOp(op_name) {
135
+ Jsep.max_unop_len = Math.max(op_name.length, Jsep.max_unop_len);
136
+ Jsep.unary_ops[op_name] = 1;
137
+ return Jsep;
138
+ }
139
+
140
+ /**
141
+ * @method jsep.addBinaryOp
142
+ * @param {string} op_name The name of the binary op to add
143
+ * @param {number} precedence The precedence of the binary op (can be a float). Higher number = higher precedence
144
+ * @param {boolean} [isRightAssociative=false] whether operator is right-associative
145
+ * @returns {Jsep}
146
+ */
147
+ static addBinaryOp(op_name, precedence, isRightAssociative) {
148
+ Jsep.max_binop_len = Math.max(op_name.length, Jsep.max_binop_len);
149
+ Jsep.binary_ops[op_name] = precedence;
150
+ if (isRightAssociative) {
151
+ Jsep.right_associative.add(op_name);
152
+ } else {
153
+ Jsep.right_associative.delete(op_name);
154
+ }
155
+ return Jsep;
156
+ }
157
+
158
+ /**
159
+ * @method addIdentifierChar
160
+ * @param {string} char The additional character to treat as a valid part of an identifier
161
+ * @returns {Jsep}
162
+ */
163
+ static addIdentifierChar(char) {
164
+ Jsep.additional_identifier_chars.add(char);
165
+ return Jsep;
166
+ }
167
+
168
+ /**
169
+ * @method addLiteral
170
+ * @param {string} literal_name The name of the literal to add
171
+ * @param {*} literal_value The value of the literal
172
+ * @returns {Jsep}
173
+ */
174
+ static addLiteral(literal_name, literal_value) {
175
+ Jsep.literals[literal_name] = literal_value;
176
+ return Jsep;
177
+ }
178
+
179
+ /**
180
+ * @method removeUnaryOp
181
+ * @param {string} op_name The name of the unary op to remove
182
+ * @returns {Jsep}
183
+ */
184
+ static removeUnaryOp(op_name) {
185
+ delete Jsep.unary_ops[op_name];
186
+ if (op_name.length === Jsep.max_unop_len) {
187
+ Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
188
+ }
189
+ return Jsep;
190
+ }
191
+
192
+ /**
193
+ * @method removeAllUnaryOps
194
+ * @returns {Jsep}
195
+ */
196
+ static removeAllUnaryOps() {
197
+ Jsep.unary_ops = {};
198
+ Jsep.max_unop_len = 0;
199
+ return Jsep;
200
+ }
201
+
202
+ /**
203
+ * @method removeIdentifierChar
204
+ * @param {string} char The additional character to stop treating as a valid part of an identifier
205
+ * @returns {Jsep}
206
+ */
207
+ static removeIdentifierChar(char) {
208
+ Jsep.additional_identifier_chars.delete(char);
209
+ return Jsep;
210
+ }
211
+
212
+ /**
213
+ * @method removeBinaryOp
214
+ * @param {string} op_name The name of the binary op to remove
215
+ * @returns {Jsep}
216
+ */
217
+ static removeBinaryOp(op_name) {
218
+ delete Jsep.binary_ops[op_name];
219
+ if (op_name.length === Jsep.max_binop_len) {
220
+ Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
221
+ }
222
+ Jsep.right_associative.delete(op_name);
223
+ return Jsep;
224
+ }
225
+
226
+ /**
227
+ * @method removeAllBinaryOps
228
+ * @returns {Jsep}
229
+ */
230
+ static removeAllBinaryOps() {
231
+ Jsep.binary_ops = {};
232
+ Jsep.max_binop_len = 0;
233
+ return Jsep;
234
+ }
235
+
236
+ /**
237
+ * @method removeLiteral
238
+ * @param {string} literal_name The name of the literal to remove
239
+ * @returns {Jsep}
240
+ */
241
+ static removeLiteral(literal_name) {
242
+ delete Jsep.literals[literal_name];
243
+ return Jsep;
244
+ }
245
+
246
+ /**
247
+ * @method removeAllLiterals
248
+ * @returns {Jsep}
249
+ */
250
+ static removeAllLiterals() {
251
+ Jsep.literals = {};
252
+ return Jsep;
253
+ }
254
+ // ==================== END CONFIG ============================
255
+
256
+ /**
257
+ * @returns {string}
258
+ */
259
+ get char() {
260
+ return this.expr.charAt(this.index);
261
+ }
262
+
263
+ /**
264
+ * @returns {number}
265
+ */
266
+ get code() {
267
+ return this.expr.charCodeAt(this.index);
268
+ }
269
+ /**
270
+ * @param {string} expr a string with the passed in express
271
+ * @returns Jsep
272
+ */
273
+ constructor(expr) {
274
+ // `index` stores the character number we are currently at
275
+ // All of the gobbles below will modify `index` as we move along
276
+ this.expr = expr;
277
+ this.index = 0;
278
+ }
279
+
280
+ /**
281
+ * static top-level parser
282
+ * @returns {jsep.Expression}
283
+ */
284
+ static parse(expr) {
285
+ return new Jsep(expr).parse();
286
+ }
287
+
288
+ /**
289
+ * Get the longest key length of any object
290
+ * @param {object} obj
291
+ * @returns {number}
292
+ */
293
+ static getMaxKeyLen(obj) {
294
+ return Math.max(0, ...Object.keys(obj).map(k => k.length));
295
+ }
296
+
297
+ /**
298
+ * `ch` is a character code in the next three functions
299
+ * @param {number} ch
300
+ * @returns {boolean}
301
+ */
302
+ static isDecimalDigit(ch) {
303
+ return ch >= 48 && ch <= 57; // 0...9
304
+ }
305
+
306
+ /**
307
+ * Returns the precedence of a binary operator or `0` if it isn't a binary operator. Can be float.
308
+ * @param {string} op_val
309
+ * @returns {number}
310
+ */
311
+ static binaryPrecedence(op_val) {
312
+ return Jsep.binary_ops[op_val] || 0;
313
+ }
314
+
315
+ /**
316
+ * Looks for start of identifier
317
+ * @param {number} ch
318
+ * @returns {boolean}
319
+ */
320
+ static isIdentifierStart(ch) {
321
+ return ch >= 65 && ch <= 90 ||
322
+ // A...Z
323
+ ch >= 97 && ch <= 122 ||
324
+ // a...z
325
+ ch >= 128 && !Jsep.binary_ops[String.fromCharCode(ch)] ||
326
+ // any non-ASCII that is not an operator
327
+ Jsep.additional_identifier_chars.has(String.fromCharCode(ch)); // additional characters
328
+ }
329
+
330
+ /**
331
+ * @param {number} ch
332
+ * @returns {boolean}
333
+ */
334
+ static isIdentifierPart(ch) {
335
+ return Jsep.isIdentifierStart(ch) || Jsep.isDecimalDigit(ch);
336
+ }
337
+
338
+ /**
339
+ * throw error at index of the expression
340
+ * @param {string} message
341
+ * @throws
342
+ */
343
+ throwError(message) {
344
+ const error = new Error(message + ' at character ' + this.index);
345
+ error.index = this.index;
346
+ error.description = message;
347
+ throw error;
348
+ }
349
+
350
+ /**
351
+ * Run a given hook
352
+ * @param {string} name
353
+ * @param {jsep.Expression|false} [node]
354
+ * @returns {?jsep.Expression}
355
+ */
356
+ runHook(name, node) {
357
+ if (Jsep.hooks[name]) {
358
+ const env = {
359
+ context: this,
360
+ node
361
+ };
362
+ Jsep.hooks.run(name, env);
363
+ return env.node;
364
+ }
365
+ return node;
366
+ }
367
+
368
+ /**
369
+ * Runs a given hook until one returns a node
370
+ * @param {string} name
371
+ * @returns {?jsep.Expression}
372
+ */
373
+ searchHook(name) {
374
+ if (Jsep.hooks[name]) {
375
+ const env = {
376
+ context: this
377
+ };
378
+ Jsep.hooks[name].find(function (callback) {
379
+ callback.call(env.context, env);
380
+ return env.node;
381
+ });
382
+ return env.node;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Push `index` up to the next non-space character
388
+ */
389
+ gobbleSpaces() {
390
+ let ch = this.code;
391
+ // Whitespace
392
+ while (ch === Jsep.SPACE_CODE || ch === Jsep.TAB_CODE || ch === Jsep.LF_CODE || ch === Jsep.CR_CODE) {
393
+ ch = this.expr.charCodeAt(++this.index);
394
+ }
395
+ this.runHook('gobble-spaces');
396
+ }
397
+
398
+ /**
399
+ * Top-level method to parse all expressions and returns compound or single node
400
+ * @returns {jsep.Expression}
401
+ */
402
+ parse() {
403
+ this.runHook('before-all');
404
+ const nodes = this.gobbleExpressions();
405
+
406
+ // If there's only one expression just try returning the expression
407
+ const node = nodes.length === 1 ? nodes[0] : {
408
+ type: Jsep.COMPOUND,
409
+ body: nodes
410
+ };
411
+ return this.runHook('after-all', node);
412
+ }
413
+
414
+ /**
415
+ * top-level parser (but can be reused within as well)
416
+ * @param {number} [untilICode]
417
+ * @returns {jsep.Expression[]}
418
+ */
419
+ gobbleExpressions(untilICode) {
420
+ let nodes = [],
421
+ ch_i,
422
+ node;
423
+ while (this.index < this.expr.length) {
424
+ ch_i = this.code;
425
+
426
+ // Expressions can be separated by semicolons, commas, or just inferred without any
427
+ // separators
428
+ if (ch_i === Jsep.SEMCOL_CODE || ch_i === Jsep.COMMA_CODE) {
429
+ this.index++; // ignore separators
430
+ } else {
431
+ // Try to gobble each expression individually
432
+ if (node = this.gobbleExpression()) {
433
+ nodes.push(node);
434
+ // If we weren't able to find a binary expression and are out of room, then
435
+ // the expression passed in probably has too much
436
+ } else if (this.index < this.expr.length) {
437
+ if (ch_i === untilICode) {
438
+ break;
439
+ }
440
+ this.throwError('Unexpected "' + this.char + '"');
441
+ }
442
+ }
443
+ }
444
+ return nodes;
445
+ }
446
+
447
+ /**
448
+ * The main parsing function.
449
+ * @returns {?jsep.Expression}
450
+ */
451
+ gobbleExpression() {
452
+ const node = this.searchHook('gobble-expression') || this.gobbleBinaryExpression();
453
+ this.gobbleSpaces();
454
+ return this.runHook('after-expression', node);
455
+ }
456
+
457
+ /**
458
+ * Search for the operation portion of the string (e.g. `+`, `===`)
459
+ * Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
460
+ * and move down from 3 to 2 to 1 character until a matching binary operation is found
461
+ * then, return that binary operation
462
+ * @returns {string|boolean}
463
+ */
464
+ gobbleBinaryOp() {
465
+ this.gobbleSpaces();
466
+ let to_check = this.expr.substr(this.index, Jsep.max_binop_len);
467
+ let tc_len = to_check.length;
468
+ while (tc_len > 0) {
469
+ // Don't accept a binary op when it is an identifier.
470
+ // Binary ops that start with a identifier-valid character must be followed
471
+ // by a non identifier-part valid character
472
+ if (Jsep.binary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
473
+ this.index += tc_len;
474
+ return to_check;
475
+ }
476
+ to_check = to_check.substr(0, --tc_len);
477
+ }
478
+ return false;
479
+ }
480
+
481
+ /**
482
+ * This function is responsible for gobbling an individual expression,
483
+ * e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
484
+ * @returns {?jsep.BinaryExpression}
485
+ */
486
+ gobbleBinaryExpression() {
487
+ let node, biop, prec, stack, biop_info, left, right, i, cur_biop;
488
+
489
+ // First, try to get the leftmost thing
490
+ // Then, check to see if there's a binary operator operating on that leftmost thing
491
+ // Don't gobbleBinaryOp without a left-hand-side
492
+ left = this.gobbleToken();
493
+ if (!left) {
494
+ return left;
495
+ }
496
+ biop = this.gobbleBinaryOp();
497
+
498
+ // If there wasn't a binary operator, just return the leftmost node
499
+ if (!biop) {
500
+ return left;
501
+ }
502
+
503
+ // Otherwise, we need to start a stack to properly place the binary operations in their
504
+ // precedence structure
505
+ biop_info = {
506
+ value: biop,
507
+ prec: Jsep.binaryPrecedence(biop),
508
+ right_a: Jsep.right_associative.has(biop)
509
+ };
510
+ right = this.gobbleToken();
511
+ if (!right) {
512
+ this.throwError("Expected expression after " + biop);
513
+ }
514
+ stack = [left, biop_info, right];
515
+
516
+ // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
517
+ while (biop = this.gobbleBinaryOp()) {
518
+ prec = Jsep.binaryPrecedence(biop);
519
+ if (prec === 0) {
520
+ this.index -= biop.length;
521
+ break;
522
+ }
523
+ biop_info = {
524
+ value: biop,
525
+ prec,
526
+ right_a: Jsep.right_associative.has(biop)
527
+ };
528
+ cur_biop = biop;
529
+
530
+ // Reduce: make a binary expression from the three topmost entries.
531
+ const comparePrev = prev => biop_info.right_a && prev.right_a ? prec > prev.prec : prec <= prev.prec;
532
+ while (stack.length > 2 && comparePrev(stack[stack.length - 2])) {
533
+ right = stack.pop();
534
+ biop = stack.pop().value;
535
+ left = stack.pop();
536
+ node = {
537
+ type: Jsep.BINARY_EXP,
538
+ operator: biop,
539
+ left,
540
+ right
541
+ };
542
+ stack.push(node);
543
+ }
544
+ node = this.gobbleToken();
545
+ if (!node) {
546
+ this.throwError("Expected expression after " + cur_biop);
547
+ }
548
+ stack.push(biop_info, node);
549
+ }
550
+ i = stack.length - 1;
551
+ node = stack[i];
552
+ while (i > 1) {
553
+ node = {
554
+ type: Jsep.BINARY_EXP,
555
+ operator: stack[i - 1].value,
556
+ left: stack[i - 2],
557
+ right: node
558
+ };
559
+ i -= 2;
560
+ }
561
+ return node;
562
+ }
563
+
564
+ /**
565
+ * An individual part of a binary expression:
566
+ * e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
567
+ * @returns {boolean|jsep.Expression}
568
+ */
569
+ gobbleToken() {
570
+ let ch, to_check, tc_len, node;
571
+ this.gobbleSpaces();
572
+ node = this.searchHook('gobble-token');
573
+ if (node) {
574
+ return this.runHook('after-token', node);
575
+ }
576
+ ch = this.code;
577
+ if (Jsep.isDecimalDigit(ch) || ch === Jsep.PERIOD_CODE) {
578
+ // Char code 46 is a dot `.` which can start off a numeric literal
579
+ return this.gobbleNumericLiteral();
580
+ }
581
+ if (ch === Jsep.SQUOTE_CODE || ch === Jsep.DQUOTE_CODE) {
582
+ // Single or double quotes
583
+ node = this.gobbleStringLiteral();
584
+ } else if (ch === Jsep.OBRACK_CODE) {
585
+ node = this.gobbleArray();
586
+ } else {
587
+ to_check = this.expr.substr(this.index, Jsep.max_unop_len);
588
+ tc_len = to_check.length;
589
+ while (tc_len > 0) {
590
+ // Don't accept an unary op when it is an identifier.
591
+ // Unary ops that start with a identifier-valid character must be followed
592
+ // by a non identifier-part valid character
593
+ if (Jsep.unary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
594
+ this.index += tc_len;
595
+ const argument = this.gobbleToken();
596
+ if (!argument) {
597
+ this.throwError('missing unaryOp argument');
598
+ }
599
+ return this.runHook('after-token', {
600
+ type: Jsep.UNARY_EXP,
601
+ operator: to_check,
602
+ argument,
603
+ prefix: true
604
+ });
605
+ }
606
+ to_check = to_check.substr(0, --tc_len);
607
+ }
608
+ if (Jsep.isIdentifierStart(ch)) {
609
+ node = this.gobbleIdentifier();
610
+ if (Jsep.literals.hasOwnProperty(node.name)) {
611
+ node = {
612
+ type: Jsep.LITERAL,
613
+ value: Jsep.literals[node.name],
614
+ raw: node.name
615
+ };
616
+ } else if (node.name === Jsep.this_str) {
617
+ node = {
618
+ type: Jsep.THIS_EXP
619
+ };
620
+ }
621
+ } else if (ch === Jsep.OPAREN_CODE) {
622
+ // open parenthesis
623
+ node = this.gobbleGroup();
624
+ }
625
+ }
626
+ if (!node) {
627
+ return this.runHook('after-token', false);
628
+ }
629
+ node = this.gobbleTokenProperty(node);
630
+ return this.runHook('after-token', node);
631
+ }
632
+
633
+ /**
634
+ * Gobble properties of of identifiers/strings/arrays/groups.
635
+ * e.g. `foo`, `bar.baz`, `foo['bar'].baz`
636
+ * It also gobbles function calls:
637
+ * e.g. `Math.acos(obj.angle)`
638
+ * @param {jsep.Expression} node
639
+ * @returns {jsep.Expression}
640
+ */
641
+ gobbleTokenProperty(node) {
642
+ this.gobbleSpaces();
643
+ let ch = this.code;
644
+ while (ch === Jsep.PERIOD_CODE || ch === Jsep.OBRACK_CODE || ch === Jsep.OPAREN_CODE || ch === Jsep.QUMARK_CODE) {
645
+ let optional;
646
+ if (ch === Jsep.QUMARK_CODE) {
647
+ if (this.expr.charCodeAt(this.index + 1) !== Jsep.PERIOD_CODE) {
648
+ break;
649
+ }
650
+ optional = true;
651
+ this.index += 2;
652
+ this.gobbleSpaces();
653
+ ch = this.code;
654
+ }
655
+ this.index++;
656
+ if (ch === Jsep.OBRACK_CODE) {
657
+ node = {
658
+ type: Jsep.MEMBER_EXP,
659
+ computed: true,
660
+ object: node,
661
+ property: this.gobbleExpression()
662
+ };
663
+ if (!node.property) {
664
+ this.throwError('Unexpected "' + this.char + '"');
665
+ }
666
+ this.gobbleSpaces();
667
+ ch = this.code;
668
+ if (ch !== Jsep.CBRACK_CODE) {
669
+ this.throwError('Unclosed [');
670
+ }
671
+ this.index++;
672
+ } else if (ch === Jsep.OPAREN_CODE) {
673
+ // A function call is being made; gobble all the arguments
674
+ node = {
675
+ type: Jsep.CALL_EXP,
676
+ 'arguments': this.gobbleArguments(Jsep.CPAREN_CODE),
677
+ callee: node
678
+ };
679
+ } else if (ch === Jsep.PERIOD_CODE || optional) {
680
+ if (optional) {
681
+ this.index--;
682
+ }
683
+ this.gobbleSpaces();
684
+ node = {
685
+ type: Jsep.MEMBER_EXP,
686
+ computed: false,
687
+ object: node,
688
+ property: this.gobbleIdentifier()
689
+ };
690
+ }
691
+ if (optional) {
692
+ node.optional = true;
693
+ } // else leave undefined for compatibility with esprima
694
+
695
+ this.gobbleSpaces();
696
+ ch = this.code;
697
+ }
698
+ return node;
699
+ }
700
+
701
+ /**
702
+ * Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
703
+ * keep track of everything in the numeric literal and then calling `parseFloat` on that string
704
+ * @returns {jsep.Literal}
705
+ */
706
+ gobbleNumericLiteral() {
707
+ let number = '',
708
+ ch,
709
+ chCode;
710
+ while (Jsep.isDecimalDigit(this.code)) {
711
+ number += this.expr.charAt(this.index++);
712
+ }
713
+ if (this.code === Jsep.PERIOD_CODE) {
714
+ // can start with a decimal marker
715
+ number += this.expr.charAt(this.index++);
716
+ while (Jsep.isDecimalDigit(this.code)) {
717
+ number += this.expr.charAt(this.index++);
718
+ }
719
+ }
720
+ ch = this.char;
721
+ if (ch === 'e' || ch === 'E') {
722
+ // exponent marker
723
+ number += this.expr.charAt(this.index++);
724
+ ch = this.char;
725
+ if (ch === '+' || ch === '-') {
726
+ // exponent sign
727
+ number += this.expr.charAt(this.index++);
728
+ }
729
+ while (Jsep.isDecimalDigit(this.code)) {
730
+ // exponent itself
731
+ number += this.expr.charAt(this.index++);
732
+ }
733
+ if (!Jsep.isDecimalDigit(this.expr.charCodeAt(this.index - 1))) {
734
+ this.throwError('Expected exponent (' + number + this.char + ')');
735
+ }
736
+ }
737
+ chCode = this.code;
738
+
739
+ // Check to make sure this isn't a variable name that start with a number (123abc)
740
+ if (Jsep.isIdentifierStart(chCode)) {
741
+ this.throwError('Variable names cannot start with a number (' + number + this.char + ')');
742
+ } else if (chCode === Jsep.PERIOD_CODE || number.length === 1 && number.charCodeAt(0) === Jsep.PERIOD_CODE) {
743
+ this.throwError('Unexpected period');
744
+ }
745
+ return {
746
+ type: Jsep.LITERAL,
747
+ value: parseFloat(number),
748
+ raw: number
749
+ };
750
+ }
751
+
752
+ /**
753
+ * Parses a string literal, staring with single or double quotes with basic support for escape codes
754
+ * e.g. `"hello world"`, `'this is\nJSEP'`
755
+ * @returns {jsep.Literal}
756
+ */
757
+ gobbleStringLiteral() {
758
+ let str = '';
759
+ const startIndex = this.index;
760
+ const quote = this.expr.charAt(this.index++);
761
+ let closed = false;
762
+ while (this.index < this.expr.length) {
763
+ let ch = this.expr.charAt(this.index++);
764
+ if (ch === quote) {
765
+ closed = true;
766
+ break;
767
+ } else if (ch === '\\') {
768
+ // Check for all of the common escape codes
769
+ ch = this.expr.charAt(this.index++);
770
+ switch (ch) {
771
+ case 'n':
772
+ str += '\n';
773
+ break;
774
+ case 'r':
775
+ str += '\r';
776
+ break;
777
+ case 't':
778
+ str += '\t';
779
+ break;
780
+ case 'b':
781
+ str += '\b';
782
+ break;
783
+ case 'f':
784
+ str += '\f';
785
+ break;
786
+ case 'v':
787
+ str += '\x0B';
788
+ break;
789
+ default:
790
+ str += ch;
791
+ }
792
+ } else {
793
+ str += ch;
794
+ }
795
+ }
796
+ if (!closed) {
797
+ this.throwError('Unclosed quote after "' + str + '"');
798
+ }
799
+ return {
800
+ type: Jsep.LITERAL,
801
+ value: str,
802
+ raw: this.expr.substring(startIndex, this.index)
803
+ };
804
+ }
805
+
806
+ /**
807
+ * Gobbles only identifiers
808
+ * e.g.: `foo`, `_value`, `$x1`
809
+ * Also, this function checks if that identifier is a literal:
810
+ * (e.g. `true`, `false`, `null`) or `this`
811
+ * @returns {jsep.Identifier}
812
+ */
813
+ gobbleIdentifier() {
814
+ let ch = this.code,
815
+ start = this.index;
816
+ if (Jsep.isIdentifierStart(ch)) {
817
+ this.index++;
818
+ } else {
819
+ this.throwError('Unexpected ' + this.char);
820
+ }
821
+ while (this.index < this.expr.length) {
822
+ ch = this.code;
823
+ if (Jsep.isIdentifierPart(ch)) {
824
+ this.index++;
825
+ } else {
826
+ break;
827
+ }
828
+ }
829
+ return {
830
+ type: Jsep.IDENTIFIER,
831
+ name: this.expr.slice(start, this.index)
832
+ };
833
+ }
834
+
835
+ /**
836
+ * Gobbles a list of arguments within the context of a function call
837
+ * or array literal. This function also assumes that the opening character
838
+ * `(` or `[` has already been gobbled, and gobbles expressions and commas
839
+ * until the terminator character `)` or `]` is encountered.
840
+ * e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
841
+ * @param {number} termination
842
+ * @returns {jsep.Expression[]}
843
+ */
844
+ gobbleArguments(termination) {
845
+ const args = [];
846
+ let closed = false;
847
+ let separator_count = 0;
848
+ while (this.index < this.expr.length) {
849
+ this.gobbleSpaces();
850
+ let ch_i = this.code;
851
+ if (ch_i === termination) {
852
+ // done parsing
853
+ closed = true;
854
+ this.index++;
855
+ if (termination === Jsep.CPAREN_CODE && separator_count && separator_count >= args.length) {
856
+ this.throwError('Unexpected token ' + String.fromCharCode(termination));
857
+ }
858
+ break;
859
+ } else if (ch_i === Jsep.COMMA_CODE) {
860
+ // between expressions
861
+ this.index++;
862
+ separator_count++;
863
+ if (separator_count !== args.length) {
864
+ // missing argument
865
+ if (termination === Jsep.CPAREN_CODE) {
866
+ this.throwError('Unexpected token ,');
867
+ } else if (termination === Jsep.CBRACK_CODE) {
868
+ for (let arg = args.length; arg < separator_count; arg++) {
869
+ args.push(null);
870
+ }
871
+ }
872
+ }
873
+ } else if (args.length !== separator_count && separator_count !== 0) {
874
+ // NOTE: `&& separator_count !== 0` allows for either all commas, or all spaces as arguments
875
+ this.throwError('Expected comma');
876
+ } else {
877
+ const node = this.gobbleExpression();
878
+ if (!node || node.type === Jsep.COMPOUND) {
879
+ this.throwError('Expected comma');
880
+ }
881
+ args.push(node);
882
+ }
883
+ }
884
+ if (!closed) {
885
+ this.throwError('Expected ' + String.fromCharCode(termination));
886
+ }
887
+ return args;
888
+ }
889
+
890
+ /**
891
+ * Responsible for parsing a group of things within parentheses `()`
892
+ * that have no identifier in front (so not a function call)
893
+ * This function assumes that it needs to gobble the opening parenthesis
894
+ * and then tries to gobble everything within that parenthesis, assuming
895
+ * that the next thing it should see is the close parenthesis. If not,
896
+ * then the expression probably doesn't have a `)`
897
+ * @returns {boolean|jsep.Expression}
898
+ */
899
+ gobbleGroup() {
900
+ this.index++;
901
+ let nodes = this.gobbleExpressions(Jsep.CPAREN_CODE);
902
+ if (this.code === Jsep.CPAREN_CODE) {
903
+ this.index++;
904
+ if (nodes.length === 1) {
905
+ return nodes[0];
906
+ } else if (!nodes.length) {
907
+ return false;
908
+ } else {
909
+ return {
910
+ type: Jsep.SEQUENCE_EXP,
911
+ expressions: nodes
912
+ };
913
+ }
914
+ } else {
915
+ this.throwError('Unclosed (');
916
+ }
917
+ }
918
+
919
+ /**
920
+ * Responsible for parsing Array literals `[1, 2, 3]`
921
+ * This function assumes that it needs to gobble the opening bracket
922
+ * and then tries to gobble the expressions as arguments.
923
+ * @returns {jsep.ArrayExpression}
924
+ */
925
+ gobbleArray() {
926
+ this.index++;
927
+ return {
928
+ type: Jsep.ARRAY_EXP,
929
+ elements: this.gobbleArguments(Jsep.CBRACK_CODE)
930
+ };
931
+ }
932
+ }
933
+
934
+ // Static fields:
935
+ const hooks = new Hooks();
936
+ Object.assign(Jsep, {
937
+ hooks,
938
+ plugins: new Plugins(Jsep),
939
+ // Node Types
940
+ // ----------
941
+ // This is the full set of types that any JSEP node can be.
942
+ // Store them here to save space when minified
943
+ COMPOUND: 'Compound',
944
+ SEQUENCE_EXP: 'SequenceExpression',
945
+ IDENTIFIER: 'Identifier',
946
+ MEMBER_EXP: 'MemberExpression',
947
+ LITERAL: 'Literal',
948
+ THIS_EXP: 'ThisExpression',
949
+ CALL_EXP: 'CallExpression',
950
+ UNARY_EXP: 'UnaryExpression',
951
+ BINARY_EXP: 'BinaryExpression',
952
+ ARRAY_EXP: 'ArrayExpression',
953
+ TAB_CODE: 9,
954
+ LF_CODE: 10,
955
+ CR_CODE: 13,
956
+ SPACE_CODE: 32,
957
+ PERIOD_CODE: 46,
958
+ // '.'
959
+ COMMA_CODE: 44,
960
+ // ','
961
+ SQUOTE_CODE: 39,
962
+ // single quote
963
+ DQUOTE_CODE: 34,
964
+ // double quotes
965
+ OPAREN_CODE: 40,
966
+ // (
967
+ CPAREN_CODE: 41,
968
+ // )
969
+ OBRACK_CODE: 91,
970
+ // [
971
+ CBRACK_CODE: 93,
972
+ // ]
973
+ QUMARK_CODE: 63,
974
+ // ?
975
+ SEMCOL_CODE: 59,
976
+ // ;
977
+ COLON_CODE: 58,
978
+ // :
979
+
980
+ // Operations
981
+ // ----------
982
+ // Use a quickly-accessible map to store all of the unary operators
983
+ // Values are set to `1` (it really doesn't matter)
984
+ unary_ops: {
985
+ '-': 1,
986
+ '!': 1,
987
+ '~': 1,
988
+ '+': 1
989
+ },
990
+ // Also use a map for the binary operations but set their values to their
991
+ // binary precedence for quick reference (higher number = higher precedence)
992
+ // see [Order of operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
993
+ binary_ops: {
994
+ '||': 1,
995
+ '??': 1,
996
+ '&&': 2,
997
+ '|': 3,
998
+ '^': 4,
999
+ '&': 5,
1000
+ '==': 6,
1001
+ '!=': 6,
1002
+ '===': 6,
1003
+ '!==': 6,
1004
+ '<': 7,
1005
+ '>': 7,
1006
+ '<=': 7,
1007
+ '>=': 7,
1008
+ '<<': 8,
1009
+ '>>': 8,
1010
+ '>>>': 8,
1011
+ '+': 9,
1012
+ '-': 9,
1013
+ '*': 10,
1014
+ '/': 10,
1015
+ '%': 10,
1016
+ '**': 11
1017
+ },
1018
+ // sets specific binary_ops as right-associative
1019
+ right_associative: new Set(['**']),
1020
+ // Additional valid identifier chars, apart from a-z, A-Z and 0-9 (except on the starting char)
1021
+ additional_identifier_chars: new Set(['$', '_']),
1022
+ // Literals
1023
+ // ----------
1024
+ // Store the values to return for the various literals we may encounter
1025
+ literals: {
1026
+ 'true': true,
1027
+ 'false': false,
1028
+ 'null': null
1029
+ },
1030
+ // Except for `this`, which is special. This could be changed to something like `'self'` as well
1031
+ this_str: 'this'
1032
+ });
1033
+ Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
1034
+ Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
1035
+
1036
+ // Backward Compatibility:
1037
+ const jsep = expr => new Jsep(expr).parse();
1038
+ const stdClassProps = Object.getOwnPropertyNames(class Test {});
1039
+ Object.getOwnPropertyNames(Jsep).filter(prop => !stdClassProps.includes(prop) && jsep[prop] === undefined).forEach(m => {
1040
+ jsep[m] = Jsep[m];
1041
+ });
1042
+ jsep.Jsep = Jsep; // allows for const { Jsep } = require('jsep');
1043
+
1044
+ const CONDITIONAL_EXP = 'ConditionalExpression';
1045
+ var ternary = {
1046
+ name: 'ternary',
1047
+ init(jsep) {
1048
+ // Ternary expression: test ? consequent : alternate
1049
+ jsep.hooks.add('after-expression', function gobbleTernary(env) {
1050
+ if (env.node && this.code === jsep.QUMARK_CODE) {
1051
+ this.index++;
1052
+ const test = env.node;
1053
+ const consequent = this.gobbleExpression();
1054
+ if (!consequent) {
1055
+ this.throwError('Expected expression');
1056
+ }
1057
+ this.gobbleSpaces();
1058
+ if (this.code === jsep.COLON_CODE) {
1059
+ this.index++;
1060
+ const alternate = this.gobbleExpression();
1061
+ if (!alternate) {
1062
+ this.throwError('Expected expression');
1063
+ }
1064
+ env.node = {
1065
+ type: CONDITIONAL_EXP,
1066
+ test,
1067
+ consequent,
1068
+ alternate
1069
+ };
1070
+
1071
+ // check for operators of higher priority than ternary (i.e. assignment)
1072
+ // jsep sets || at 1, and assignment at 0.9, and conditional should be between them
1073
+ if (test.operator && jsep.binary_ops[test.operator] <= 0.9) {
1074
+ let newTest = test;
1075
+ while (newTest.right.operator && jsep.binary_ops[newTest.right.operator] <= 0.9) {
1076
+ newTest = newTest.right;
1077
+ }
1078
+ env.node.test = newTest.right;
1079
+ newTest.right = env.node;
1080
+ env.node = test;
1081
+ }
1082
+ } else {
1083
+ this.throwError('Expected :');
1084
+ }
1085
+ }
1086
+ });
1087
+ }
1088
+ };
1089
+
1090
+ // Add default plugins:
1091
+
1092
+ jsep.plugins.register(ternary);
1093
+
1094
+ const FSLASH_CODE = 47; // '/'
1095
+ const BSLASH_CODE = 92; // '\\'
1096
+
1097
+ var index = {
1098
+ name: 'regex',
1099
+ init(jsep) {
1100
+ // Regex literal: /abc123/ig
1101
+ jsep.hooks.add('gobble-token', function gobbleRegexLiteral(env) {
1102
+ if (this.code === FSLASH_CODE) {
1103
+ const patternIndex = ++this.index;
1104
+ let inCharSet = false;
1105
+ while (this.index < this.expr.length) {
1106
+ if (this.code === FSLASH_CODE && !inCharSet) {
1107
+ const pattern = this.expr.slice(patternIndex, this.index);
1108
+ let flags = '';
1109
+ while (++this.index < this.expr.length) {
1110
+ const code = this.code;
1111
+ if (code >= 97 && code <= 122 // a...z
1112
+ || code >= 65 && code <= 90 // A...Z
1113
+ || code >= 48 && code <= 57) {
1114
+ // 0-9
1115
+ flags += this.char;
1116
+ } else {
1117
+ break;
1118
+ }
1119
+ }
1120
+ let value;
1121
+ try {
1122
+ value = new RegExp(pattern, flags);
1123
+ } catch (e) {
1124
+ this.throwError(e.message);
1125
+ }
1126
+ env.node = {
1127
+ type: jsep.LITERAL,
1128
+ value,
1129
+ raw: this.expr.slice(patternIndex - 1, this.index)
1130
+ };
1131
+
1132
+ // allow . [] and () after regex: /regex/.test(a)
1133
+ env.node = this.gobbleTokenProperty(env.node);
1134
+ return env.node;
1135
+ }
1136
+ if (this.code === jsep.OBRACK_CODE) {
1137
+ inCharSet = true;
1138
+ } else if (inCharSet && this.code === jsep.CBRACK_CODE) {
1139
+ inCharSet = false;
1140
+ }
1141
+ this.index += this.code === BSLASH_CODE ? 2 : 1;
1142
+ }
1143
+ this.throwError('Unclosed Regex');
1144
+ }
1145
+ });
1146
+ }
1147
+ };
1148
+
1149
+ const PLUS_CODE = 43; // +
1150
+ const MINUS_CODE = 45; // -
1151
+
1152
+ const plugin = {
1153
+ name: 'assignment',
1154
+ assignmentOperators: new Set(['=', '*=', '**=', '/=', '%=', '+=', '-=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '||=', '&&=', '??=']),
1155
+ updateOperators: [PLUS_CODE, MINUS_CODE],
1156
+ assignmentPrecedence: 0.9,
1157
+ init(jsep) {
1158
+ const updateNodeTypes = [jsep.IDENTIFIER, jsep.MEMBER_EXP];
1159
+ plugin.assignmentOperators.forEach(op => jsep.addBinaryOp(op, plugin.assignmentPrecedence, true));
1160
+ jsep.hooks.add('gobble-token', function gobbleUpdatePrefix(env) {
1161
+ const code = this.code;
1162
+ if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
1163
+ this.index += 2;
1164
+ env.node = {
1165
+ type: 'UpdateExpression',
1166
+ operator: code === PLUS_CODE ? '++' : '--',
1167
+ argument: this.gobbleTokenProperty(this.gobbleIdentifier()),
1168
+ prefix: true
1169
+ };
1170
+ if (!env.node.argument || !updateNodeTypes.includes(env.node.argument.type)) {
1171
+ this.throwError(`Unexpected ${env.node.operator}`);
1172
+ }
1173
+ }
1174
+ });
1175
+ jsep.hooks.add('after-token', function gobbleUpdatePostfix(env) {
1176
+ if (env.node) {
1177
+ const code = this.code;
1178
+ if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
1179
+ if (!updateNodeTypes.includes(env.node.type)) {
1180
+ this.throwError(`Unexpected ${env.node.operator}`);
1181
+ }
1182
+ this.index += 2;
1183
+ env.node = {
1184
+ type: 'UpdateExpression',
1185
+ operator: code === PLUS_CODE ? '++' : '--',
1186
+ argument: env.node,
1187
+ prefix: false
1188
+ };
1189
+ }
1190
+ }
1191
+ });
1192
+ jsep.hooks.add('after-expression', function gobbleAssignment(env) {
1193
+ if (env.node) {
1194
+ // Note: Binaries can be chained in a single expression to respect
1195
+ // operator precedence (i.e. a = b = 1 + 2 + 3)
1196
+ // Update all binary assignment nodes in the tree
1197
+ updateBinariesToAssignments(env.node);
1198
+ }
1199
+ });
1200
+ function updateBinariesToAssignments(node) {
1201
+ if (plugin.assignmentOperators.has(node.operator)) {
1202
+ node.type = 'AssignmentExpression';
1203
+ updateBinariesToAssignments(node.left);
1204
+ updateBinariesToAssignments(node.right);
1205
+ } else if (!node.operator) {
1206
+ Object.values(node).forEach(val => {
1207
+ if (val && typeof val === 'object') {
1208
+ updateBinariesToAssignments(val);
1209
+ }
1210
+ });
1211
+ }
1212
+ }
1213
+ }
1214
+ };
1215
+
1216
+ /* eslint-disable no-bitwise -- Convenient */
1217
+
1218
+ // register plugins
1219
+ jsep.plugins.register(index, plugin);
1220
+ jsep.addUnaryOp('typeof');
1221
+ jsep.addUnaryOp('void');
1222
+ jsep.addLiteral('null', null);
1223
+ jsep.addLiteral('undefined', undefined);
17
1224
 
18
1225
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
19
1226
 
@@ -2485,11 +3692,11 @@ function requireIdentifiers () {
2485
3692
  return identifiers;
2486
3693
  }
2487
3694
 
2488
- var semver$1;
3695
+ var semver$2;
2489
3696
  var hasRequiredSemver$1;
2490
3697
 
2491
3698
  function requireSemver$1 () {
2492
- if (hasRequiredSemver$1) return semver$1;
3699
+ if (hasRequiredSemver$1) return semver$2;
2493
3700
  hasRequiredSemver$1 = 1;
2494
3701
 
2495
3702
  const debug = requireDebug();
@@ -2822,8 +4029,8 @@ function requireSemver$1 () {
2822
4029
  }
2823
4030
  }
2824
4031
 
2825
- semver$1 = SemVer;
2826
- return semver$1;
4032
+ semver$2 = SemVer;
4033
+ return semver$2;
2827
4034
  }
2828
4035
 
2829
4036
  var parse_1;
@@ -4744,11 +5951,11 @@ function requireSubset () {
4744
5951
  return subset_1;
4745
5952
  }
4746
5953
 
4747
- var semver;
5954
+ var semver$1;
4748
5955
  var hasRequiredSemver;
4749
5956
 
4750
5957
  function requireSemver () {
4751
- if (hasRequiredSemver) return semver;
5958
+ if (hasRequiredSemver) return semver$1;
4752
5959
  hasRequiredSemver = 1;
4753
5960
 
4754
5961
  // just pre-load all the stuff that index.js lazily exports
@@ -4793,7 +6000,7 @@ function requireSemver () {
4793
6000
  const intersects = requireIntersects();
4794
6001
  const simplifyRange = requireSimplify();
4795
6002
  const subset = requireSubset();
4796
- semver = {
6003
+ semver$1 = {
4797
6004
  parse,
4798
6005
  valid,
4799
6006
  clean,
@@ -4840,10 +6047,11 @@ function requireSemver () {
4840
6047
  compareIdentifiers: identifiers.compareIdentifiers,
4841
6048
  rcompareIdentifiers: identifiers.rcompareIdentifiers,
4842
6049
  };
4843
- return semver;
6050
+ return semver$1;
4844
6051
  }
4845
6052
 
4846
6053
  var semverExports = requireSemver();
6054
+ var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
4847
6055
 
4848
6056
  function commonjsRequire(path) {
4849
6057
  throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
@@ -17217,6 +18425,142 @@ ZodPromise.create;
17217
18425
  ZodOptional.create;
17218
18426
  ZodNullable.create;
17219
18427
 
18428
+ /**
18429
+ * Directory and file path conventions for the Jeeves platform.
18430
+ */
18431
+ /** Core config directory name within the config root. */
18432
+ const CORE_CONFIG_DIR = 'jeeves-core';
18433
+ /** Prefix for component config directories: `jeeves-{name}`. */
18434
+ const COMPONENT_CONFIG_PREFIX = 'jeeves-';
18435
+ /** Default workspace file names. */
18436
+ const WORKSPACE_FILES = {
18437
+ /** TOOLS.md — live platform state and component sections. */
18438
+ tools: 'TOOLS.md',
18439
+ /** SOUL.md — professional discipline and behavioral foundations. */
18440
+ soul: 'SOUL.md',
18441
+ /** AGENTS.md — operational protocols and memory architecture. */
18442
+ agents: 'AGENTS.md',
18443
+ };
18444
+ /** Templates directory name within core config. */
18445
+ const TEMPLATES_DIR = 'templates';
18446
+ /** Registry cache file name. */
18447
+ const REGISTRY_CACHE_FILE = 'registry-cache.json';
18448
+ /** Core config file name. */
18449
+ const CONFIG_FILE = 'config.json';
18450
+ /** Component versions state file name. */
18451
+ const COMPONENT_VERSIONS_FILE = 'component-versions.json';
18452
+
18453
+ /**
18454
+ * Shared file I/O helpers for managed section operations.
18455
+ *
18456
+ * @remarks
18457
+ * Extracts the atomic write pattern and file-level locking into
18458
+ * reusable utilities, eliminating duplication between
18459
+ * `updateManagedSection` and `removeManagedSection`.
18460
+ */
18461
+ /** Stale lock threshold in ms (2 minutes). */
18462
+ const STALE_LOCK_MS = 120_000;
18463
+ /** Default core version when none provided. */
18464
+ const DEFAULT_CORE_VERSION = '0.0.0';
18465
+ /** Lock retry options. */
18466
+ const LOCK_RETRIES = { retries: 5, minTimeout: 100, maxTimeout: 1000 };
18467
+ /**
18468
+ * Write content to a file atomically via a temp file + rename.
18469
+ *
18470
+ * @param filePath - Absolute path to the target file.
18471
+ * @param content - Content to write.
18472
+ */
18473
+ function atomicWrite(filePath, content) {
18474
+ const dir = dirname(filePath);
18475
+ const tempPath = join(dir, `.${String(Date.now())}.tmp`);
18476
+ writeFileSync(tempPath, content, 'utf-8');
18477
+ renameSync(tempPath, filePath);
18478
+ }
18479
+ /**
18480
+ * Execute a callback while holding a file lock.
18481
+ *
18482
+ * @remarks
18483
+ * Acquires a lock on the file, executes the callback, and releases
18484
+ * the lock in a finally block. The lock uses a 2-minute stale threshold
18485
+ * and retries up to 5 times.
18486
+ *
18487
+ * @param filePath - Absolute path to the file to lock.
18488
+ * @param fn - Async callback to execute while holding the lock.
18489
+ */
18490
+ async function withFileLock(filePath, fn) {
18491
+ let release;
18492
+ try {
18493
+ release = await properLockfileExports.lock(filePath, {
18494
+ stale: STALE_LOCK_MS,
18495
+ retries: LOCK_RETRIES,
18496
+ });
18497
+ await fn();
18498
+ }
18499
+ finally {
18500
+ if (release) {
18501
+ try {
18502
+ await release();
18503
+ }
18504
+ catch {
18505
+ // Lock already released or file deleted — safe to ignore
18506
+ }
18507
+ }
18508
+ }
18509
+ }
18510
+
18511
+ /**
18512
+ * Shared component version state file management.
18513
+ *
18514
+ * @remarks
18515
+ * Each `ComponentWriter` cycle writes its component's entry to
18516
+ * `{coreConfigDir}/component-versions.json`. The Platform Handlebars
18517
+ * template reads this file to populate ALL rows in the service health
18518
+ * table, not just the calling component's.
18519
+ */
18520
+ /**
18521
+ * Read the component versions state file.
18522
+ *
18523
+ * @param coreConfigDir - Path to the core config directory.
18524
+ * @returns The parsed state, or an empty object if the file doesn't exist.
18525
+ */
18526
+ function readComponentVersions(coreConfigDir) {
18527
+ const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
18528
+ if (!existsSync(filePath))
18529
+ return {};
18530
+ try {
18531
+ const raw = readFileSync(filePath, 'utf-8');
18532
+ return JSON.parse(raw);
18533
+ }
18534
+ catch {
18535
+ return {};
18536
+ }
18537
+ }
18538
+ /**
18539
+ * Write a component's version entry to the shared state file.
18540
+ *
18541
+ * @remarks
18542
+ * Reads the existing file, merges the new entry, and writes atomically.
18543
+ *
18544
+ * @param coreConfigDir - Path to the core config directory.
18545
+ * @param options - Component version data to write.
18546
+ */
18547
+ function writeComponentVersion(coreConfigDir, options) {
18548
+ const existing = readComponentVersions(coreConfigDir);
18549
+ existing[options.componentName] = {
18550
+ serviceVersion: options.serviceVersion,
18551
+ pluginVersion: options.pluginVersion,
18552
+ servicePackage: options.servicePackage,
18553
+ pluginPackage: options.pluginPackage,
18554
+ updatedAt: new Date().toISOString(),
18555
+ };
18556
+ const filePath = join(coreConfigDir, COMPONENT_VERSIONS_FILE);
18557
+ const dir = dirname(filePath);
18558
+ if (!existsSync(dir)) {
18559
+ mkdirSync(dir, { recursive: true });
18560
+ }
18561
+ atomicWrite(filePath, JSON.stringify(existing, null, 2) + '\n');
18562
+ }
18563
+
17220
18564
  /**
17221
18565
  * Comment markers for managed content blocks.
17222
18566
  *
@@ -17266,29 +18610,6 @@ const STALENESS_THRESHOLD_MS = 5 * 60 * 1000;
17266
18610
  /** Warning text prepended inside managed block when cleanup is needed. */
17267
18611
  const CLEANUP_FLAG = '> ⚠️ CLEANUP NEEDED: Orphaned Jeeves content may exist below this managed section. Review everything after the END marker and remove any content that duplicates what appears above.';
17268
18612
 
17269
- /**
17270
- * Directory and file path conventions for the Jeeves platform.
17271
- */
17272
- /** Core config directory name within the config root. */
17273
- const CORE_CONFIG_DIR = 'jeeves-core';
17274
- /** Prefix for component config directories: `jeeves-{name}`. */
17275
- const COMPONENT_CONFIG_PREFIX = 'jeeves-';
17276
- /** Default workspace file names. */
17277
- const WORKSPACE_FILES = {
17278
- /** TOOLS.md — live platform state and component sections. */
17279
- tools: 'TOOLS.md',
17280
- /** SOUL.md — professional discipline and behavioral foundations. */
17281
- soul: 'SOUL.md',
17282
- /** AGENTS.md — operational protocols and memory architecture. */
17283
- agents: 'AGENTS.md',
17284
- };
17285
- /** Templates directory name within core config. */
17286
- const TEMPLATES_DIR = 'templates';
17287
- /** Registry cache file name. */
17288
- const REGISTRY_CACHE_FILE = 'registry-cache.json';
17289
- /** Core config file name. */
17290
- const CONFIG_FILE = 'config.json';
17291
-
17292
18613
  /**
17293
18614
  * Default port assignments for Jeeves platform services.
17294
18615
  *
@@ -17351,14 +18672,14 @@ const SECTION_ORDER = [
17351
18672
  * Core library version, inlined at build time.
17352
18673
  *
17353
18674
  * @remarks
17354
- * The `0.1.5` placeholder is replaced by
18675
+ * The `0.1.6` placeholder is replaced by
17355
18676
  * `@rollup/plugin-replace` during the build with the actual version
17356
18677
  * from `package.json`. This ensures the correct version survives
17357
18678
  * when consumers bundle core into their own dist (where runtime
17358
18679
  * `import.meta.url`-based resolution would find the wrong package.json).
17359
18680
  */
17360
18681
  /** The core library version from package.json (inlined at build time). */
17361
- const CORE_VERSION = '0.1.5';
18682
+ const CORE_VERSION = '0.1.6';
17362
18683
 
17363
18684
  /**
17364
18685
  * Workspace and config root initialization.
@@ -17670,10 +18991,6 @@ function shouldWrite(myVersion, existing, stalenessThresholdMs = STALENESS_THRES
17670
18991
  *
17671
18992
  * Provides file-level locking, version-stamp convergence, and atomic writes.
17672
18993
  */
17673
- /** Default core version when none provided. */
17674
- const DEFAULT_VERSION = '0.0.0';
17675
- /** Stale lock threshold in ms (2 minutes). */
17676
- const STALE_LOCK_MS = 120_000;
17677
18994
  /**
17678
18995
  * Update a managed section in a file.
17679
18996
  *
@@ -17682,7 +18999,7 @@ const STALE_LOCK_MS = 120_000;
17682
18999
  * @param options - Write mode and optional configuration.
17683
19000
  */
17684
19001
  async function updateManagedSection(filePath, content, options = {}) {
17685
- const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion = DEFAULT_VERSION, stalenessThresholdMs, } = options;
19002
+ const { mode = 'block', sectionId, markers = TOOLS_MARKERS, coreVersion = DEFAULT_CORE_VERSION, stalenessThresholdMs, } = options;
17686
19003
  if (mode === 'section' && !sectionId) {
17687
19004
  throw new Error('sectionId is required when mode is "section"');
17688
19005
  }
@@ -17694,93 +19011,77 @@ async function updateManagedSection(filePath, content, options = {}) {
17694
19011
  if (!existsSync(filePath)) {
17695
19012
  writeFileSync(filePath, '', 'utf-8');
17696
19013
  }
17697
- let release;
17698
19014
  try {
17699
- release = await properLockfileExports.lock(filePath, {
17700
- stale: STALE_LOCK_MS,
17701
- retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 },
17702
- });
17703
- const fileContent = readFileSync(filePath, 'utf-8');
17704
- const parsed = parseManaged(fileContent, markers);
17705
- // Version-stamp convergence check (block mode only).
17706
- // In section mode, components always write their own sections — the version
17707
- // stamp governs shared content convergence, not component-specific sections.
17708
- if (mode === 'block' &&
17709
- !shouldWrite(coreVersion, parsed.versionStamp, stalenessThresholdMs)) {
17710
- return;
17711
- }
17712
- let newManagedBody;
17713
- if (mode === 'block') {
17714
- // Prepend H1 title if markers specify one
17715
- newManagedBody = markers.title
17716
- ? `# ${markers.title}\n\n${content}`
17717
- : content;
17718
- }
17719
- else {
17720
- // Section mode: upsert the named section
17721
- const sections = [...parsed.sections];
17722
- const existingIdx = sections.findIndex((s) => s.id === sectionId);
17723
- if (existingIdx >= 0) {
17724
- sections[existingIdx] = { id: sectionId, content };
19015
+ await withFileLock(filePath, () => {
19016
+ const fileContent = readFileSync(filePath, 'utf-8');
19017
+ const parsed = parseManaged(fileContent, markers);
19018
+ // Version-stamp convergence check (block mode only).
19019
+ // In section mode, components always write their own sections — the version
19020
+ // stamp governs shared content convergence, not component-specific sections.
19021
+ if (mode === 'block' &&
19022
+ !shouldWrite(coreVersion, parsed.versionStamp, stalenessThresholdMs)) {
19023
+ return;
19024
+ }
19025
+ let newManagedBody;
19026
+ if (mode === 'block') {
19027
+ // Prepend H1 title if markers specify one
19028
+ newManagedBody = markers.title
19029
+ ? `# ${markers.title}\n\n${content}`
19030
+ : content;
17725
19031
  }
17726
19032
  else {
17727
- sections.push({ id: sectionId, content });
19033
+ // Section mode: upsert the named section
19034
+ const sections = [...parsed.sections];
19035
+ const existingIdx = sections.findIndex((s) => s.id === sectionId);
19036
+ if (existingIdx >= 0) {
19037
+ sections[existingIdx] = { id: sectionId, content };
19038
+ }
19039
+ else {
19040
+ sections.push({ id: sectionId, content });
19041
+ }
19042
+ sortSectionsByOrder(sections);
19043
+ const sectionText = sections
19044
+ .map((s) => `## ${s.id}\n\n${s.content}`)
19045
+ .join('\n\n');
19046
+ // Prepend H1 title if markers specify one
19047
+ newManagedBody = markers.title
19048
+ ? `# ${markers.title}\n\n${sectionText}`
19049
+ : sectionText;
19050
+ }
19051
+ // Cleanup detection
19052
+ const userContent = parsed.userContent;
19053
+ const cleanupNeeded = needsCleanup(newManagedBody, userContent);
19054
+ // Build the full managed block
19055
+ const beginLine = formatBeginMarker(markers.begin, coreVersion);
19056
+ const endLine = formatEndMarker(markers.end);
19057
+ const parts = [];
19058
+ if (parsed.beforeContent) {
19059
+ parts.push(parsed.beforeContent);
19060
+ parts.push('');
19061
+ }
19062
+ parts.push(beginLine);
19063
+ if (cleanupNeeded) {
19064
+ parts.push('');
19065
+ parts.push(CLEANUP_FLAG);
17728
19066
  }
17729
- sortSectionsByOrder(sections);
17730
- const sectionText = sections
17731
- .map((s) => `## ${s.id}\n\n${s.content}`)
17732
- .join('\n\n');
17733
- // Prepend H1 title if markers specify one (e.g., "# Jeeves Platform Tools")
17734
- newManagedBody = markers.title
17735
- ? `# ${markers.title}\n\n${sectionText}`
17736
- : sectionText;
17737
- }
17738
- // Cleanup detection
17739
- const userContent = parsed.userContent;
17740
- const cleanupNeeded = needsCleanup(newManagedBody, userContent);
17741
- // Build the full managed block
17742
- const beginLine = formatBeginMarker(markers.begin, coreVersion);
17743
- const endLine = formatEndMarker(markers.end);
17744
- const parts = [];
17745
- if (parsed.beforeContent) {
17746
- parts.push(parsed.beforeContent);
17747
19067
  parts.push('');
17748
- }
17749
- parts.push(beginLine);
17750
- if (cleanupNeeded) {
19068
+ parts.push(newManagedBody);
17751
19069
  parts.push('');
17752
- parts.push(CLEANUP_FLAG);
17753
- }
17754
- parts.push('');
17755
- parts.push(newManagedBody);
17756
- parts.push('');
17757
- parts.push(endLine);
17758
- if (userContent) {
19070
+ parts.push(endLine);
19071
+ if (userContent) {
19072
+ parts.push('');
19073
+ parts.push(userContent);
19074
+ }
17759
19075
  parts.push('');
17760
- parts.push(userContent);
17761
- }
17762
- parts.push('');
17763
- const newFileContent = parts.join('\n');
17764
- // Atomic write: write to temp file, then rename
17765
- const tempPath = join(dir, `.${String(Date.now())}.tmp`);
17766
- writeFileSync(tempPath, newFileContent, 'utf-8');
17767
- renameSync(tempPath, filePath);
19076
+ const newFileContent = parts.join('\n');
19077
+ atomicWrite(filePath, newFileContent);
19078
+ });
17768
19079
  }
17769
19080
  catch (err) {
17770
19081
  // Log warning but don't throw — writer cycles are periodic
17771
19082
  const message = err instanceof Error ? err.message : String(err);
17772
19083
  console.warn(`jeeves-core: updateManagedSection failed for ${filePath}: ${message}`);
17773
19084
  }
17774
- finally {
17775
- if (release) {
17776
- try {
17777
- await release();
17778
- }
17779
- catch {
17780
- // Lock already released or file deleted — safe to ignore
17781
- }
17782
- }
17783
- }
17784
19085
  }
17785
19086
 
17786
19087
  var agentsSectionContent = `## Memory Architecture
@@ -18213,7 +19514,7 @@ function loadConfig(configDir) {
18213
19514
  * @returns The resolved service URL.
18214
19515
  * @throws Error if `init()` has not been called or the service is unknown.
18215
19516
  */
18216
- function getServiceUrl(serviceName, consumerName) {
19517
+ function getServiceUrl$1(serviceName, consumerName) {
18217
19518
  // 2. Check core config
18218
19519
  const coreDir = getCoreConfigDir();
18219
19520
  const coreConfig = loadConfig(coreDir);
@@ -18259,7 +19560,7 @@ function extractPort(url) {
18259
19560
  * @returns Probe result.
18260
19561
  */
18261
19562
  async function probeService(serviceName, consumerName, timeoutMs = 3000) {
18262
- const url = getServiceUrl(serviceName);
19563
+ const url = getServiceUrl$1(serviceName);
18263
19564
  const port = extractPort(url);
18264
19565
  const endpoints = ['/status', '/health'];
18265
19566
  for (const endpoint of endpoints) {
@@ -18364,6 +19665,63 @@ function checkRegistryVersion(packageName, cacheDir, ttlSeconds = 3600) {
18364
19665
  }
18365
19666
  }
18366
19667
 
19668
+ /**
19669
+ * Build enriched service rows for the Platform template.
19670
+ *
19671
+ * @remarks
19672
+ * Merges health probe results with component version state and
19673
+ * npm registry update availability into rows for the Handlebars
19674
+ * Platform template.
19675
+ */
19676
+ /**
19677
+ * Check whether an available version is newer than the current one.
19678
+ *
19679
+ * @param available - Registry version string.
19680
+ * @param current - Currently installed version string.
19681
+ * @returns The available version if it's newer, otherwise undefined.
19682
+ */
19683
+ function newerVersion(available, current) {
19684
+ if (!available ||
19685
+ !current ||
19686
+ !semver.valid(available) ||
19687
+ !semver.valid(current)) {
19688
+ return undefined;
19689
+ }
19690
+ return semver.gt(available, current) ? available : undefined;
19691
+ }
19692
+ /**
19693
+ * Build enriched service rows for the Platform Handlebars template.
19694
+ *
19695
+ * @param options - Probe results, version state, and configuration.
19696
+ * @returns Array of enriched service rows.
19697
+ */
19698
+ function buildServiceRows(options) {
19699
+ const { probeResults, componentVersions, cacheDir, skipRegistryCheck } = options;
19700
+ return probeResults.map((r) => {
19701
+ const entry = componentVersions[r.name];
19702
+ if (!entry)
19703
+ return { ...r };
19704
+ let availableServiceVersion;
19705
+ let availablePluginVersion;
19706
+ if (!skipRegistryCheck) {
19707
+ if (entry.servicePackage) {
19708
+ const registryVersion = checkRegistryVersion(entry.servicePackage, cacheDir);
19709
+ availableServiceVersion = newerVersion(registryVersion, r.version);
19710
+ }
19711
+ if (entry.pluginPackage && entry.pluginVersion) {
19712
+ const registryVersion = checkRegistryVersion(entry.pluginPackage, cacheDir);
19713
+ availablePluginVersion = newerVersion(registryVersion, entry.pluginVersion);
19714
+ }
19715
+ }
19716
+ return {
19717
+ ...r,
19718
+ pluginVersion: entry.pluginVersion,
19719
+ availableServiceVersion,
19720
+ availablePluginVersion,
19721
+ };
19722
+ });
19723
+ }
19724
+
18367
19725
  /**
18368
19726
  * Internal function to maintain SOUL.md, AGENTS.md, and TOOLS.md Platform section.
18369
19727
  *
@@ -18417,15 +19775,28 @@ function copyTemplates(coreConfigDir) {
18417
19775
  }
18418
19776
  /** Whether Handlebars helpers have been registered. */
18419
19777
  let helpersRegistered = false;
18420
- /**
18421
- * Register Handlebars helpers used in the Platform template.
18422
- */
19778
+ /** Register Handlebars helpers used in the Platform template. */
18423
19779
  function registerHelpers() {
18424
19780
  if (helpersRegistered)
18425
19781
  return;
18426
19782
  helpersRegistered = true;
18427
19783
  Handlebars.registerHelper('gt', (a, b) => typeof a === 'number' && typeof b === 'number' && a > b);
18428
19784
  }
19785
+ /**
19786
+ * Check if a newer core version is available on npm.
19787
+ *
19788
+ * @returns The newer version string, or undefined.
19789
+ */
19790
+ function checkCoreUpdate(coreVersion, cacheDir) {
19791
+ const registryVersion = checkRegistryVersion('@karmaniverous/jeeves', cacheDir);
19792
+ if (registryVersion &&
19793
+ semver.valid(registryVersion) &&
19794
+ semver.valid(coreVersion) &&
19795
+ semver.gt(registryVersion, coreVersion)) {
19796
+ return registryVersion;
19797
+ }
19798
+ return undefined;
19799
+ }
18429
19800
  /**
18430
19801
  * Refresh platform content: SOUL.md, AGENTS.md, and TOOLS.md Platform section.
18431
19802
  *
@@ -18437,55 +19808,46 @@ async function refreshPlatformContent(options) {
18437
19808
  const coreConfigDir = getCoreConfigDir();
18438
19809
  // 1. Probe all services
18439
19810
  const probeResults = await probeAllServices(undefined, probeTimeoutMs);
18440
- const unhealthyServices = probeResults.filter((r) => !r.healthy);
18441
- // 2. Registry version checks
19811
+ // 2. Write calling component's version entry (with serviceVersion from probe)
19812
+ if (componentName) {
19813
+ const callerProbe = probeResults.find((r) => r.name === componentName);
19814
+ writeComponentVersion(coreConfigDir, {
19815
+ componentName,
19816
+ serviceVersion: callerProbe?.version,
19817
+ pluginVersion: componentVersion,
19818
+ servicePackage,
19819
+ pluginPackage,
19820
+ });
19821
+ }
19822
+ // 3. Read all component versions from the shared state file
19823
+ const componentVersions = readComponentVersions(coreConfigDir);
19824
+ // 4. Build enriched service rows with registry checks
18442
19825
  const cacheDir = componentName
18443
19826
  ? getComponentConfigDir(componentName)
18444
19827
  : coreConfigDir;
18445
- let availableCoreVersion;
18446
- let availableServiceVersion;
18447
- let availablePluginVersion;
18448
- if (!skipRegistryCheck) {
18449
- const coreRegistryVersion = checkRegistryVersion('@karmaniverous/jeeves', cacheDir);
18450
- if (coreRegistryVersion && coreRegistryVersion !== coreVersion) {
18451
- availableCoreVersion = coreRegistryVersion;
18452
- }
18453
- if (servicePackage) {
18454
- const svcVersion = checkRegistryVersion(servicePackage, cacheDir);
18455
- if (svcVersion) {
18456
- availableServiceVersion = svcVersion;
18457
- }
18458
- }
18459
- if (pluginPackage) {
18460
- const plgVersion = checkRegistryVersion(pluginPackage, cacheDir);
18461
- if (plgVersion) {
18462
- availablePluginVersion = plgVersion;
18463
- }
18464
- }
18465
- }
18466
- // 3. Build enriched service rows — match the calling component by name
18467
- const serviceRows = probeResults.map((r) => ({
18468
- ...r,
18469
- pluginVersion: r.name === componentName ? componentVersion : undefined,
18470
- availableServiceVersion: r.name === componentName ? availableServiceVersion : undefined,
18471
- availablePluginVersion: r.name === componentName ? availablePluginVersion : undefined,
18472
- }));
18473
- // 5. Check if templates are available
19828
+ const availableCoreVersion = skipRegistryCheck
19829
+ ? undefined
19830
+ : checkCoreUpdate(coreVersion, cacheDir);
19831
+ const serviceRows = buildServiceRows({
19832
+ probeResults,
19833
+ componentVersions,
19834
+ cacheDir,
19835
+ skipRegistryCheck,
19836
+ });
19837
+ // 5. Render Platform template
18474
19838
  const templatePath = join(coreConfigDir, TEMPLATES_DIR);
18475
- const templatesAvailable = existsSync(templatePath);
18476
- // 6. Render Platform template
18477
19839
  registerHelpers();
18478
19840
  const template = Handlebars.compile(toolsPlatformTemplate);
18479
19841
  const templateData = {
18480
19842
  services: serviceRows,
18481
- unhealthyServices,
19843
+ unhealthyServices: serviceRows.filter((r) => !r.healthy),
18482
19844
  coreVersion,
18483
19845
  availableCoreVersion,
18484
- templatesAvailable,
19846
+ templatesAvailable: existsSync(templatePath),
18485
19847
  templatePath,
18486
19848
  };
18487
19849
  const platformContent = template(templateData);
18488
- // 7. Write TOOLS.md Platform section
19850
+ // 6. Write TOOLS.md Platform section
18489
19851
  const toolsPath = join(workspacePath, WORKSPACE_FILES.tools);
18490
19852
  await updateManagedSection(toolsPath, platformContent, {
18491
19853
  mode: 'section',
@@ -18494,7 +19856,7 @@ async function refreshPlatformContent(options) {
18494
19856
  coreVersion,
18495
19857
  stalenessThresholdMs,
18496
19858
  });
18497
- // 8. Write SOUL.md managed block
19859
+ // 7. Write SOUL.md managed block
18498
19860
  const soulPath = join(workspacePath, WORKSPACE_FILES.soul);
18499
19861
  await updateManagedSection(soulPath, soulSectionContent, {
18500
19862
  mode: 'block',
@@ -18502,7 +19864,7 @@ async function refreshPlatformContent(options) {
18502
19864
  coreVersion,
18503
19865
  stalenessThresholdMs,
18504
19866
  });
18505
- // 9. Write AGENTS.md managed block
19867
+ // 8. Write AGENTS.md managed block
18506
19868
  const agentsPath = join(workspacePath, WORKSPACE_FILES.agents);
18507
19869
  await updateManagedSection(agentsPath, agentsSectionContent, {
18508
19870
  mode: 'block',
@@ -18510,7 +19872,7 @@ async function refreshPlatformContent(options) {
18510
19872
  coreVersion,
18511
19873
  stalenessThresholdMs,
18512
19874
  });
18513
- // 10. Copy templates to config dir
19875
+ // 9. Copy templates to config dir
18514
19876
  copyTemplates(coreConfigDir);
18515
19877
  }
18516
19878
 
@@ -18590,6 +19952,8 @@ class ComponentWriter {
18590
19952
  coreVersion: CORE_VERSION,
18591
19953
  });
18592
19954
  // Platform content maintenance: SOUL.md, AGENTS.md, Platform section
19955
+ // refreshPlatformContent also writes the component version entry
19956
+ // (with serviceVersion from probe) to the shared state file.
18593
19957
  await refreshPlatformContent({
18594
19958
  coreVersion: CORE_VERSION,
18595
19959
  componentName: this.component.name,
@@ -18744,88 +20108,204 @@ function createComponentWriter(component, options) {
18744
20108
  }
18745
20109
 
18746
20110
  /**
18747
- * Resolve the OpenClaw workspace root from the plugin API.
20111
+ * Plugin resolution helpers for the OpenClaw plugin SDK.
18748
20112
  *
18749
20113
  * @remarks
18750
- * Tries three sources in order:
18751
- * 1. `api.config.agents.defaults.workspace` explicit config (most authoritative)
18752
- * 2. `api.resolvePath('.')` gateway-provided path resolver
18753
- * 3. `process.cwd()` — last resort (unsafe when gateway runs from system32)
18754
- *
18755
- * The config value is checked first because `api.resolvePath('.')` delegates
18756
- * to `path.resolve('.')`, which returns `process.cwd()` — not the workspace.
18757
- * When the gateway runs as a Windows service from `C:\Windows\system32`,
18758
- * `resolvePath('.')` returns system32, not the configured workspace.
18759
- *
18760
- * Plugins should call this once at registration time and pass the result
18761
- * to `init({ workspacePath })`.
20114
+ * Provides workspace path resolution and plugin setting resolution
20115
+ * with a standard three-step fallback chain:
20116
+ * plugin config environment variable → default value.
18762
20117
  */
18763
20118
  /**
18764
20119
  * Resolve the workspace root from the OpenClaw plugin API.
18765
20120
  *
18766
- * @param api - The plugin API object provided by the gateway at registration.
20121
+ * @remarks
20122
+ * Tries three sources in order:
20123
+ * 1. `api.config.agents.defaults.workspace` — explicit config
20124
+ * 2. `api.resolvePath('.')` — gateway-provided path resolver
20125
+ * 3. `process.cwd()` — last resort
20126
+ *
20127
+ * @param api - The plugin API object provided by the gateway.
18767
20128
  * @returns Absolute path to the workspace root.
18768
20129
  */
18769
20130
  function resolveWorkspacePath(api) {
18770
- // 1. Explicit config value (most authoritative)
18771
20131
  const configured = api.config?.agents?.defaults?.workspace;
18772
20132
  if (typeof configured === 'string' && configured.trim()) {
18773
20133
  return configured;
18774
20134
  }
18775
- // 2. Gateway-provided path resolver
18776
20135
  if (typeof api.resolvePath === 'function') {
18777
20136
  return api.resolvePath('.');
18778
20137
  }
18779
- // 3. Last resort — unsafe when gateway runs from system32
18780
20138
  return process.cwd();
18781
20139
  }
18782
-
18783
20140
  /**
18784
- * Shared constants for the jeeves-server OpenClaw plugin.
20141
+ * Resolve a plugin setting via the standard three-step fallback chain:
20142
+ * plugin config → environment variable → fallback value.
20143
+ *
20144
+ * @param api - Plugin API object.
20145
+ * @param pluginId - Plugin identifier (e.g., 'jeeves-watcher-openclaw').
20146
+ * @param key - Config key within the plugin's config object.
20147
+ * @param envVar - Environment variable name.
20148
+ * @param fallback - Default value if neither source provides one.
20149
+ * @returns The resolved setting value.
18785
20150
  */
18786
- /** Plugin identifier used in OpenClaw config and extension paths. */
18787
- const PLUGIN_ID = 'jeeves-server-openclaw';
20151
+ function resolvePluginSetting(api, pluginId, key, envVar, fallback) {
20152
+ const fromPlugin = api.config?.plugins?.entries?.[pluginId]?.config?.[key];
20153
+ if (typeof fromPlugin === 'string')
20154
+ return fromPlugin;
20155
+ const fromEnv = process.env[envVar];
20156
+ if (fromEnv)
20157
+ return fromEnv;
20158
+ return fallback;
20159
+ }
18788
20160
 
18789
20161
  /**
18790
- * Shared types and utility functions for the OpenClaw plugin tool registrations.
20162
+ * HTTP helpers for the OpenClaw plugin SDK.
20163
+ *
20164
+ * @remarks
20165
+ * Thin wrappers around `fetch` that throw on non-OK responses
20166
+ * and handle JSON serialisation/deserialisation.
20167
+ */
20168
+ /**
20169
+ * Fetch JSON from a URL, throwing on non-OK responses.
20170
+ *
20171
+ * @param url - URL to fetch.
20172
+ * @param init - Optional `fetch` init options.
20173
+ * @returns Parsed JSON response body.
20174
+ * @throws Error with `HTTP {status}: {body}` message on non-OK responses.
18791
20175
  */
18792
- const DEFAULT_API_URL = 'http://127.0.0.1:1934';
18793
- /** Extract plugin config from the API object. */
18794
- function getPluginConfig(api) {
18795
- return api.config?.plugins?.entries?.[PLUGIN_ID]?.config;
20176
+ async function fetchJson(url, init) {
20177
+ const res = await fetch(url, init);
20178
+ if (!res.ok) {
20179
+ throw new Error('HTTP ' + String(res.status) + ': ' + (await res.text()));
20180
+ }
20181
+ return res.json();
18796
20182
  }
18797
- /** Resolve the server API base URL from plugin config. */
18798
- function getApiUrl(api) {
18799
- const url = getPluginConfig(api)?.apiUrl;
18800
- return typeof url === 'string' ? url : DEFAULT_API_URL;
20183
+
20184
+ /**
20185
+ * OpenClaw configuration helpers for plugin CLI installers.
20186
+ *
20187
+ * @remarks
20188
+ * Provides resolution of OpenClaw home directory and config file path,
20189
+ * plus idempotent config patching for plugin install/uninstall.
20190
+ */
20191
+ /**
20192
+ * Resolve the OpenClaw home directory.
20193
+ *
20194
+ * @remarks
20195
+ * Resolution order:
20196
+ * 1. `OPENCLAW_CONFIG` env var → dirname of the config file path
20197
+ * 2. `OPENCLAW_HOME` env var → resolved path
20198
+ * 3. Default: `~/.openclaw`
20199
+ *
20200
+ * @returns Absolute path to the OpenClaw home directory.
20201
+ */
20202
+ function resolveOpenClawHome() {
20203
+ if (process.env.OPENCLAW_CONFIG) {
20204
+ return dirname(resolve(process.env.OPENCLAW_CONFIG));
20205
+ }
20206
+ if (process.env.OPENCLAW_HOME) {
20207
+ return resolve(process.env.OPENCLAW_HOME);
20208
+ }
20209
+ return join(homedir(), '.openclaw');
18801
20210
  }
18802
- /** Resolve the plugin key seed from plugin config. */
18803
- function getPluginKey(api) {
18804
- const key = getPluginConfig(api)?.pluginKey;
18805
- return typeof key === 'string' ? key : undefined;
20211
+ /**
20212
+ * Resolve the OpenClaw config file path.
20213
+ *
20214
+ * @remarks
20215
+ * If `OPENCLAW_CONFIG` is set, uses that directly.
20216
+ * Otherwise defaults to `{home}/openclaw.json`.
20217
+ *
20218
+ * @param home - The OpenClaw home directory.
20219
+ * @returns Absolute path to the config file.
20220
+ */
20221
+ function resolveConfigPath(home) {
20222
+ if (process.env.OPENCLAW_CONFIG) {
20223
+ return resolve(process.env.OPENCLAW_CONFIG);
20224
+ }
20225
+ return join(home, 'openclaw.json');
18806
20226
  }
18807
- /** Derive HMAC key from seed. */
18808
- function deriveKey(seed) {
18809
- return createHmac('sha256', seed)
18810
- .update('insider')
18811
- .digest('hex')
18812
- .substring(0, 32);
20227
+ /**
20228
+ * Patch an allowlist array: add or remove the plugin ID.
20229
+ *
20230
+ * @returns A log message if a change was made, or undefined.
20231
+ */
20232
+ function patchAllowList(parent, key, label, pluginId, mode) {
20233
+ {
20234
+ if (!Array.isArray(parent[key]))
20235
+ return undefined;
20236
+ const list = parent[key];
20237
+ const filtered = list.filter((id) => id !== pluginId);
20238
+ if (filtered.length !== list.length) {
20239
+ parent[key] = filtered;
20240
+ return `Removed "${pluginId}" from ${label}`;
20241
+ }
20242
+ }
20243
+ return undefined;
18813
20244
  }
18814
- /** Append auth key query param to a URL. */
18815
- function withAuth(url, keySeed) {
18816
- if (!keySeed)
18817
- return url;
18818
- const derived = deriveKey(keySeed);
18819
- const sep = url.includes('?') ? '&' : '?';
18820
- return url + sep + 'key=' + derived;
20245
+ /**
20246
+ * Patch an OpenClaw config for plugin install or uninstall.
20247
+ *
20248
+ * @remarks
20249
+ * Manages `plugins.entries.{pluginId}` and `tools.alsoAllow`.
20250
+ * Idempotent: adding twice produces no duplicates; removing when absent
20251
+ * produces no errors.
20252
+ *
20253
+ * @param config - The parsed OpenClaw config object (mutated in place).
20254
+ * @param pluginId - The plugin identifier.
20255
+ * @param mode - Whether to add or remove the plugin.
20256
+ * @returns Array of log messages describing changes made.
20257
+ */
20258
+ function patchConfig(config, pluginId, mode) {
20259
+ const messages = [];
20260
+ // Ensure plugins section
20261
+ if (!config.plugins || typeof config.plugins !== 'object') {
20262
+ config.plugins = {};
20263
+ }
20264
+ const plugins = config.plugins;
20265
+ // plugins.entries
20266
+ if (!plugins.entries || typeof plugins.entries !== 'object') {
20267
+ plugins.entries = {};
20268
+ }
20269
+ const entries = plugins.entries;
20270
+ if (pluginId in entries) {
20271
+ Reflect.deleteProperty(entries, pluginId);
20272
+ messages.push(`Removed "${pluginId}" from plugins.entries`);
20273
+ }
20274
+ // tools.alsoAllow
20275
+ if (!config.tools || typeof config.tools !== 'object') {
20276
+ config.tools = {};
20277
+ }
20278
+ const tools = config.tools;
20279
+ const toolAlsoAllow = patchAllowList(tools, 'alsoAllow', 'tools.alsoAllow', pluginId);
20280
+ if (toolAlsoAllow)
20281
+ messages.push(toolAlsoAllow);
20282
+ return messages;
18821
20283
  }
18822
- /** Format a successful tool result. */
20284
+
20285
+ /**
20286
+ * Tool result formatters for the OpenClaw plugin SDK.
20287
+ *
20288
+ * @remarks
20289
+ * Provides standardised helpers for building `ToolResult` objects:
20290
+ * success, error, and connection-error variants.
20291
+ */
20292
+ /**
20293
+ * Format a successful tool result.
20294
+ *
20295
+ * @param data - Arbitrary data to return as JSON.
20296
+ * @returns A `ToolResult` with JSON-stringified content.
20297
+ */
18823
20298
  function ok(data) {
18824
20299
  return {
18825
20300
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
18826
20301
  };
18827
20302
  }
18828
- /** Format an error tool result. */
20303
+ /**
20304
+ * Format an error tool result.
20305
+ *
20306
+ * @param error - Error instance, string, or other value.
20307
+ * @returns A `ToolResult` with `isError: true`.
20308
+ */
18829
20309
  function fail(error) {
18830
20310
  const message = error instanceof Error ? error.message : String(error);
18831
20311
  return {
@@ -18833,8 +20313,21 @@ function fail(error) {
18833
20313
  isError: true,
18834
20314
  };
18835
20315
  }
18836
- /** Format a connection error with actionable guidance. */
18837
- function connectionFail(error, baseUrl) {
20316
+ /**
20317
+ * Format a connection error with actionable guidance.
20318
+ *
20319
+ * @remarks
20320
+ * Detects `ECONNREFUSED`, `ENOTFOUND`, and `ETIMEDOUT` from
20321
+ * `error.cause.code` and returns a user-friendly message referencing
20322
+ * the plugin's `config.apiUrl` setting. Falls back to `fail()` for
20323
+ * non-connection errors.
20324
+ *
20325
+ * @param error - Error instance (typically from `fetch`).
20326
+ * @param baseUrl - The URL that was being contacted.
20327
+ * @param pluginId - The plugin identifier for config guidance.
20328
+ * @returns A `ToolResult` with `isError: true`.
20329
+ */
20330
+ function connectionFail(error, baseUrl, pluginId) {
18838
20331
  const cause = error instanceof Error ? error.cause : undefined;
18839
20332
  const code = cause && typeof cause === 'object' && 'code' in cause
18840
20333
  ? String(cause.code)
@@ -18846,9 +20339,9 @@ function connectionFail(error, baseUrl) {
18846
20339
  {
18847
20340
  type: 'text',
18848
20341
  text: [
18849
- 'Server not reachable at ' + baseUrl + '.',
18850
- 'Either start the jeeves-server service, or if it runs on a different port,',
18851
- 'set plugins.entries.jeeves-server-openclaw.config.apiUrl in openclaw.json.',
20342
+ `Service not reachable at ${baseUrl}.`,
20343
+ 'Either start the service, or if it runs on a different port,',
20344
+ `set plugins.entries.${pluginId}.config.apiUrl in openclaw.json.`,
18852
20345
  ].join('\n'),
18853
20346
  },
18854
20347
  ],
@@ -18857,14 +20350,12 @@ function connectionFail(error, baseUrl) {
18857
20350
  }
18858
20351
  return fail(error);
18859
20352
  }
18860
- /** Fetch JSON from a URL, throwing on non-OK responses. */
18861
- async function fetchJson(url, init) {
18862
- const res = await fetch(url, init);
18863
- if (!res.ok) {
18864
- throw new Error('HTTP ' + String(res.status) + ': ' + (await res.text()));
18865
- }
18866
- return res.json();
18867
- }
20353
+
20354
+ /**
20355
+ * Shared constants for the jeeves-server OpenClaw plugin.
20356
+ */
20357
+ /** Plugin identifier used in OpenClaw config and extension paths. */
20358
+ const PLUGIN_ID = 'jeeves-server-openclaw';
18868
20359
 
18869
20360
  /**
18870
20361
  * Generates the Server menu string for TOOLS.md injection.
@@ -18961,6 +20452,38 @@ async function generateServerMenu(apiUrl) {
18961
20452
  return lines.join('\n');
18962
20453
  }
18963
20454
 
20455
+ /**
20456
+ * Server-specific HMAC authentication helpers for the OpenClaw plugin.
20457
+ *
20458
+ * @remarks
20459
+ * These helpers handle insider key derivation and URL signing for
20460
+ * authenticated requests to jeeves-server. Generic helpers (ok, fail,
20461
+ * connectionFail, fetchJson, etc.) are imported from `@karmaniverous/jeeves`.
20462
+ *
20463
+ * @packageDocumentation
20464
+ */
20465
+ /** Resolve the plugin key seed from plugin config. */
20466
+ function getPluginKey(api) {
20467
+ const config = api.config?.plugins?.entries?.[PLUGIN_ID]?.config;
20468
+ const key = config?.pluginKey;
20469
+ return typeof key === 'string' ? key : undefined;
20470
+ }
20471
+ /** Derive HMAC key from seed. */
20472
+ function deriveKey(seed) {
20473
+ return createHmac('sha256', seed)
20474
+ .update('insider')
20475
+ .digest('hex')
20476
+ .substring(0, 32);
20477
+ }
20478
+ /** Append auth key query param to a URL. */
20479
+ function withAuth(url, keySeed) {
20480
+ if (!keySeed)
20481
+ return url;
20482
+ const derived = deriveKey(keySeed);
20483
+ const sep = url.includes('?') ? '&' : '?';
20484
+ return url + sep + 'key=' + derived;
20485
+ }
20486
+
18964
20487
  /**
18965
20488
  * Server tool registrations (server_* tools) for the OpenClaw plugin.
18966
20489
  */
@@ -18990,7 +20513,7 @@ function registerApiTool(api, baseUrl, keySeed, config) {
18990
20513
  return ok(data);
18991
20514
  }
18992
20515
  catch (error) {
18993
- return connectionFail(error, baseUrl);
20516
+ return connectionFail(error, baseUrl, PLUGIN_ID);
18994
20517
  }
18995
20518
  },
18996
20519
  }, { optional: true });
@@ -19121,75 +20644,6 @@ function registerServerTools(api, baseUrl) {
19121
20644
  }
19122
20645
  }
19123
20646
 
19124
- /**
19125
- * OpenClaw home and config path resolution.
19126
- *
19127
- * @remarks
19128
- * Shared by the CLI installer and the in-process plugin commands.
19129
- */
19130
- /**
19131
- * Resolve the OpenClaw home directory from environment or default.
19132
- */
19133
- function resolveOpenClawHome() {
19134
- if (process.env.OPENCLAW_CONFIG)
19135
- return dirname(resolve(process.env.OPENCLAW_CONFIG));
19136
- if (process.env.OPENCLAW_HOME)
19137
- return resolve(process.env.OPENCLAW_HOME);
19138
- return join(homedir(), '.openclaw');
19139
- }
19140
- /**
19141
- * Resolve the OpenClaw config file path from environment or default.
19142
- */
19143
- function resolveConfigPath(home) {
19144
- if (process.env.OPENCLAW_CONFIG)
19145
- return resolve(process.env.OPENCLAW_CONFIG);
19146
- return join(home, 'openclaw.json');
19147
- }
19148
-
19149
- /**
19150
- * OpenClaw config patching utilities.
19151
- *
19152
- * @remarks
19153
- * Shared by both the `npx ... install|uninstall` CLI and by in-process
19154
- * plugin commands.
19155
- */
19156
- function patchAllowList(parent, key, label, mode) {
19157
- if (!Array.isArray(parent[key]) || parent[key].length === 0)
19158
- return undefined;
19159
- const list = parent[key];
19160
- {
19161
- const filtered = list.filter((id) => id !== PLUGIN_ID);
19162
- if (filtered.length !== list.length) {
19163
- parent[key] = filtered;
19164
- return 'Removed "' + PLUGIN_ID + '" from ' + label;
19165
- }
19166
- }
19167
- return undefined;
19168
- }
19169
- function patchConfig(config, mode) {
19170
- const messages = [];
19171
- if (!config.plugins || typeof config.plugins !== 'object')
19172
- config.plugins = {};
19173
- const plugins = config.plugins;
19174
- const pluginAllow = patchAllowList(plugins, 'allow', 'plugins.allow');
19175
- if (pluginAllow)
19176
- messages.push(pluginAllow);
19177
- if (!plugins.entries || typeof plugins.entries !== 'object')
19178
- plugins.entries = {};
19179
- const entries = plugins.entries;
19180
- if (PLUGIN_ID in entries) {
19181
- Reflect.deleteProperty(entries, PLUGIN_ID);
19182
- messages.push('Removed "' + PLUGIN_ID + '" from plugins.entries');
19183
- }
19184
- if (!config.tools || typeof config.tools !== 'object')
19185
- config.tools = {};
19186
- const tools = config.tools;
19187
- const toolAllow = patchAllowList(tools, 'allow', 'tools.allow');
19188
- if (toolAllow)
19189
- messages.push(toolAllow);
19190
- return messages;
19191
- }
19192
-
19193
20647
  /**
19194
20648
  * Shared plugin removal logic for extension directory and config cleanup.
19195
20649
  *
@@ -19214,7 +20668,7 @@ function removePlugin(home, configPath) {
19214
20668
  if (existsSync(configPath)) {
19215
20669
  const raw = readFileSync(configPath, 'utf8');
19216
20670
  const config = JSON.parse(raw);
19217
- const patchMessages = patchConfig(config);
20671
+ const patchMessages = patchConfig(config, PLUGIN_ID);
19218
20672
  if (patchMessages.length > 0) {
19219
20673
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
19220
20674
  messages.push(...patchMessages);
@@ -19307,18 +20761,17 @@ function createPluginCommands() {
19307
20761
  /** Plugin version derived from package.json at runtime. */
19308
20762
  const require$1 = createRequire(import.meta.url);
19309
20763
  const { version: PLUGIN_VERSION } = require$1('../package.json');
19310
- /** Default config root when not specified in plugin config. */
19311
- const DEFAULT_CONFIG_ROOT = 'j:/config';
19312
20764
  /** Refresh interval in seconds (must be prime). */
19313
20765
  const REFRESH_INTERVAL_SECONDS = 61;
19314
20766
  /** Active writer instance — stopped on re-registration to prevent leaks. */
19315
20767
  let activeWriter = null;
19316
- /**
19317
- * Extract the configRoot from plugin config.
19318
- */
20768
+ /** Resolve the server API base URL from plugin config or environment. */
20769
+ function getServiceUrl(api) {
20770
+ return resolvePluginSetting(api, PLUGIN_ID, 'apiUrl', 'JEEVES_SERVER_URL', 'http://127.0.0.1:1934');
20771
+ }
20772
+ /** Resolve the platform config root from plugin config or environment. */
19319
20773
  function getConfigRoot(api) {
19320
- const root = getPluginConfig(api)?.configRoot;
19321
- return typeof root === 'string' ? root : DEFAULT_CONFIG_ROOT;
20774
+ return resolvePluginSetting(api, PLUGIN_ID, 'configRoot', 'JEEVES_CONFIG_ROOT', 'j:/config');
19322
20775
  }
19323
20776
  /** Register all jeeves-server tools and start the TOOLS.md writer. */
19324
20777
  function register(api) {
@@ -19327,7 +20780,7 @@ function register(api) {
19327
20780
  activeWriter.stop();
19328
20781
  activeWriter = null;
19329
20782
  }
19330
- const baseUrl = getApiUrl(api);
20783
+ const baseUrl = getServiceUrl(api);
19331
20784
  registerServerTools(api, baseUrl);
19332
20785
  // Initialize jeeves-core
19333
20786
  const workspacePath = resolveWorkspacePath(api);