@karmaniverous/jeeves-meta 0.1.0 → 0.2.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,7 +1,7 @@
1
1
  import { readdirSync, unlinkSync, readFileSync, mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs';
2
2
  import { join, dirname, relative, sep } from 'node:path';
3
- import { randomUUID, createHash } from 'node:crypto';
4
3
  import { z } from 'zod';
4
+ import { randomUUID, createHash } from 'node:crypto';
5
5
 
6
6
  /**
7
7
  * List archive snapshot files in chronological order.
@@ -99,6 +99,201 @@ function createSnapshot(metaPath, meta) {
99
99
  return archiveFile;
100
100
  }
101
101
 
102
+ /**
103
+ * Zod schema for jeeves-meta configuration.
104
+ *
105
+ * Consumers load config however they want (file, env, constructor).
106
+ * The library validates via this schema.
107
+ *
108
+ * @module schema/config
109
+ */
110
+ /** Zod schema for jeeves-meta configuration. */
111
+ const synthConfigSchema = z.object({
112
+ /** Filesystem paths to watch for .meta/ directories. */
113
+ watchPaths: z.array(z.string()).min(1),
114
+ /** Watcher service base URL. */
115
+ watcherUrl: z.url(),
116
+ /** OpenClaw gateway base URL for subprocess spawning. */
117
+ gatewayUrl: z.url().default('http://127.0.0.1:3000'),
118
+ /** Optional API key for gateway authentication. */
119
+ gatewayApiKey: z.string().optional(),
120
+ /** Run architect every N cycles (per meta). */
121
+ architectEvery: z.number().int().min(1).default(10),
122
+ /** Exponent for depth weighting in staleness formula. */
123
+ depthWeight: z.number().min(0).default(0.5),
124
+ /** Maximum archive snapshots to retain per meta. */
125
+ maxArchive: z.number().int().min(1).default(20),
126
+ /** Maximum lines of context to include in subprocess prompts. */
127
+ maxLines: z.number().int().min(50).default(500),
128
+ /** Architect subprocess timeout in seconds. */
129
+ architectTimeout: z.number().int().min(30).default(120),
130
+ /** Builder subprocess timeout in seconds. */
131
+ builderTimeout: z.number().int().min(60).default(600),
132
+ /** Critic subprocess timeout in seconds. */
133
+ criticTimeout: z.number().int().min(30).default(300),
134
+ /** Resolved architect system prompt text. */
135
+ defaultArchitect: z.string(),
136
+ /** Resolved critic system prompt text. */
137
+ defaultCritic: z.string(),
138
+ /**
139
+ * When true, skip unchanged candidates and iterate to the next-stalest
140
+ * until finding one with actual changes. Skipped candidates get their
141
+ * _generatedAt bumped to prevent re-selection next cycle.
142
+ */
143
+ skipUnchanged: z.boolean().default(true),
144
+ /** Number of metas to synthesize per invocation. */
145
+ batchSize: z.number().int().min(1).default(1),
146
+ });
147
+
148
+ /**
149
+ * Structured error from a synthesis step failure.
150
+ *
151
+ * @module schema/error
152
+ */
153
+ /** Zod schema for synthesis step errors. */
154
+ const synthErrorSchema = z.object({
155
+ /** Which step failed: 'architect', 'builder', or 'critic'. */
156
+ step: z.enum(['architect', 'builder', 'critic']),
157
+ /** Error classification code. */
158
+ code: z.string(),
159
+ /** Human-readable error message. */
160
+ message: z.string(),
161
+ });
162
+
163
+ /**
164
+ * Zod schema for .meta/meta.json files.
165
+ *
166
+ * Reserved properties are underscore-prefixed and engine-managed.
167
+ * All other keys are open schema (builder output).
168
+ *
169
+ * @module schema/meta
170
+ */
171
+ /** Zod schema for the reserved (underscore-prefixed) meta.json properties. */
172
+ const metaJsonSchema = z
173
+ .object({
174
+ /** Stable identity. Generated on first synthesis, never changes. */
175
+ _id: z.uuid(),
176
+ /** Human-provided steering prompt. Optional. */
177
+ _steer: z.string().optional(),
178
+ /** Architect system prompt used this turn. Defaults from config. */
179
+ _architect: z.string().optional(),
180
+ /**
181
+ * Task brief generated by the architect. Cached and reused across cycles;
182
+ * regenerated only when triggered.
183
+ */
184
+ _builder: z.string().optional(),
185
+ /** Critic system prompt used this turn. Defaults from config. */
186
+ _critic: z.string().optional(),
187
+ /** Timestamp of last synthesis. ISO 8601. */
188
+ _generatedAt: z.iso.datetime().optional(),
189
+ /** Narrative synthesis output. Rendered by watcher for embedding. */
190
+ _content: z.string().optional(),
191
+ /**
192
+ * Hash of sorted file listing in scope. Detects directory structure
193
+ * changes that trigger an architect re-run.
194
+ */
195
+ _structureHash: z.string().optional(),
196
+ /**
197
+ * Cycles since last architect run. Reset to 0 when architect runs.
198
+ * Used with architectEvery to trigger periodic re-prompting.
199
+ */
200
+ _synthesisCount: z.number().int().min(0).optional(),
201
+ /** Critic evaluation of the last synthesis. */
202
+ _feedback: z.string().optional(),
203
+ /**
204
+ * Present and true on archive snapshots. Distinguishes live vs. archived
205
+ * metas.
206
+ */
207
+ _archived: z.boolean().optional(),
208
+ /** Timestamp when this snapshot was archived. ISO 8601. */
209
+ _archivedAt: z.iso.datetime().optional(),
210
+ /**
211
+ * Scheduling priority. Higher = updates more often. Negative allowed;
212
+ * normalized to min 0 at scheduling time.
213
+ */
214
+ _depth: z.number().optional(),
215
+ /**
216
+ * Emphasis multiplier for depth weighting in scheduling.
217
+ * Default 1. Higher values increase this meta's scheduling priority
218
+ * relative to its depth. Set to 0.5 to halve the depth effect,
219
+ * 2 to double it, 0 to ignore depth entirely for this meta.
220
+ */
221
+ _emphasis: z.number().min(0).optional(),
222
+ /** Token count from last architect subprocess call. */
223
+ _architectTokens: z.number().int().optional(),
224
+ /** Token count from last builder subprocess call. */
225
+ _builderTokens: z.number().int().optional(),
226
+ /** Token count from last critic subprocess call. */
227
+ _criticTokens: z.number().int().optional(),
228
+ /** Exponential moving average of architect token usage (decay 0.3). */
229
+ _architectTokensAvg: z.number().optional(),
230
+ /** Exponential moving average of builder token usage (decay 0.3). */
231
+ _builderTokensAvg: z.number().optional(),
232
+ /** Exponential moving average of critic token usage (decay 0.3). */
233
+ _criticTokensAvg: z.number().optional(),
234
+ /**
235
+ * Structured error from last cycle. Present when a step failed.
236
+ * Cleared on successful cycle.
237
+ */
238
+ _error: synthErrorSchema.optional(),
239
+ })
240
+ .loose();
241
+
242
+ /**
243
+ * Load and resolve jeeves-meta config with \@file: indirection.
244
+ *
245
+ * @module configLoader
246
+ */
247
+ /**
248
+ * Resolve \@file: references in a config value.
249
+ *
250
+ * @param value - String value that may start with "\@file:".
251
+ * @param baseDir - Base directory for resolving relative paths.
252
+ * @returns The resolved string (file contents or original value).
253
+ */
254
+ function resolveFileRef(value, baseDir) {
255
+ if (!value.startsWith('@file:'))
256
+ return value;
257
+ const filePath = join(baseDir, value.slice(6));
258
+ return readFileSync(filePath, 'utf8');
259
+ }
260
+ /**
261
+ * Load synth config from a JSON file, resolving \@file: references.
262
+ *
263
+ * @param configPath - Path to jeeves-meta.config.json.
264
+ * @returns Validated SynthConfig with resolved prompt strings.
265
+ */
266
+ function loadSynthConfig(configPath) {
267
+ const raw = JSON.parse(readFileSync(configPath, 'utf8'));
268
+ const baseDir = dirname(configPath);
269
+ if (typeof raw.defaultArchitect === 'string') {
270
+ raw.defaultArchitect = resolveFileRef(raw.defaultArchitect, baseDir);
271
+ }
272
+ if (typeof raw.defaultCritic === 'string') {
273
+ raw.defaultCritic = resolveFileRef(raw.defaultCritic, baseDir);
274
+ }
275
+ return synthConfigSchema.parse(raw);
276
+ }
277
+ /**
278
+ * Resolve config path from --config flag or JEEVES_META_CONFIG env var.
279
+ *
280
+ * @param args - CLI arguments (process.argv.slice(2)).
281
+ * @returns Resolved config path.
282
+ * @throws If no config path found.
283
+ */
284
+ function resolveConfigPath(args) {
285
+ // Check --config flag
286
+ const configIdx = args.indexOf('--config');
287
+ if (configIdx !== -1 && args[configIdx + 1]) {
288
+ return args[configIdx + 1];
289
+ }
290
+ // Check env var
291
+ const envPath = process.env['JEEVES_META_CONFIG'];
292
+ if (envPath)
293
+ return envPath;
294
+ throw new Error('Config path required. Use --config <path> or set JEEVES_META_CONFIG env var.');
295
+ }
296
+
102
297
  /**
103
298
  * Ensure meta.json exists in each .meta/ directory.
104
299
  *
@@ -243,6 +438,16 @@ function buildOwnershipTree(metaPaths) {
243
438
  }
244
439
  return { nodes, roots };
245
440
  }
441
+ /**
442
+ * Find a node in the ownership tree by meta path or owner path.
443
+ *
444
+ * @param tree - The ownership tree to search.
445
+ * @param targetPath - Path to search for (meta path or owner path).
446
+ * @returns The matching node, or undefined if not found.
447
+ */
448
+ function findNode(tree, targetPath) {
449
+ return Array.from(tree.nodes.values()).find((n) => n.metaPath === targetPath || n.ownerPath === targetPath);
450
+ }
246
451
 
247
452
  /**
248
453
  * Compute the file scope owned by a meta node.
@@ -379,12 +584,6 @@ function condenseScopeFiles(files, maxIndividual = 30) {
379
584
  .map(([pattern, count]) => pattern + ' (' + count.toString() + ' files)')
380
585
  .join('\n');
381
586
  }
382
- /** Filter files to exclude child meta subtrees. */
383
- function excludeChildSubtrees(files, childPrefixes) {
384
- if (childPrefixes.length === 0)
385
- return files;
386
- return files.filter((f) => childPrefixes.every((cp) => !f.startsWith(cp)));
387
- }
388
587
  /**
389
588
  * Build the context package for a synthesis cycle.
390
589
  *
@@ -394,13 +593,11 @@ function excludeChildSubtrees(files, childPrefixes) {
394
593
  * @returns The computed context package.
395
594
  */
396
595
  async function buildContextPackage(node, meta, watcher) {
397
- const childPrefixes = node.children.map((c) => c.ownerPath + '/');
398
596
  // Scope files via watcher scan, excluding child subtrees
399
597
  const allScanFiles = await paginatedScan(watcher, {
400
598
  pathPrefix: node.ownerPath,
401
599
  });
402
- const allFiles = allScanFiles.map((f) => f.file_path);
403
- const scopeFiles = excludeChildSubtrees(allFiles, childPrefixes);
600
+ const scopeFiles = filterInScope(node, allScanFiles.map((f) => f.file_path));
404
601
  // Delta files: modified since _generatedAt
405
602
  let deltaFiles;
406
603
  if (meta._generatedAt) {
@@ -409,7 +606,7 @@ async function buildContextPackage(node, meta, watcher) {
409
606
  pathPrefix: node.ownerPath,
410
607
  modifiedAfter,
411
608
  });
412
- deltaFiles = excludeChildSubtrees(deltaScanFiles.map((f) => f.file_path), childPrefixes);
609
+ deltaFiles = filterInScope(node, deltaScanFiles.map((f) => f.file_path));
413
610
  }
414
611
  else {
415
612
  deltaFiles = scopeFiles; // First run: all files are delta
@@ -555,142 +752,6 @@ function buildCriticTask(ctx, meta, config) {
555
752
  return sections.join('\n');
556
753
  }
557
754
 
558
- /**
559
- * Zod schema for jeeves-meta configuration.
560
- *
561
- * Consumers load config however they want (file, env, constructor).
562
- * The library validates via this schema.
563
- *
564
- * @module schema/config
565
- */
566
- /** Zod schema for jeeves-meta configuration. */
567
- const synthConfigSchema = z.object({
568
- /** Filesystem paths to watch for .meta/ directories. */
569
- watchPaths: z.array(z.string()).min(1),
570
- /** Watcher service base URL. */
571
- watcherUrl: z.url(),
572
- /** Run architect every N cycles (per meta). */
573
- architectEvery: z.number().int().min(1).default(10),
574
- /** Exponent for depth weighting in staleness formula. */
575
- depthWeight: z.number().min(0).default(0.5),
576
- /** Maximum archive snapshots to retain per meta. */
577
- maxArchive: z.number().int().min(1).default(20),
578
- /** Maximum lines of context to include in subprocess prompts. */
579
- maxLines: z.number().int().min(50).default(500),
580
- /** Architect subprocess timeout in seconds. */
581
- architectTimeout: z.number().int().min(30).default(120),
582
- /** Builder subprocess timeout in seconds. */
583
- builderTimeout: z.number().int().min(60).default(600),
584
- /** Critic subprocess timeout in seconds. */
585
- criticTimeout: z.number().int().min(30).default(300),
586
- /** Resolved architect system prompt text. */
587
- defaultArchitect: z.string(),
588
- /** Resolved critic system prompt text. */
589
- defaultCritic: z.string(),
590
- /**
591
- * When true, skip unchanged candidates and iterate to the next-stalest
592
- * until finding one with actual changes. Skipped candidates get their
593
- * _generatedAt bumped to prevent re-selection next cycle.
594
- */
595
- skipUnchanged: z.boolean().default(true),
596
- /** Number of metas to synthesize per invocation. */
597
- batchSize: z.number().int().min(1).default(1),
598
- });
599
-
600
- /**
601
- * Structured error from a synthesis step failure.
602
- *
603
- * @module schema/error
604
- */
605
- /** Zod schema for synthesis step errors. */
606
- const synthErrorSchema = z.object({
607
- /** Which step failed: 'architect', 'builder', or 'critic'. */
608
- step: z.enum(['architect', 'builder', 'critic']),
609
- /** Error classification code. */
610
- code: z.string(),
611
- /** Human-readable error message. */
612
- message: z.string(),
613
- });
614
-
615
- /**
616
- * Zod schema for .meta/meta.json files.
617
- *
618
- * Reserved properties are underscore-prefixed and engine-managed.
619
- * All other keys are open schema (builder output).
620
- *
621
- * @module schema/meta
622
- */
623
- /** Zod schema for the reserved (underscore-prefixed) meta.json properties. */
624
- const metaJsonSchema = z
625
- .object({
626
- /** Stable identity. Generated on first synthesis, never changes. */
627
- _id: z.uuid(),
628
- /** Human-provided steering prompt. Optional. */
629
- _steer: z.string().optional(),
630
- /** Architect system prompt used this turn. Defaults from config. */
631
- _architect: z.string().optional(),
632
- /**
633
- * Task brief generated by the architect. Cached and reused across cycles;
634
- * regenerated only when triggered.
635
- */
636
- _builder: z.string().optional(),
637
- /** Critic system prompt used this turn. Defaults from config. */
638
- _critic: z.string().optional(),
639
- /** Timestamp of last synthesis. ISO 8601. */
640
- _generatedAt: z.iso.datetime().optional(),
641
- /** Narrative synthesis output. Rendered by watcher for embedding. */
642
- _content: z.string().optional(),
643
- /**
644
- * Hash of sorted file listing in scope. Detects directory structure
645
- * changes that trigger an architect re-run.
646
- */
647
- _structureHash: z.string().optional(),
648
- /**
649
- * Cycles since last architect run. Reset to 0 when architect runs.
650
- * Used with architectEvery to trigger periodic re-prompting.
651
- */
652
- _synthesisCount: z.number().int().min(0).optional(),
653
- /** Critic evaluation of the last synthesis. */
654
- _feedback: z.string().optional(),
655
- /**
656
- * Present and true on archive snapshots. Distinguishes live vs. archived
657
- * metas.
658
- */
659
- _archived: z.boolean().optional(),
660
- /** Timestamp when this snapshot was archived. ISO 8601. */
661
- _archivedAt: z.iso.datetime().optional(),
662
- /**
663
- * Scheduling priority. Higher = updates more often. Negative allowed;
664
- * normalized to min 0 at scheduling time.
665
- */
666
- _depth: z.number().optional(),
667
- /**
668
- * Emphasis multiplier for depth weighting in scheduling.
669
- * Default 1. Higher values increase this meta's scheduling priority
670
- * relative to its depth. Set to 0.5 to halve the depth effect,
671
- * 2 to double it, 0 to ignore depth entirely for this meta.
672
- */
673
- _emphasis: z.number().min(0).optional(),
674
- /** Token count from last architect subprocess call. */
675
- _architectTokens: z.number().int().optional(),
676
- /** Token count from last builder subprocess call. */
677
- _builderTokens: z.number().int().optional(),
678
- /** Token count from last critic subprocess call. */
679
- _criticTokens: z.number().int().optional(),
680
- /** Exponential moving average of architect token usage (decay 0.3). */
681
- _architectTokensAvg: z.number().optional(),
682
- /** Exponential moving average of builder token usage (decay 0.3). */
683
- _builderTokensAvg: z.number().optional(),
684
- /** Exponential moving average of critic token usage (decay 0.3). */
685
- _criticTokensAvg: z.number().optional(),
686
- /**
687
- * Structured error from last cycle. Present when a step failed.
688
- * Cleared on successful cycle.
689
- */
690
- _error: synthErrorSchema.optional(),
691
- })
692
- .loose();
693
-
694
755
  /**
695
756
  * Merge synthesis results into meta.json.
696
757
  *
@@ -874,6 +935,25 @@ function isLocked(metaPath) {
874
935
  }
875
936
  }
876
937
 
938
+ /**
939
+ * Normalize file paths to forward slashes for consistency with watcher-indexed paths.
940
+ *
941
+ * Watcher indexes paths with forward slashes (`j:/domains/...`). This utility
942
+ * ensures all paths in the library use the same convention, regardless of
943
+ * the platform's native separator.
944
+ *
945
+ * @module normalizePath
946
+ */
947
+ /**
948
+ * Normalize a file path to forward slashes.
949
+ *
950
+ * @param p - File path (may contain backslashes).
951
+ * @returns Path with all backslashes replaced by forward slashes.
952
+ */
953
+ function normalizePath(p) {
954
+ return p.replaceAll('\\', '/');
955
+ }
956
+
877
957
  /**
878
958
  * Select the best synthesis candidate from stale metas.
879
959
  *
@@ -937,6 +1017,34 @@ function actualStaleness(meta) {
937
1017
  const generatedMs = new Date(meta._generatedAt).getTime();
938
1018
  return (Date.now() - generatedMs) / 1000;
939
1019
  }
1020
+ /**
1021
+ * Check whether the architect step should be triggered.
1022
+ *
1023
+ * @param meta - Current meta.json.
1024
+ * @param structureChanged - Whether the structure hash changed.
1025
+ * @param steerChanged - Whether the steer directive changed.
1026
+ * @param architectEvery - Config: run architect every N cycles.
1027
+ * @returns True if the architect step should run.
1028
+ */
1029
+ function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
1030
+ return (!meta._builder ||
1031
+ structureChanged ||
1032
+ steerChanged ||
1033
+ (meta._synthesisCount ?? 0) >= architectEvery);
1034
+ }
1035
+ /**
1036
+ * Detect whether the steer directive changed since the last archive.
1037
+ *
1038
+ * @param currentSteer - Current _steer value (or undefined).
1039
+ * @param archiveSteer - Archive _steer value (or undefined).
1040
+ * @param hasArchive - Whether an archive snapshot exists.
1041
+ * @returns True if steer changed.
1042
+ */
1043
+ function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
1044
+ if (!hasArchive)
1045
+ return Boolean(currentSteer);
1046
+ return currentSteer !== archiveSteer;
1047
+ }
940
1048
 
941
1049
  /**
942
1050
  * Weighted staleness formula for candidate selection.
@@ -1070,10 +1178,6 @@ function parseCriticOutput(output) {
1070
1178
  *
1071
1179
  * @module orchestrator/orchestrate
1072
1180
  */
1073
- /** Normalize path separators to forward slashes. */
1074
- function normalizePath(p) {
1075
- return p.replaceAll('\\', '/');
1076
- }
1077
1181
  /** Finalize a cycle: merge, snapshot, prune. */
1078
1182
  function finalizeCycle(metaPath, current, config, architect, builder, critic, builderOutput, feedback, structureHash, synthesisCount, error, architectTokens, builderTokens, criticTokens) {
1079
1183
  const updated = mergeAndWrite({
@@ -1172,16 +1276,11 @@ async function orchestrateOnce(config, executor, watcher) {
1172
1276
  const structureChanged = newStructureHash !== currentMeta._structureHash;
1173
1277
  // Step 6: Steer change detection
1174
1278
  const latestArchive = readLatestArchive(node.metaPath);
1175
- const steerChanged = latestArchive
1176
- ? currentMeta._steer !== latestArchive._steer
1177
- : Boolean(currentMeta._steer);
1279
+ const steerChanged = hasSteerChanged(currentMeta._steer, latestArchive?._steer, Boolean(latestArchive));
1178
1280
  // Step 7: Compute context
1179
1281
  const ctx = await buildContextPackage(node, currentMeta, watcher);
1180
1282
  // Step 8: Architect (conditional)
1181
- const architectTriggered = !currentMeta._builder ||
1182
- structureChanged ||
1183
- steerChanged ||
1184
- (currentMeta._synthesisCount ?? 0) >= config.architectEvery;
1283
+ const architectTriggered = isArchitectTriggered(currentMeta, structureChanged, steerChanged, config.architectEvery);
1185
1284
  let builderBrief = currentMeta._builder ?? '';
1186
1285
  let synthesisCount = currentMeta._synthesisCount ?? 0;
1187
1286
  let stepError = null;
@@ -1307,6 +1406,108 @@ function createSynthEngine(config, executor, watcher) {
1307
1406
  };
1308
1407
  }
1309
1408
 
1409
+ /**
1410
+ * SynthExecutor implementation using the OpenClaw gateway HTTP API.
1411
+ *
1412
+ * Lives in the library package so both plugin and runner can import it.
1413
+ * Spawns sub-agent sessions via the gateway, polls for completion,
1414
+ * and extracts output text.
1415
+ *
1416
+ * @module executor/GatewayExecutor
1417
+ */
1418
+ const DEFAULT_POLL_INTERVAL_MS = 5000;
1419
+ const DEFAULT_TIMEOUT_MS = 600_000; // 10 minutes
1420
+ /** Sleep helper. */
1421
+ function sleep$1(ms) {
1422
+ return new Promise((resolve) => setTimeout(resolve, ms));
1423
+ }
1424
+ /**
1425
+ * SynthExecutor that spawns OpenClaw sessions via the gateway HTTP API.
1426
+ *
1427
+ * Used by both the OpenClaw plugin (in-process tool calls) and the
1428
+ * runner/CLI (external invocation). Constructs from `gatewayUrl` and
1429
+ * optional `apiKey` — typically sourced from `SynthConfig`.
1430
+ */
1431
+ class GatewayExecutor {
1432
+ gatewayUrl;
1433
+ apiKey;
1434
+ pollIntervalMs;
1435
+ constructor(options = {}) {
1436
+ this.gatewayUrl = (options.gatewayUrl ?? 'http://127.0.0.1:3000').replace(/\/+$/, '');
1437
+ this.apiKey = options.apiKey;
1438
+ this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1439
+ }
1440
+ async spawn(task, options) {
1441
+ const timeoutMs = (options?.timeout ?? DEFAULT_TIMEOUT_MS / 1000) * 1000;
1442
+ const deadline = Date.now() + timeoutMs;
1443
+ const headers = {
1444
+ 'Content-Type': 'application/json',
1445
+ };
1446
+ if (this.apiKey) {
1447
+ headers['Authorization'] = 'Bearer ' + this.apiKey;
1448
+ }
1449
+ const spawnRes = await fetch(this.gatewayUrl + '/api/sessions/spawn', {
1450
+ method: 'POST',
1451
+ headers,
1452
+ body: JSON.stringify({
1453
+ task,
1454
+ mode: 'run',
1455
+ model: options?.model,
1456
+ runTimeoutSeconds: options?.timeout,
1457
+ }),
1458
+ });
1459
+ if (!spawnRes.ok) {
1460
+ const text = await spawnRes.text();
1461
+ throw new Error('Gateway spawn failed: HTTP ' +
1462
+ spawnRes.status.toString() +
1463
+ ' - ' +
1464
+ text);
1465
+ }
1466
+ const spawnData = (await spawnRes.json());
1467
+ if (!spawnData.sessionKey) {
1468
+ throw new Error('Gateway spawn returned no sessionKey: ' + JSON.stringify(spawnData));
1469
+ }
1470
+ const { sessionKey } = spawnData;
1471
+ // Poll for completion
1472
+ while (Date.now() < deadline) {
1473
+ await sleep$1(this.pollIntervalMs);
1474
+ const historyRes = await fetch(this.gatewayUrl +
1475
+ '/api/sessions/' +
1476
+ encodeURIComponent(sessionKey) +
1477
+ '/history?limit=50', { headers });
1478
+ if (!historyRes.ok)
1479
+ continue;
1480
+ const history = (await historyRes.json());
1481
+ if (history.status === 'completed' || history.status === 'done') {
1482
+ // Extract token usage from session-level or message-level usage
1483
+ let tokens;
1484
+ if (history.usage?.totalTokens) {
1485
+ tokens = history.usage.totalTokens;
1486
+ }
1487
+ else {
1488
+ // Sum message-level usage as fallback
1489
+ let sum = 0;
1490
+ for (const msg of history.messages ?? []) {
1491
+ if (msg.usage?.totalTokens)
1492
+ sum += msg.usage.totalTokens;
1493
+ }
1494
+ if (sum > 0)
1495
+ tokens = sum;
1496
+ }
1497
+ // Extract the last assistant message as output
1498
+ const messages = history.messages ?? [];
1499
+ for (let i = messages.length - 1; i >= 0; i--) {
1500
+ if (messages[i].role === 'assistant' && messages[i].content) {
1501
+ return { output: messages[i].content, tokens };
1502
+ }
1503
+ }
1504
+ return { output: '', tokens };
1505
+ }
1506
+ }
1507
+ throw new Error('Synthesis subprocess timed out after ' + timeoutMs.toString() + 'ms');
1508
+ }
1509
+ }
1510
+
1310
1511
  /**
1311
1512
  * HTTP implementation of the WatcherClient interface.
1312
1513
  *
@@ -1365,9 +1566,13 @@ class HttpWatcherClient {
1365
1566
  throw new Error('Retry exhausted');
1366
1567
  }
1367
1568
  async scan(params) {
1368
- const body = {
1369
- pathPrefix: params.pathPrefix,
1370
- };
1569
+ const body = {};
1570
+ if (params.pathPrefix !== undefined) {
1571
+ body.pathPrefix = params.pathPrefix;
1572
+ }
1573
+ if (params.filter !== undefined) {
1574
+ body.filter = params.filter;
1575
+ }
1371
1576
  if (params.modifiedAfter !== undefined) {
1372
1577
  body.modifiedAfter = params.modifiedAfter;
1373
1578
  }
@@ -1391,4 +1596,4 @@ class HttpWatcherClient {
1391
1596
  }
1392
1597
  }
1393
1598
 
1394
- export { HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, createSynthEngine, ensureMetaJson, filterInScope, getScopeExclusions, getScopePrefix, globMetas, isLocked, isStale, listArchiveFiles, mergeAndWrite, metaJsonSchema, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
1599
+ export { GatewayExecutor, HttpWatcherClient, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, computeEffectiveStaleness, computeEma, computeStructureHash, createSnapshot, createSynthEngine, ensureMetaJson, filterInScope, findNode, getScopeExclusions, getScopePrefix, globMetas, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, loadSynthConfig, mergeAndWrite, metaJsonSchema, normalizePath, orchestrate, paginatedScan, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, releaseLock, resolveConfigPath, selectCandidate, synthConfigSchema, synthErrorSchema, toSynthError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Knowledge synthesis engine for the Jeeves platform",
6
6
  "license": "BSD-3-Clause",
@@ -107,5 +107,8 @@
107
107
  "npm": {
108
108
  "publish": true
109
109
  }
110
+ },
111
+ "bin": {
112
+ "jeeves-meta": "dist/cli.js"
110
113
  }
111
114
  }