@holoscript/holoscript-agent 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { homedir as homedir3 } from "os";
4
+ import { homedir as homedir3, hostname as hostname2 } from "os";
5
5
  import { join as join4 } from "path";
6
+ import { createHash as createHash4, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
7
+ import { Wallet as Wallet2 } from "ethers";
6
8
  import {
7
9
  createAnthropicProvider,
8
10
  createOpenAIProvider,
9
11
  createGeminiProvider,
10
12
  createMockProvider,
11
- createLocalLLMProvider
13
+ createLocalLLMProvider,
14
+ createXAIProvider,
15
+ createOpenRouterProvider,
16
+ resolveSovereignProviderAsync
12
17
  } from "@holoscript/llm-provider";
13
18
 
14
19
  // src/identity.ts
@@ -16,6 +21,8 @@ var VALID_PROVIDERS = /* @__PURE__ */ new Set([
16
21
  "anthropic",
17
22
  "openai",
18
23
  "gemini",
24
+ "xai",
25
+ "openrouter",
19
26
  "mock",
20
27
  "bitnet",
21
28
  "local-llm"
@@ -36,7 +43,9 @@ function loadIdentity(env = process.env) {
36
43
  const budgetRaw = env.HOLOSCRIPT_AGENT_BUDGET_USD_DAY ?? "5";
37
44
  const budget = Number(budgetRaw);
38
45
  if (!Number.isFinite(budget) || budget < 0) {
39
- throw new Error(`HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`);
46
+ throw new Error(
47
+ `HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`
48
+ );
40
49
  }
41
50
  return {
42
51
  handle,
@@ -74,16 +83,52 @@ function identityForLog(id) {
74
83
  // src/brain.ts
75
84
  import { readFile } from "fs/promises";
76
85
  async function loadBrain(brainPath, scopeTier = "warm") {
77
- const systemPrompt = await readFile(brainPath, "utf8");
78
- const { domain, capabilityTags } = extractIdentity(systemPrompt);
79
- return { brainPath, systemPrompt, capabilityTags, domain, scopeTier };
86
+ const raw = await readFile(brainPath, "utf8");
87
+ const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);
88
+ const systemPrompt = extractSystemPromptPreamble(raw);
89
+ return {
90
+ brainPath,
91
+ systemPrompt,
92
+ capabilityTags,
93
+ domain,
94
+ scopeTier,
95
+ requires,
96
+ prefers,
97
+ avoids,
98
+ reflect: extractReflect(raw)
99
+ };
100
+ }
101
+ function extractReflect(brain) {
102
+ const block = sliceNamedBlock(brain, "reflect");
103
+ if (block === void 0) return void 0;
104
+ const criteria = scalarField(block, "criteria") ?? scalarField(block, "scorer") ?? scalarField(block, "of") ?? "correctness, completeness, and valid HoloScript syntax";
105
+ const escRaw = scalarField(block, "escalate_on_fail") ?? scalarField(block, "escalateOnFail") ?? scalarField(block, "escalate");
106
+ return { criteria, escalateOnFail: (escRaw ?? "").split(",")[0].trim().toLowerCase() === "true" };
107
+ }
108
+ function extractSystemPromptPreamble(src) {
109
+ const lines = src.split("\n");
110
+ const BLOCK_START = /^(#version|#target|#mode|identity\s*\{|state\s*\{|computed\s*\{|traits\s*\[|capabilities\s*\{|directives\s*\{|behavior\s)/;
111
+ let cutLine = -1;
112
+ for (let i = 0; i < lines.length; i++) {
113
+ if (BLOCK_START.test(lines[i].trim())) {
114
+ cutLine = i;
115
+ break;
116
+ }
117
+ }
118
+ if (cutLine <= 0) return src;
119
+ return lines.slice(0, cutLine).join("\n").trimEnd();
80
120
  }
81
121
  function extractIdentity(brain) {
82
122
  const identityBlock = sliceNamedBlock(brain, "identity");
83
- if (!identityBlock) return { domain: "unknown", capabilityTags: [] };
123
+ if (!identityBlock) {
124
+ return { domain: "unknown", capabilityTags: [], requires: [], prefers: [], avoids: [] };
125
+ }
84
126
  const domain = scalarField(identityBlock, "domain") ?? "unknown";
85
127
  const capabilityTags = listField(identityBlock, "capability_tags") ?? [];
86
- return { domain, capabilityTags };
128
+ const requires = listField(identityBlock, "requires") ?? [];
129
+ const prefers = listField(identityBlock, "prefers") ?? [];
130
+ const avoids = listField(identityBlock, "avoids") ?? [];
131
+ return { domain, capabilityTags, requires, prefers, avoids };
87
132
  }
88
133
  function sliceNamedBlock(src, name) {
89
134
  const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
@@ -138,8 +183,10 @@ function listField(block, key) {
138
183
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
139
184
  import { dirname } from "path";
140
185
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
141
- "claude-opus-4-7": { input: 15, output: 75 },
142
- "claude-opus-4-6": { input: 15, output: 75 },
186
+ "claude-opus-4-8": { input: 10, output: 50 },
187
+ // 3× cheaper than 4.7 on total cost; A-020 2026-06-08
188
+ "claude-opus-4-7": { input: 5, output: 25 },
189
+ "claude-opus-4-6": { input: 5, output: 25 },
143
190
  "claude-sonnet-4-6": { input: 3, output: 15 },
144
191
  "claude-haiku-4-5-20251001": { input: 1, output: 5 },
145
192
  "claude-haiku-4-5": { input: 1, output: 5 }
@@ -156,8 +203,30 @@ function defaultAnthropicPricer(model, usage) {
156
203
  function defaultLocalLlmPricer(_model, _usage) {
157
204
  return 0;
158
205
  }
206
+ var XAI_PRICING_USD_PER_MTOK = {};
207
+ function defaultXAIPricer(model, usage) {
208
+ const price = XAI_PRICING_USD_PER_MTOK[model];
209
+ if (!price) {
210
+ throw new Error(
211
+ `No xAI pricing configured for model "${model}" \u2014 populate XAI_PRICING_USD_PER_MTOK (see /research task_1778109552044_qed8 in docs/LLM_CAPABILITIES.md) or pass a custom pricer`
212
+ );
213
+ }
214
+ return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1e6;
215
+ }
216
+ var OPENROUTER_PRICING_USD_PER_MTOK = {};
217
+ function defaultOpenRouterPricer(model, usage) {
218
+ const price = OPENROUTER_PRICING_USD_PER_MTOK[model];
219
+ if (!price) {
220
+ throw new Error(
221
+ `No OpenRouter pricing configured for model "${model}" \u2014 populate OPENROUTER_PRICING_USD_PER_MTOK or pass a custom pricer`
222
+ );
223
+ }
224
+ return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1e6;
225
+ }
159
226
  function defaultPricerForProvider(provider) {
160
227
  if (provider === "local-llm" || provider === "mock") return defaultLocalLlmPricer;
228
+ if (provider === "xai") return defaultXAIPricer;
229
+ if (provider === "openrouter") return defaultOpenRouterPricer;
161
230
  return defaultAnthropicPricer;
162
231
  }
163
232
  var CostGuard = class {
@@ -219,6 +288,181 @@ function todayUtc() {
219
288
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
220
289
  }
221
290
 
291
+ // src/capability-router.ts
292
+ import {
293
+ ANTHROPIC_CAPABILITIES,
294
+ OPENAI_CAPABILITIES,
295
+ GEMINI_CAPABILITIES,
296
+ XAI_CAPABILITIES,
297
+ OPENROUTER_CAPABILITIES,
298
+ LOCAL_LLM_CAPABILITIES,
299
+ BITNET_CAPABILITIES,
300
+ MOCK_CAPABILITIES
301
+ } from "@holoscript/llm-provider";
302
+ var NoEligibleProviderError = class extends Error {
303
+ constructor(requires, avoids, considered, excludedByAvoids) {
304
+ super(
305
+ `No provider satisfies brain requires=[${requires.join(", ")}] avoids=[${avoids.join(", ")}]. Considered: [${considered.join(", ")}]. Excluded by avoids: [${excludedByAvoids.join(", ")}].`
306
+ );
307
+ this.requires = requires;
308
+ this.avoids = avoids;
309
+ this.considered = considered;
310
+ this.excludedByAvoids = excludedByAvoids;
311
+ this.name = "NoEligibleProviderError";
312
+ }
313
+ };
314
+ function satisfies(capabilities, key) {
315
+ const value = capabilities[key];
316
+ if (typeof value === "boolean") return value;
317
+ if (typeof value === "number") return value > 0;
318
+ return false;
319
+ }
320
+ function countMatches(capabilities, keys) {
321
+ let count = 0;
322
+ for (const key of keys) {
323
+ if (satisfies(capabilities, key)) count++;
324
+ }
325
+ return count;
326
+ }
327
+ function unsatisfiedKeys(capabilities, keys) {
328
+ return keys.filter((key) => !satisfies(capabilities, key));
329
+ }
330
+ function pickProvider(opts) {
331
+ const { brain, envOverride, candidates } = opts;
332
+ const tieBreaker = opts.tieBreakerOrder ?? candidates.map((c) => c.name);
333
+ if (candidates.length === 0) {
334
+ throw new Error("pickProvider: no candidates supplied");
335
+ }
336
+ const excludedByAvoids = [];
337
+ const notAvoided = [];
338
+ for (const candidate of candidates) {
339
+ const matchesAvoid = brain.avoids.some((a) => satisfies(candidate.capabilities, a));
340
+ if (matchesAvoid) {
341
+ excludedByAvoids.push(candidate.name);
342
+ } else {
343
+ notAvoided.push(candidate);
344
+ }
345
+ }
346
+ if (brain.requires.length === 0) {
347
+ if (envOverride !== void 0) {
348
+ const envCandidate = candidates.find((c) => c.name === envOverride);
349
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
350
+ return {
351
+ picked: envOverride,
352
+ reason: "env-override-no-requirements",
353
+ unsatisfiedRequires: [],
354
+ matchedPrefers,
355
+ excludedByAvoids,
356
+ alternatives: candidates.filter((c) => c.name !== envOverride).map((c) => c.name)
357
+ };
358
+ }
359
+ const ordered = orderCandidates(notAvoided, tieBreaker);
360
+ if (ordered.length === 0) {
361
+ return {
362
+ picked: candidates[0].name,
363
+ reason: "open-routing-default",
364
+ unsatisfiedRequires: [],
365
+ matchedPrefers: brain.prefers.filter((p) => satisfies(candidates[0].capabilities, p)),
366
+ excludedByAvoids,
367
+ alternatives: candidates.slice(1).map((c) => c.name)
368
+ };
369
+ }
370
+ return {
371
+ picked: ordered[0].name,
372
+ reason: "open-routing-default",
373
+ unsatisfiedRequires: [],
374
+ matchedPrefers: brain.prefers.filter((p) => satisfies(ordered[0].capabilities, p)),
375
+ excludedByAvoids,
376
+ alternatives: ordered.slice(1).map((c) => c.name)
377
+ };
378
+ }
379
+ const eligible = notAvoided.filter(
380
+ (c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0
381
+ );
382
+ if (eligible.length === 0) {
383
+ if (envOverride !== void 0) {
384
+ const envCandidate = candidates.find((c) => c.name === envOverride);
385
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
386
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
387
+ return {
388
+ picked: envOverride,
389
+ reason: "env-override-mismatch",
390
+ unsatisfiedRequires: unsatisfied,
391
+ matchedPrefers,
392
+ excludedByAvoids,
393
+ alternatives: []
394
+ };
395
+ }
396
+ throw new NoEligibleProviderError(
397
+ brain.requires,
398
+ brain.avoids,
399
+ candidates.map((c) => c.name),
400
+ excludedByAvoids
401
+ );
402
+ }
403
+ const ranked = [...eligible].sort((a, b) => {
404
+ const aMatches = countMatches(a.capabilities, brain.prefers);
405
+ const bMatches = countMatches(b.capabilities, brain.prefers);
406
+ if (aMatches !== bMatches) return bMatches - aMatches;
407
+ const aIdx = tieBreaker.indexOf(a.name);
408
+ const bIdx = tieBreaker.indexOf(b.name);
409
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
410
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
411
+ return aRank - bRank;
412
+ });
413
+ if (envOverride !== void 0) {
414
+ const envEligible = ranked.find((c) => c.name === envOverride);
415
+ if (envEligible) {
416
+ return {
417
+ picked: envOverride,
418
+ reason: "env-override-satisfies",
419
+ unsatisfiedRequires: [],
420
+ matchedPrefers: brain.prefers.filter((p) => satisfies(envEligible.capabilities, p)),
421
+ excludedByAvoids,
422
+ alternatives: ranked.filter((c) => c.name !== envOverride).map((c) => c.name)
423
+ };
424
+ }
425
+ const envCandidate = candidates.find((c) => c.name === envOverride);
426
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
427
+ return {
428
+ picked: envOverride,
429
+ reason: "env-override-mismatch",
430
+ unsatisfiedRequires: unsatisfied,
431
+ matchedPrefers: envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [],
432
+ excludedByAvoids,
433
+ alternatives: ranked.map((c) => c.name)
434
+ };
435
+ }
436
+ const top = ranked[0];
437
+ return {
438
+ picked: top.name,
439
+ reason: "capability-best-fit",
440
+ unsatisfiedRequires: [],
441
+ matchedPrefers: brain.prefers.filter((p) => satisfies(top.capabilities, p)),
442
+ excludedByAvoids,
443
+ alternatives: ranked.slice(1).map((c) => c.name)
444
+ };
445
+ }
446
+ var BUILT_IN_CANDIDATES = [
447
+ { name: "anthropic", capabilities: ANTHROPIC_CAPABILITIES },
448
+ { name: "openai", capabilities: OPENAI_CAPABILITIES },
449
+ { name: "gemini", capabilities: GEMINI_CAPABILITIES },
450
+ { name: "xai", capabilities: XAI_CAPABILITIES },
451
+ { name: "openrouter", capabilities: OPENROUTER_CAPABILITIES },
452
+ { name: "local-llm", capabilities: LOCAL_LLM_CAPABILITIES },
453
+ { name: "bitnet", capabilities: BITNET_CAPABILITIES },
454
+ { name: "mock", capabilities: MOCK_CAPABILITIES }
455
+ ];
456
+ function orderCandidates(candidates, tieBreaker) {
457
+ return [...candidates].sort((a, b) => {
458
+ const aIdx = tieBreaker.indexOf(a.name);
459
+ const bIdx = tieBreaker.indexOf(b.name);
460
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
461
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
462
+ return aRank - bRank;
463
+ });
464
+ }
465
+
222
466
  // src/holomesh-client.ts
223
467
  var HolomeshClient = class {
224
468
  constructor(opts) {
@@ -226,9 +470,14 @@ var HolomeshClient = class {
226
470
  this.bearer = opts.bearer;
227
471
  this.teamId = opts.teamId;
228
472
  this.fetchImpl = opts.fetchImpl ?? fetch;
473
+ this.signer = opts.signer;
474
+ }
475
+ /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
476
+ async signBody(body) {
477
+ return this.signer ? await this.signer(body) : body;
229
478
  }
230
479
  async heartbeat(payload) {
231
- await this.req("POST", `/team/${this.teamId}/presence`, payload);
480
+ await this.req("POST", `/team/${this.teamId}/presence`, await this.signBody(payload));
232
481
  }
233
482
  async getOpenTasks() {
234
483
  const data = await this.req(
@@ -238,28 +487,33 @@ var HolomeshClient = class {
238
487
  return data.tasks ?? data.open ?? [];
239
488
  }
240
489
  async claim(taskId) {
241
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, { action: "claim" });
490
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "claim" }));
242
491
  }
243
492
  async joinTeam() {
244
493
  return this.req(
245
494
  "POST",
246
495
  `/team/${this.teamId}/join`,
247
- {}
496
+ await this.signBody({})
248
497
  );
249
498
  }
250
499
  async sendMessageOnTask(taskId, body) {
251
- await this.req("POST", `/team/${this.teamId}/message`, {
500
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({
252
501
  to: "team",
253
502
  subject: `task:${taskId}`,
254
503
  content: body
255
- });
504
+ }));
256
505
  }
257
506
  async markDone(taskId, summary, commitHash) {
258
- await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
507
+ await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({
259
508
  action: "done",
260
509
  summary,
261
- commitHash
262
- });
510
+ // verification_evidence required by server before task can be closed.
511
+ verification_evidence: summary,
512
+ // Exclude commitHash when undefined — JSON.stringify drops undefined but
513
+ // canonicalizeSigning preserves it as the literal string "undefined",
514
+ // causing a signature-mismatch vs what the server sees after JSON.parse.
515
+ ...commitHash !== void 0 ? { commitHash } : {}
516
+ }));
263
517
  }
264
518
  // POST CAEL audit records for this agent. Server validator at
265
519
  // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
@@ -282,6 +536,40 @@ var HolomeshClient = class {
282
536
  wallet: raw.wallet
283
537
  };
284
538
  }
539
+ // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────
540
+ /** Read recent team messages. */
541
+ async getTeamMessages(limit = 20) {
542
+ const data = await this.req(
543
+ "GET",
544
+ `/team/${this.teamId}/messages?limit=${limit}`
545
+ );
546
+ return data.messages ?? [];
547
+ }
548
+ /** Post a message to the team feed. */
549
+ async sendTeamMessage(content, messageType = "text") {
550
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));
551
+ }
552
+ // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
553
+ /** Switch team mode. Requires owner or founder role. */
554
+ async setTeamMode(mode, reason) {
555
+ return this.req("POST", `/team/${this.teamId}/mode`, await this.signBody({ mode, reason }));
556
+ }
557
+ /** Update room preferences. Requires config:write permission. */
558
+ async patchRoomPrefs(prefs) {
559
+ return this.req("PATCH", `/team/${this.teamId}/room`, await this.signBody(prefs));
560
+ }
561
+ /** Update a board task. */
562
+ async updateTask(taskId, updates) {
563
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "update", ...updates }));
564
+ }
565
+ /** Delete a board task. */
566
+ async deleteTask(taskId) {
567
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delete" }));
568
+ }
569
+ /** Delegate a board task to another agent. */
570
+ async delegateTask(taskId, toAgentId) {
571
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
572
+ }
285
573
  async req(method, path, body) {
286
574
  const url = `${this.apiBase}${path}`;
287
575
  const res = await this.fetchImpl(url, {
@@ -349,7 +637,18 @@ function brainClassOf(brain) {
349
637
  return "unknown";
350
638
  }
351
639
  function buildCaelRecord(input) {
352
- const { identity, brain, task, messages, finalText, usage, costUsd, spentUsd, prevChain, runtimeVersion } = input;
640
+ const {
641
+ identity,
642
+ brain,
643
+ task,
644
+ messages,
645
+ finalText,
646
+ usage,
647
+ costUsd,
648
+ spentUsd,
649
+ prevChain,
650
+ runtimeVersion
651
+ } = input;
353
652
  const l0 = sha(brain.systemPrompt);
354
653
  const l1 = sha(`${task.id}|${task.title}|${task.description ?? ""}`);
355
654
  const l2 = sha(JSON.stringify(messages));
@@ -365,15 +664,16 @@ function buildCaelRecord(input) {
365
664
  prev_hash: prevChain,
366
665
  fnv1a_chain,
367
666
  version_vector_fingerprint: `agent@${runtimeVersion}|brain@${brainClassOf(brain)}|provider@${identity.llmProvider}|model@${identity.llmModel}`,
368
- brain_class: brainClassOf(brain)
667
+ brain_class: brainClassOf(brain),
668
+ trust_epoch: "post-w107"
369
669
  };
370
670
  }
371
671
 
372
672
  // src/tools.ts
373
673
  import { readFile as readFile2, writeFile, readdir, mkdir, stat } from "fs/promises";
374
- import { resolve, dirname as dirname2 } from "path";
674
+ import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
375
675
  import { spawn } from "child_process";
376
- var ALLOWED_READ_ROOTS = [
676
+ var FLEET_READ_ROOTS = [
377
677
  "/root/msc-paper-22",
378
678
  // Paper 22 mechanization inputs (scp'd by deploy)
379
679
  "/root/holoscript-mesh",
@@ -381,15 +681,24 @@ var ALLOWED_READ_ROOTS = [
381
681
  "/root/agent-output"
382
682
  // Read back what we wrote
383
683
  ];
384
- var ALLOWED_WRITE_ROOTS = [
684
+ var FLEET_WRITE_ROOTS = [
385
685
  "/root/agent-output"
386
686
  // Single write sink — keeps deliverables in one place
387
687
  ];
388
- var BASH_WHITELIST = [
389
- "lake build",
390
- "lake env",
391
- "lake clean",
392
- "lean ",
688
+ function parseRootsEnv(raw, fallback) {
689
+ if (!raw) return fallback;
690
+ const roots = raw.split(delimiter).map((r) => r.trim()).filter((r) => r.length > 0 && isAbsolute(r));
691
+ return roots.length > 0 ? roots : fallback;
692
+ }
693
+ var ALLOWED_READ_ROOTS = parseRootsEnv(
694
+ process.env.HOLOSCRIPT_AGENT_READ_ROOTS,
695
+ FLEET_READ_ROOTS
696
+ );
697
+ var ALLOWED_WRITE_ROOTS = parseRootsEnv(
698
+ process.env.HOLOSCRIPT_AGENT_WRITE_ROOTS,
699
+ FLEET_WRITE_ROOTS
700
+ );
701
+ var BASH_READ_ONLY_PREFIXES = [
393
702
  "ls ",
394
703
  "ls\n",
395
704
  "ls$",
@@ -404,16 +713,36 @@ var BASH_WHITELIST = [
404
713
  "git log",
405
714
  "git diff",
406
715
  "git show",
716
+ "pwd",
717
+ "echo ",
718
+ "lake env"
719
+ ];
720
+ var BASH_PRODUCTIVE_PREFIXES = [
721
+ "lake build",
722
+ "lake clean",
723
+ "lean ",
407
724
  "pnpm --filter",
408
725
  "pnpm vitest",
409
726
  "vitest run",
410
- "pwd",
411
- "echo "
727
+ // Robotics / edge-node (Jetson) productive commands — without these, every
728
+ // ros2/colcon/tegrastats task fails the W.107 artifact gate and is abandoned
729
+ // as no-artifact. (jetson-orin-01 lane.)
730
+ "ros2 launch",
731
+ "ros2 topic pub",
732
+ "ros2 service call",
733
+ "colcon build",
734
+ "tegrastats"
412
735
  ];
736
+ var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
737
+ function isProductiveBashCommand(cmd) {
738
+ const trimmed = String(cmd ?? "").trim();
739
+ if (!trimmed) return false;
740
+ return BASH_PRODUCTIVE_PREFIXES.some((prefix) => trimmed.startsWith(prefix.trim()));
741
+ }
413
742
  var MESH_TOOLS = [
414
743
  {
415
744
  name: "read_file",
416
- description: "Read a file from the agent sandbox. Allowed roots: /root/msc-paper-22, /root/holoscript-mesh, /root/agent-output. Returns the file content as text. Use this to inspect inputs scp'd to the instance (e.g. MSC/Invariants.lean).",
745
+ description: `Read a file from the agent sandbox. Allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}. Returns the file content as text. Use this to inspect task inputs and the read-only repo view.`,
417
746
  input_schema: {
418
747
  type: "object",
419
748
  properties: {
@@ -435,11 +764,11 @@ var MESH_TOOLS = [
435
764
  },
436
765
  {
437
766
  name: "write_file",
438
- description: "Write a file to /root/agent-output/. This is the deliverable sink \u2014 anything you want to emit as task output (a Lean proof, a markdown report, a JSON dataset) goes here. Creates parent directories. Will refuse paths outside the write root.",
767
+ description: `Write a file to the deliverable sink (write roots: ${ALLOWED_WRITE_ROOTS.join(", ")}). Anything you want to emit as task output (a Lean proof, a markdown report, a JSON dataset, a .holo scene) goes here. Creates parent directories. Will refuse paths outside the write root(s).`,
439
768
  input_schema: {
440
769
  type: "object",
441
770
  properties: {
442
- path: { type: "string", description: "Absolute path under /root/agent-output/" },
771
+ path: { type: "string", description: `Absolute path under a write root: ${ALLOWED_WRITE_ROOTS.join(", ")}` },
443
772
  content: { type: "string", description: "File content to write (UTF-8)" }
444
773
  },
445
774
  required: ["path", "content"]
@@ -447,7 +776,7 @@ var MESH_TOOLS = [
447
776
  },
448
777
  {
449
778
  name: "bash",
450
- description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo. Hard 60s wall timeout, 1MB stdout cap. Use for lake build / lean kernel-checks, git inspection, repo greps. Refuses rm, curl, ssh, sudo, eval.",
779
+ description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo, ros2 launch/topic/service, colcon build, tegrastats. Hard 60s wall timeout, 1MB stdout cap. Use for builds, tests, hardware probes. Refuses rm, curl, ssh, sudo, eval.",
451
780
  input_schema: {
452
781
  type: "object",
453
782
  properties: {
@@ -456,22 +785,52 @@ var MESH_TOOLS = [
456
785
  },
457
786
  required: ["cmd"]
458
787
  }
788
+ },
789
+ {
790
+ name: "emit_hardware_receipt",
791
+ description: "Emit a portable hardware receipt (PortableHardwareReceiptMetadata v1) capturing device identity, runtime, and measured performance. Writes a JSON receipt to the agent output dir. Use after running tegrastats or colcon build to record hardware evidence for the CAEL audit chain. Accepts either pre-parsed measurements or raw tegrastats output (the tool parses it automatically).",
792
+ input_schema: {
793
+ type: "object",
794
+ properties: {
795
+ device_kind: {
796
+ type: "string",
797
+ description: 'Device identifier, e.g. "jetson-orin-nano-super", "raspberry-pi-5"'
798
+ },
799
+ accelerator: {
800
+ description: 'Accelerator string, e.g. "NVIDIA CUDA 8.7", or null for CPU-only'
801
+ },
802
+ runtime_name: { type: "string", description: 'Inference runtime, e.g. "Ollama", "llama.cpp"' },
803
+ runtime_version: { type: "string", description: 'Runtime version, e.g. "0.30.8"' },
804
+ host_os: { type: "string", description: 'OS + firmware, e.g. "JetPack 6.2.1 / Ubuntu 22.04"' },
805
+ composition_id: { type: "string", description: 'Brain composition reference, e.g. "jetson-orin-brain"' },
806
+ measurements: {
807
+ type: "array",
808
+ description: "Pre-parsed measurements. Each item: {metric: string, value: number, unit: string}",
809
+ items: { type: "object" }
810
+ },
811
+ tegrastats_output: {
812
+ type: "string",
813
+ description: "Raw tegrastats output line(s) \u2014 tool auto-parses GPU%, RAM, temp, power"
814
+ }
815
+ },
816
+ required: ["device_kind", "runtime_name", "runtime_version", "host_os"]
817
+ }
459
818
  }
460
819
  ];
461
820
  function isUnderRoot(absPath, root) {
462
821
  const resolved = resolve(absPath);
463
822
  const rootResolved = resolve(root);
464
- return resolved === rootResolved || resolved.startsWith(rootResolved + "/");
823
+ return resolved === rootResolved || resolved.startsWith(rootResolved + sep);
465
824
  }
466
825
  function checkReadAllowed(path) {
467
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
826
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
468
827
  for (const root of ALLOWED_READ_ROOTS) {
469
828
  if (isUnderRoot(path, root)) return null;
470
829
  }
471
830
  return `read denied \u2014 path "${path}" not under allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}`;
472
831
  }
473
832
  function checkWriteAllowed(path) {
474
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
833
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
475
834
  for (const root of ALLOWED_WRITE_ROOTS) {
476
835
  if (isUnderRoot(path, root)) return null;
477
836
  }
@@ -526,12 +885,113 @@ async function runTool(use) {
526
885
  return result.code === 0 ? okResult(use.id, result.stdout) : errResult(use.id, `exit=${result.code}
527
886
  ${result.stderr || result.stdout}`);
528
887
  }
888
+ if (use.name === "emit_hardware_receipt") {
889
+ const deviceKind = String(use.input.device_kind ?? "unknown-device");
890
+ const accelerator = use.input.accelerator === null || use.input.accelerator === "null" ? null : String(use.input.accelerator ?? "").trim() || null;
891
+ const runtimeName = String(use.input.runtime_name ?? "Ollama");
892
+ const runtimeVersion = String(use.input.runtime_version ?? "unknown");
893
+ const hostOs = String(use.input.host_os ?? "unknown");
894
+ const compositionId = String(use.input.composition_id ?? "unknown");
895
+ let measurements = [];
896
+ if (Array.isArray(use.input.measurements)) {
897
+ for (const m of use.input.measurements) {
898
+ const metric = String(m.metric ?? "");
899
+ const value = Number(m.value ?? 0);
900
+ const unit = String(m.unit ?? "");
901
+ if (metric && Number.isFinite(value)) {
902
+ measurements.push({ metric, value, unit, method: "measured" });
903
+ }
904
+ }
905
+ }
906
+ if (typeof use.input.tegrastats_output === "string" && use.input.tegrastats_output.length > 0) {
907
+ measurements = [...measurements, ...parseTegrastats(use.input.tegrastats_output)];
908
+ }
909
+ if (measurements.length === 0) {
910
+ measurements.push({ metric: "agent-tick", value: 1, unit: "count", method: "presence" });
911
+ }
912
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
913
+ const receipt = {
914
+ schemaVersion: "holoscript.hardware-receipt-metadata.v1",
915
+ target: {
916
+ id: `${deviceKind}-${Date.now()}`,
917
+ kind: deviceKind,
918
+ architecture: /jetson|orin|nano|agx|xavier/i.test(deviceKind) ? "arm64" : "unknown",
919
+ artifactKind: "measurement-trace"
920
+ },
921
+ device: {
922
+ vendor: /jetson|orin|nvidia/i.test(deviceKind) ? "nvidia" : "unknown",
923
+ model: deviceKind,
924
+ accelerator
925
+ },
926
+ runtime: { name: runtimeName, version: runtimeVersion, hostOS: hostOs },
927
+ compilerVersion: "holoscript-agent-1.0.0",
928
+ constraints: [],
929
+ measuredResults: measurements,
930
+ replayInputs: [
931
+ { kind: "composition-ref", uri: `compositions/${compositionId}`, sha256: "unknown" }
932
+ ],
933
+ provenance: {
934
+ capturedAt,
935
+ sourceCompositionHash: compositionId
936
+ },
937
+ owner: {
938
+ agent: process.env.HOLOSCRIPT_AGENT_HANDLE ?? "unknown",
939
+ ...process.env.HOLOMESH_TEAM_ID ? { team: process.env.HOLOMESH_TEAM_ID } : {}
940
+ }
941
+ };
942
+ const ts = capturedAt.replace(/[:.]/g, "-");
943
+ const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
944
+ const denied = checkWriteAllowed(outPath);
945
+ if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
946
+ await mkdir(dirname2(outPath), { recursive: true });
947
+ await writeFile(outPath, JSON.stringify(receipt, null, 2), "utf8");
948
+ return okResult(
949
+ use.id,
950
+ `Hardware receipt written to ${outPath} \u2014 ${measurements.length} measurements, accelerator=${accelerator ?? "none"}`
951
+ );
952
+ }
529
953
  return errResult(use.id, `unknown tool: ${use.name}`);
530
954
  } catch (err) {
531
955
  return errResult(use.id, err instanceof Error ? err.message : String(err));
532
956
  }
533
957
  }
958
+ function parseTegrastats(raw) {
959
+ const results = [];
960
+ const m = (pattern, metric, unit, transform) => {
961
+ const match = raw.match(pattern);
962
+ if (match?.[1]) {
963
+ const value = transform ? transform(match[1]) : Number(match[1]);
964
+ if (Number.isFinite(value)) results.push({ metric, value, unit, method: "tegrastats" });
965
+ }
966
+ };
967
+ const ram = raw.match(/RAM\s+(\d+)\/(\d+)MB/);
968
+ if (ram) {
969
+ const used = Number(ram[1]);
970
+ const total = Number(ram[2]);
971
+ results.push({ metric: "ram-used", value: used, unit: "MB", method: "tegrastats" });
972
+ results.push({ metric: "ram-total", value: total, unit: "MB", method: "tegrastats" });
973
+ if (total > 0)
974
+ results.push({ metric: "ram-pct", value: Math.round(used / total * 100), unit: "%", method: "tegrastats" });
975
+ }
976
+ m(/GR3D_FREQ\s+(\d+)%/, "gpu-util", "%");
977
+ m(/EMC_FREQ\s+(\d+)%/, "emc-freq-pct", "%");
978
+ m(/tj@([\d.]+)C/, "temp-tj", "C", parseFloat);
979
+ m(/cpu@([\d.]+)C/, "temp-cpu", "C", parseFloat);
980
+ m(/gpu@([\d.]+)C/, "temp-gpu", "C", parseFloat);
981
+ m(/VDD_SOC\s+(\d+)mW/, "power-soc", "mW");
982
+ m(/VDD_CPU_CV\s+(\d+)mW/, "power-cpu-cv", "mW");
983
+ m(/VDD_IN\s+(\d+)mW/, "power-total", "mW");
984
+ m(/CPU\s+\[(\d+)%/, "cpu-util-core0", "%");
985
+ return results;
986
+ }
534
987
  function runBash(cmd, cwd) {
988
+ if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
989
+ return Promise.resolve({
990
+ code: 0,
991
+ stdout: `[mock-bash under vitest] cmd="${cmd}" cwd="${cwd}"`,
992
+ stderr: ""
993
+ });
994
+ }
535
995
  return new Promise((resolveProm) => {
536
996
  const child = spawn("bash", ["-c", cmd], { cwd, env: process.env });
537
997
  let stdout = "";
@@ -600,6 +1060,35 @@ var AgentRunner = class {
600
1060
  const { identity, brain, mesh, costGuard, provider, logger } = this.opts;
601
1061
  const log = logger ?? (() => void 0);
602
1062
  await this.heartbeatWithAutoRejoin();
1063
+ if (this.opts.messageHandler) {
1064
+ try {
1065
+ const receipts = await this.opts.messageHandler.processMessages();
1066
+ if (receipts.length > 0) {
1067
+ log({
1068
+ ev: "messages-processed",
1069
+ count: receipts.length,
1070
+ statuses: receipts.map((r) => r.status)
1071
+ });
1072
+ if (brain.capabilityTags.length === 0 || brain.capabilityTags.every((t) => t.startsWith("delegated"))) {
1073
+ return {
1074
+ action: "messages-processed",
1075
+ spentUsd: costGuard.getState().spentUsd,
1076
+ remainingUsd: costGuard.getRemainingUsd(),
1077
+ receipts: receipts.map((r) => ({
1078
+ status: r.status,
1079
+ action: r.action,
1080
+ reason: r.reason
1081
+ }))
1082
+ };
1083
+ }
1084
+ }
1085
+ } catch (err) {
1086
+ log({
1087
+ ev: "message-handler-error",
1088
+ message: err instanceof Error ? err.message : String(err)
1089
+ });
1090
+ }
1091
+ }
603
1092
  if (costGuard.isOverBudget()) {
604
1093
  const state = costGuard.getState();
605
1094
  log({ ev: "over-budget", spentUsd: state.spentUsd, budget: identity.budgetUsdPerDay });
@@ -633,6 +1122,8 @@ var AgentRunner = class {
633
1122
  const MAX_TOOL_ITERS = 30;
634
1123
  let lastResponse;
635
1124
  const toolsCalled = /* @__PURE__ */ new Set();
1125
+ let productiveCallCount = 0;
1126
+ let lastCommitHash;
636
1127
  while (true) {
637
1128
  iters++;
638
1129
  if (iters > MAX_TOOL_ITERS) {
@@ -640,12 +1131,16 @@ var AgentRunner = class {
640
1131
  finalText = finalText || `[tool-loop hit ${MAX_TOOL_ITERS}-iter cap before final text]`;
641
1132
  break;
642
1133
  }
1134
+ const activeTools = brain.requires.includes("local-llm") ? MESH_TOOLS.filter((t) => t.name === "write_file") : MESH_TOOLS;
643
1135
  const resp = await provider.complete(
644
1136
  {
645
1137
  messages,
646
- maxTokens: 4096,
1138
+ // 8192 for local thinking models (qwen3:4b uses ~3800 tokens on thinking
1139
+ // before the tool-call JSON; 4096 cuts off mid-generation). Frontier
1140
+ // models ignore this ceiling and stop naturally earlier.
1141
+ maxTokens: 8192,
647
1142
  temperature: 0.4,
648
- tools: MESH_TOOLS
1143
+ tools: activeTools
649
1144
  },
650
1145
  identity.llmModel
651
1146
  );
@@ -656,13 +1151,39 @@ var AgentRunner = class {
656
1151
  totalTokens: aggUsage.totalTokens + resp.usage.totalTokens
657
1152
  };
658
1153
  if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
659
- log({ ev: "tool-call", taskId: target.id, iter: iters, tools: resp.toolUses.map((t) => t.name) });
660
- for (const u of resp.toolUses) toolsCalled.add(u.name);
1154
+ log({
1155
+ ev: "tool-call",
1156
+ taskId: target.id,
1157
+ iter: iters,
1158
+ tools: resp.toolUses.map((t) => t.name)
1159
+ });
1160
+ for (const u of resp.toolUses) {
1161
+ toolsCalled.add(u.name);
1162
+ if (u.name === "write_file") {
1163
+ const content = String(u.input?.content ?? "");
1164
+ if (content.length > 0) productiveCallCount++;
1165
+ } else if (u.name === "bash") {
1166
+ const cmd = String(u.input?.cmd ?? "");
1167
+ if (isProductiveBashCommand(cmd)) productiveCallCount++;
1168
+ } else if (u.name === "emit_hardware_receipt") {
1169
+ productiveCallCount++;
1170
+ }
1171
+ }
661
1172
  messages.push({
662
1173
  role: "assistant",
663
1174
  content: resp.assistantBlocks ?? []
664
1175
  });
665
1176
  const toolResults = await Promise.all(resp.toolUses.map((u) => runTool(u)));
1177
+ for (let ti = 0; ti < resp.toolUses.length; ti++) {
1178
+ const tu = resp.toolUses[ti];
1179
+ if (tu.name === "bash") {
1180
+ const tr = toolResults[ti];
1181
+ if (tr && !tr.is_error) {
1182
+ const shaMatch = tr.content.match(/\b([0-9a-f]{7,40})\b/);
1183
+ if (shaMatch) lastCommitHash = shaMatch[1];
1184
+ }
1185
+ }
1186
+ }
666
1187
  messages.push({
667
1188
  role: "user",
668
1189
  content: toolResults
@@ -673,24 +1194,75 @@ var AgentRunner = class {
673
1194
  break;
674
1195
  }
675
1196
  const durationMs = Date.now() - start;
676
- const SIDE_EFFECTING_TOOLS = /* @__PURE__ */ new Set(["write_file", "bash"]);
677
- const sideEffectingCalled = [...toolsCalled].some((t) => SIDE_EFFECTING_TOOLS.has(t));
678
- if (!sideEffectingCalled) {
1197
+ if (productiveCallCount === 0) {
679
1198
  log({
680
1199
  ev: "no-artifact",
681
1200
  taskId: target.id,
682
1201
  tool_iters: iters,
683
1202
  toolsCalled: [...toolsCalled],
684
- message: "task execution called no side-effecting tool (write_file/bash) \u2014 refusing to mark executed. Likely a pure-text or read-only-inspection response. Task remains open for a grounded attempt."
1203
+ productiveCallCount,
1204
+ message: "task execution did not produce a real artifact \u2014 refusing to mark executed. Required: write_file with non-empty content OR bash with a productive prefix (lake build / pnpm --filter / vitest run / lean / pnpm vitest). Pure-text, read-only inspection, and trivial-bash-bypass (`echo`, `cat`, etc.) do not satisfy the gate."
685
1205
  });
686
1206
  return {
687
1207
  action: "no-artifact",
688
1208
  taskId: target.id,
689
1209
  spentUsd: costGuard.getState().spentUsd,
690
1210
  remainingUsd: costGuard.getRemainingUsd(),
691
- message: `no side-effecting tool called (toolsCalled=[${[...toolsCalled].join(",")}], iters=${iters})`
1211
+ message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
692
1212
  };
693
1213
  }
1214
+ let reflectVerdict;
1215
+ if (brain.reflect) {
1216
+ try {
1217
+ const reflectResp = await provider.complete(
1218
+ {
1219
+ messages: [
1220
+ {
1221
+ role: "system",
1222
+ content: "You are a strict reviewer. Evaluate the work against the criteria; do not rewrite it."
1223
+ },
1224
+ {
1225
+ role: "user",
1226
+ content: `Reflect on the artifact produced for this task. Evaluate it for: ${brain.reflect.criteria}.
1227
+
1228
+ --- artifact / final response ---
1229
+ ${finalText.slice(0, 4e3)}
1230
+ --- end ---
1231
+
1232
+ Give a one-line reason, then end with exactly "VERDICT: PASS" or "VERDICT: FAIL".`
1233
+ }
1234
+ ],
1235
+ maxTokens: 512,
1236
+ temperature: 0.1
1237
+ },
1238
+ identity.llmModel
1239
+ );
1240
+ aggUsage = {
1241
+ promptTokens: aggUsage.promptTokens + reflectResp.usage.promptTokens,
1242
+ completionTokens: aggUsage.completionTokens + reflectResp.usage.completionTokens,
1243
+ totalTokens: aggUsage.totalTokens + reflectResp.usage.totalTokens
1244
+ };
1245
+ const verdictMatch = /VERDICT:\s*(PASS|FAIL)/i.exec(reflectResp.content);
1246
+ const pass = verdictMatch ? verdictMatch[1].toUpperCase() === "PASS" : true;
1247
+ reflectVerdict = {
1248
+ pass,
1249
+ reason: reflectResp.content.replace(/VERDICT:\s*(PASS|FAIL)/i, "").trim().slice(0, 300)
1250
+ };
1251
+ log({
1252
+ ev: "reflect",
1253
+ taskId: target.id,
1254
+ pass,
1255
+ escalateOnFail: brain.reflect.escalateOnFail,
1256
+ reason: reflectVerdict.reason.slice(0, 120)
1257
+ });
1258
+ } catch (err) {
1259
+ log({
1260
+ ev: "reflect-error",
1261
+ taskId: target.id,
1262
+ message: err instanceof Error ? err.message : String(err)
1263
+ });
1264
+ }
1265
+ }
694
1266
  const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
695
1267
  log({
696
1268
  ev: "executed",
@@ -700,7 +1272,11 @@ var AgentRunner = class {
700
1272
  tokens: aggUsage.totalTokens,
701
1273
  tool_iters: iters
702
1274
  });
703
- const response = { ...lastResponse ?? { content: finalText, usage: aggUsage }, content: finalText, usage: aggUsage };
1275
+ const response = {
1276
+ ...lastResponse ?? { content: finalText, usage: aggUsage },
1277
+ content: finalText,
1278
+ usage: aggUsage
1279
+ };
704
1280
  const execResult = {
705
1281
  taskId: target.id,
706
1282
  responseText: response.content,
@@ -734,10 +1310,32 @@ var AgentRunner = class {
734
1310
  });
735
1311
  const posted = await mesh.postAuditRecords(identity.handle, [caelRecord]);
736
1312
  this.prevCaelChain = caelRecord.fnv1a_chain;
737
- log({ ev: "cael-posted", taskId: target.id, appended: posted.appended, rejected: posted.rejected });
1313
+ log({
1314
+ ev: "cael-posted",
1315
+ taskId: target.id,
1316
+ appended: posted.appended,
1317
+ rejected: posted.rejected
1318
+ });
738
1319
  } catch (err) {
739
1320
  log({ ev: "cael-post-error", message: err instanceof Error ? err.message : String(err) });
740
1321
  }
1322
+ if (reflectVerdict && !reflectVerdict.pass && brain.reflect?.escalateOnFail) {
1323
+ try {
1324
+ await mesh.sendMessageOnTask(
1325
+ target.id,
1326
+ `[${identity.handle}] reflect gate FAILED \u2014 escalating to the fleet instead of marking done. Reason: ${reflectVerdict.reason}`
1327
+ );
1328
+ } catch {
1329
+ }
1330
+ log({ ev: "reflect-escalate", taskId: target.id, reason: reflectVerdict.reason.slice(0, 120) });
1331
+ return {
1332
+ action: "reflect-escalate",
1333
+ taskId: target.id,
1334
+ spentUsd: costGuard.getState().spentUsd,
1335
+ remainingUsd: costGuard.getRemainingUsd(),
1336
+ message: `reflect self-evaluation failed; escalated to fleet (reason: ${reflectVerdict.reason.slice(0, 120)})`
1337
+ };
1338
+ }
741
1339
  if (this.opts.onTaskExecuted) {
742
1340
  await this.opts.onTaskExecuted(execResult, target);
743
1341
  } else {
@@ -748,6 +1346,16 @@ var AgentRunner = class {
748
1346
  ${response.content}`
749
1347
  );
750
1348
  }
1349
+ try {
1350
+ await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
1351
+ log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
1352
+ } catch (err) {
1353
+ log({
1354
+ ev: "mark-done-error",
1355
+ taskId: target.id,
1356
+ message: err instanceof Error ? err.message : String(err)
1357
+ });
1358
+ }
751
1359
  return {
752
1360
  action: "executed",
753
1361
  taskId: target.id,
@@ -840,7 +1448,7 @@ function buildTaskPrompt(task) {
840
1448
  "Description:",
841
1449
  task.description ?? "(no description)",
842
1450
  "",
843
- "Produce the deliverable described in the task. Apply your brain composition rules \u2014 anti-patterns, decision loop, and scope tier all bind. Return the response as plain text suitable for posting to /room as a message on this task."
1451
+ "Produce the deliverable: call write_file (or bash with a build command) to create all required output files FIRST. Apply your brain composition rules \u2014 anti-patterns, decision loop, and scope tier all bind. After calling the tool(s), return a short plain-text summary of what you did for posting to /room."
844
1452
  ].join("\n");
845
1453
  }
846
1454
  function sleep(ms) {
@@ -877,7 +1485,9 @@ function makeCommitHook(opts) {
877
1485
  const relPath = relativeTo(cwd, filePath);
878
1486
  const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
879
1487
  if (addRes.status !== 0) {
880
- throw new Error(`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`);
1488
+ throw new Error(
1489
+ `git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`
1490
+ );
881
1491
  }
882
1492
  const message = renderCommitMessage({ scope, task, identity, result });
883
1493
  const commitArgs = ["commit", "-m", message];
@@ -886,7 +1496,9 @@ function makeCommitHook(opts) {
886
1496
  }
887
1497
  const commitRes = spawn2("git", commitArgs, { cwd, encoding: "utf8" });
888
1498
  if (commitRes.status !== 0) {
889
- throw new Error(`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`);
1499
+ throw new Error(
1500
+ `git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`
1501
+ );
890
1502
  }
891
1503
  const hashRes = spawn2("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
892
1504
  const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
@@ -1109,7 +1721,10 @@ USR:${user}`).digest("hex").slice(0, 16);
1109
1721
  }
1110
1722
  function withTimeout(p, ms, label) {
1111
1723
  return new Promise((resolve4, reject) => {
1112
- const timer = setTimeout(() => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)), ms);
1724
+ const timer = setTimeout(
1725
+ () => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)),
1726
+ ms
1727
+ );
1113
1728
  p.then(
1114
1729
  (v) => {
1115
1730
  clearTimeout(timer);
@@ -1295,13 +1910,29 @@ var Supervisor = class {
1295
1910
  return { ...managed.status };
1296
1911
  }
1297
1912
  async bootAgent(spec) {
1298
- const identity = this.identityFromSpec(spec);
1299
1913
  const brain = await loadBrain(spec.brainPath, spec.scopeTier ?? "warm");
1300
- const provider = await this.opts.providerFactory(spec, identity);
1914
+ const decision = pickProvider({
1915
+ brain,
1916
+ envOverride: spec.provider,
1917
+ candidates: BUILT_IN_CANDIDATES
1918
+ });
1919
+ const effectiveSpec = decision.picked === spec.provider ? spec : { ...spec, provider: decision.picked };
1920
+ const identity = this.identityFromSpec(effectiveSpec);
1921
+ if (decision.reason === "env-override-mismatch" && this.opts.logger) {
1922
+ this.opts.logger({
1923
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1924
+ ev: "capability-router-mismatch",
1925
+ handle: spec.handle,
1926
+ envOverride: spec.provider,
1927
+ unsatisfiedRequires: decision.unsatisfiedRequires,
1928
+ excludedByAvoids: decision.excludedByAvoids
1929
+ });
1930
+ }
1931
+ const provider = await this.opts.providerFactory(effectiveSpec, identity);
1301
1932
  const stateDir = this.opts.stateDir ?? join2(homedir(), ".holoscript-agent", "cost-state");
1302
- const isFree = spec.provider === "mock" || spec.provider === "local-llm" || spec.provider === "bitnet";
1933
+ const isFree = effectiveSpec.provider === "mock" || effectiveSpec.provider === "local-llm" || effectiveSpec.provider === "bitnet";
1303
1934
  const costGuard = new CostGuard({
1304
- statePath: join2(stateDir, `${spec.handle}.json`),
1935
+ statePath: join2(stateDir, `${effectiveSpec.handle}.json`),
1305
1936
  dailyBudgetUsd: identity.budgetUsdPerDay,
1306
1937
  pricer: isFree ? () => 0 : void 0
1307
1938
  });
@@ -1311,7 +1942,7 @@ var Supervisor = class {
1311
1942
  teamId: identity.teamId,
1312
1943
  fetchImpl: this.opts.fetchImpl
1313
1944
  });
1314
- const onTaskExecuted = spec.enableCommitHook ? this.buildCommitHook(spec, identity, mesh) : void 0;
1945
+ const onTaskExecuted = effectiveSpec.enableCommitHook ? this.buildCommitHook(effectiveSpec, identity, mesh) : void 0;
1315
1946
  const runner = new AgentRunner({
1316
1947
  identity,
1317
1948
  brain,
@@ -1320,16 +1951,16 @@ var Supervisor = class {
1320
1951
  mesh,
1321
1952
  onTaskExecuted,
1322
1953
  auditLog: this.auditLog,
1323
- logger: (ev) => this.log({ agent: spec.handle, ...ev })
1954
+ logger: (ev) => this.log({ agent: effectiveSpec.handle, ...ev })
1324
1955
  });
1325
1956
  const status = {
1326
- handle: spec.handle,
1957
+ handle: effectiveSpec.handle,
1327
1958
  state: "starting",
1328
1959
  spentUsd: 0,
1329
1960
  remainingUsd: identity.budgetUsdPerDay,
1330
1961
  restarts: 0
1331
1962
  };
1332
- return { spec, identity, brain, runner, costGuard, status };
1963
+ return { spec: effectiveSpec, identity, brain, runner, costGuard, status };
1333
1964
  }
1334
1965
  buildCommitHook(spec, identity, mesh) {
1335
1966
  const writer = makeCommitHook({
@@ -1351,7 +1982,9 @@ var Supervisor = class {
1351
1982
  }
1352
1983
  const wallet = process.env[spec.walletEnvKey];
1353
1984
  if (!wallet || !/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
1354
- throw new Error(`Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`);
1985
+ throw new Error(
1986
+ `Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`
1987
+ );
1355
1988
  }
1356
1989
  return {
1357
1990
  handle: spec.handle,
@@ -1427,6 +2060,8 @@ var VALID_PROVIDERS2 = /* @__PURE__ */ new Set([
1427
2060
  "anthropic",
1428
2061
  "openai",
1429
2062
  "gemini",
2063
+ "xai",
2064
+ "openrouter",
1430
2065
  "mock",
1431
2066
  "bitnet",
1432
2067
  "local-llm"
@@ -1440,16 +2075,21 @@ function parseSupervisorConfig(raw) {
1440
2075
  const data = JSON.parse(raw);
1441
2076
  if (!isObject(data)) throw new Error("Supervisor config must be a JSON object");
1442
2077
  if (!Array.isArray(data.agents)) throw new Error("Supervisor config.agents must be an array");
1443
- if (data.agents.length === 0) throw new Error("Supervisor config.agents must have at least one entry");
2078
+ if (data.agents.length === 0)
2079
+ throw new Error("Supervisor config.agents must have at least one entry");
1444
2080
  const seenHandles = /* @__PURE__ */ new Set();
1445
- const agents = data.agents.map((entry, idx) => validateAgent(entry, idx, seenHandles));
2081
+ const agents = data.agents.map(
2082
+ (entry, idx) => validateAgent(entry, idx, seenHandles)
2083
+ );
1446
2084
  const globalBudgetUsdPerDay = optionalNumber(data, "globalBudgetUsdPerDay");
1447
2085
  const defaultTickIntervalMs = optionalNumber(data, "defaultTickIntervalMs");
1448
2086
  if (globalBudgetUsdPerDay != null && globalBudgetUsdPerDay <= 0) {
1449
2087
  throw new Error(`globalBudgetUsdPerDay must be positive, got ${globalBudgetUsdPerDay}`);
1450
2088
  }
1451
2089
  if (defaultTickIntervalMs != null && defaultTickIntervalMs < 5e3) {
1452
- throw new Error(`defaultTickIntervalMs must be >= 5000ms (mesh-friendly), got ${defaultTickIntervalMs}`);
2090
+ throw new Error(
2091
+ `defaultTickIntervalMs must be >= 5000ms (mesh-friendly), got ${defaultTickIntervalMs}`
2092
+ );
1453
2093
  }
1454
2094
  return { agents, globalBudgetUsdPerDay, defaultTickIntervalMs };
1455
2095
  }
@@ -1472,8 +2112,10 @@ function validateAgent(entry, idx, seen) {
1472
2112
  throw new Error(`agents[${idx}].scopeTier "${scopeTier}" must be cold | warm | hot`);
1473
2113
  }
1474
2114
  const budgetUsdPerDay = optionalNumber(entry, "budgetUsdPerDay");
1475
- if (budgetUsdPerDay != null && budgetUsdPerDay <= 0) {
1476
- throw new Error(`agents[${idx}].budgetUsdPerDay must be positive, got ${budgetUsdPerDay}`);
2115
+ if (budgetUsdPerDay != null && budgetUsdPerDay < 0) {
2116
+ throw new Error(
2117
+ `agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`
2118
+ );
1477
2119
  }
1478
2120
  const tickIntervalMs = optionalNumber(entry, "tickIntervalMs");
1479
2121
  if (tickIntervalMs != null && tickIntervalMs < 5e3) {
@@ -1542,9 +2184,14 @@ async function provisionAgent(req, opts = { execute: false }) {
1542
2184
  throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN2}`);
1543
2185
  }
1544
2186
  if (!req.founderBearer || req.founderBearer.trim().length === 0) {
1545
- throw new Error("founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)");
2187
+ throw new Error(
2188
+ "founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)"
2189
+ );
1546
2190
  }
1547
- const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(/\/$/, "");
2191
+ const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(
2192
+ /\/$/,
2193
+ ""
2194
+ );
1548
2195
  const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
1549
2196
  const surface = req.handle;
1550
2197
  const seatId = makeSeatId(surface);
@@ -1559,10 +2206,7 @@ async function provisionAgent(req, opts = { execute: false }) {
1559
2206
  seatId,
1560
2207
  seatDir,
1561
2208
  willGenerateWallet: !existsSync3(walletPath),
1562
- willCallEndpoints: [
1563
- `POST ${meshApiBase}/register/challenge`,
1564
- `POST ${meshApiBase}/register`
1565
- ]
2209
+ willCallEndpoints: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`]
1566
2210
  };
1567
2211
  }
1568
2212
  if (existsSync3(walletPath) && !opts.force) {
@@ -1605,30 +2249,40 @@ async function provisionAgent(req, opts = { execute: false }) {
1605
2249
  if (!challenge.nonce) {
1606
2250
  throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
1607
2251
  }
1608
- const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, { nonce: challenge.nonce });
1609
- const registration = await postJson(
1610
- fetchImpl,
1611
- `${meshApiBase}/register`,
1612
- req.founderBearer,
1613
- {
1614
- name: req.handle,
1615
- wallet_address: wallet.address,
1616
- nonce: challenge.nonce,
1617
- signature
1618
- }
1619
- );
2252
+ const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
2253
+ nonce: challenge.nonce
2254
+ });
2255
+ const registration = await postJson(fetchImpl, `${meshApiBase}/register`, req.founderBearer, {
2256
+ name: req.handle,
2257
+ wallet_address: wallet.address,
2258
+ nonce: challenge.nonce,
2259
+ signature
2260
+ });
1620
2261
  writeFileSync3(
1621
2262
  regPath,
1622
- JSON.stringify({ status: 201, response: registration, registered_at: (/* @__PURE__ */ new Date()).toISOString(), flow: "x402" }, null, 2),
2263
+ JSON.stringify(
2264
+ {
2265
+ status: 201,
2266
+ response: registration,
2267
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2268
+ flow: "x402"
2269
+ },
2270
+ null,
2271
+ 2
2272
+ ),
1623
2273
  "utf8"
1624
2274
  );
1625
2275
  const agentId = registration.agent?.id;
1626
2276
  const bearer = registration.agent?.api_key;
1627
2277
  if (!agentId || !bearer) {
1628
- throw new Error(`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`);
2278
+ throw new Error(
2279
+ `/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`
2280
+ );
1629
2281
  }
1630
2282
  if (registration.wallet?.private_key) {
1631
- console.warn("[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key).");
2283
+ console.warn(
2284
+ "[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key)."
2285
+ );
1632
2286
  }
1633
2287
  let joinedTeam;
1634
2288
  if (req.autoJoinTeamId) {
@@ -1688,7 +2342,12 @@ function encryptPrivateKey(privKey, masterKey) {
1688
2342
  const iv = randomBytes(12);
1689
2343
  const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
1690
2344
  const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
1691
- return { iv: iv.toString("base64"), ct: ct.toString("base64"), tag: cipher.getAuthTag().toString("base64"), alg: "aes-256-gcm" };
2345
+ return {
2346
+ iv: iv.toString("base64"),
2347
+ ct: ct.toString("base64"),
2348
+ tag: cipher.getAuthTag().toString("base64"),
2349
+ alg: "aes-256-gcm"
2350
+ };
1692
2351
  }
1693
2352
  async function postJson(fetchImpl, url, bearer, body) {
1694
2353
  const res = await fetchImpl(url, {
@@ -1763,16 +2422,34 @@ async function main() {
1763
2422
  async function cmdRun(opts) {
1764
2423
  const identity = loadIdentity();
1765
2424
  const brain = await loadBrain(identity.brainPath, scopeTierFromEnv());
1766
- const provider = await buildProvider(identity);
2425
+ const decision = pickProvider({
2426
+ brain,
2427
+ envOverride: identity.llmProvider,
2428
+ candidates: BUILT_IN_CANDIDATES
2429
+ });
2430
+ const effectiveIdentity = decision.picked === identity.llmProvider ? identity : { ...identity, llmProvider: decision.picked };
2431
+ if (decision.reason === "env-override-mismatch") {
2432
+ console.log(
2433
+ JSON.stringify({
2434
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2435
+ ev: "capability-router-mismatch",
2436
+ envOverride: identity.llmProvider,
2437
+ unsatisfiedRequires: decision.unsatisfiedRequires,
2438
+ excludedByAvoids: decision.excludedByAvoids
2439
+ })
2440
+ );
2441
+ }
2442
+ const provider = await buildProvider(effectiveIdentity);
1767
2443
  const costGuard = new CostGuard({
1768
2444
  statePath: stateFilePath(identity),
1769
2445
  dailyBudgetUsd: identity.budgetUsdPerDay,
1770
- pricer: defaultPricerForProvider(identity.llmProvider)
2446
+ pricer: defaultPricerForProvider(effectiveIdentity.llmProvider)
1771
2447
  });
1772
2448
  const mesh = new HolomeshClient({
1773
2449
  apiBase: identity.meshApiBase,
1774
2450
  bearer: identity.x402Bearer,
1775
- teamId: identity.teamId
2451
+ teamId: identity.teamId,
2452
+ signer: buildRequestSigner(identity.handle)
1776
2453
  });
1777
2454
  const commitHook = buildCommitHook(identity, mesh);
1778
2455
  const auditLog = buildAuditLog();
@@ -1786,7 +2463,14 @@ async function cmdRun(opts) {
1786
2463
  onTaskExecuted: commitHook,
1787
2464
  auditLog
1788
2465
  });
1789
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "boot", identity: identityForLog(identity), brain: { domain: brain.domain, tags: brain.capabilityTags, tier: brain.scopeTier } }));
2466
+ console.log(
2467
+ JSON.stringify({
2468
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2469
+ ev: "boot",
2470
+ identity: identityForLog(identity),
2471
+ brain: { domain: brain.domain, tags: brain.capabilityTags, tier: brain.scopeTier }
2472
+ })
2473
+ );
1790
2474
  if (opts.once) {
1791
2475
  const result = await runner.tick();
1792
2476
  console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "tick-result", ...result }));
@@ -1811,15 +2495,26 @@ function supervisorProviderFactory() {
1811
2495
  return createOpenAIProvider({ defaultModel: spec.model });
1812
2496
  case "gemini":
1813
2497
  return createGeminiProvider({ defaultModel: spec.model });
2498
+ case "xai":
2499
+ return createXAIProvider({ defaultModel: spec.model });
2500
+ case "openrouter":
2501
+ return createOpenRouterProvider({ defaultModel: spec.model });
1814
2502
  case "local-llm":
1815
2503
  return createLocalLLMProvider({
1816
2504
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
1817
- model: spec.model
2505
+ model: spec.model,
2506
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
1818
2507
  });
2508
+ case "sovereign":
2509
+ return resolveSovereignProviderAsync(spec.model ? { model: spec.model } : {}).then(
2510
+ (r) => r.provider
2511
+ );
1819
2512
  case "mock":
1820
2513
  return createMockProvider();
1821
2514
  default:
1822
- throw new Error(`Provider "${spec.provider}" not yet wired in supervisor \u2014 Phase 2.5 deliverable.`);
2515
+ throw new Error(
2516
+ `Provider "${spec.provider}" not yet wired in supervisor \u2014 add a case here.`
2517
+ );
1823
2518
  }
1824
2519
  };
1825
2520
  }
@@ -1846,11 +2541,15 @@ async function cmdSupervise(rest) {
1846
2541
  process.on("SIGINT", onSig);
1847
2542
  process.on("SIGTERM", onSig);
1848
2543
  await sup.start();
1849
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervise-running", config: cfgPath }));
2544
+ console.log(
2545
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervise-running", config: cfgPath })
2546
+ );
1850
2547
  const reportEvery = Number(process.env.HOLOSCRIPT_AGENT_STATUS_REPORT_MS ?? "300000");
1851
2548
  if (reportEvery > 0) {
1852
2549
  setInterval(() => {
1853
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervisor-status", ...sup.status() }));
2550
+ console.log(
2551
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervisor-status", ...sup.status() })
2552
+ );
1854
2553
  }, reportEvery);
1855
2554
  }
1856
2555
  }
@@ -1872,7 +2571,9 @@ async function cmdAudit(rest) {
1872
2571
  const events = log.query(filter);
1873
2572
  for (const e of events) console.log(JSON.stringify(e));
1874
2573
  } else {
1875
- throw new Error("Usage: holoscript-agent audit [rollup|query|tail] [--agent=<h>] [--provider=<p>] [--task=<id>] [--kind=<k>] [--limit=<n>] [--log=<path>]");
2574
+ throw new Error(
2575
+ "Usage: holoscript-agent audit [rollup|query|tail] [--agent=<h>] [--provider=<p>] [--task=<id>] [--kind=<k>] [--limit=<n>] [--log=<path>]"
2576
+ );
1876
2577
  }
1877
2578
  }
1878
2579
  async function cmdProvision(rest) {
@@ -1884,7 +2585,9 @@ async function cmdProvision(rest) {
1884
2585
  const force = rest.includes("--force");
1885
2586
  const founderBearer = process.env.HOLOMESH_API_KEY;
1886
2587
  if (!founderBearer) {
1887
- throw new Error("HOLOMESH_API_KEY env var required for provisioning (founder-tier bearer for /register endpoints)");
2588
+ throw new Error(
2589
+ "HOLOMESH_API_KEY env var required for provisioning (founder-tier bearer for /register endpoints)"
2590
+ );
1888
2591
  }
1889
2592
  const result = await provisionAgent(
1890
2593
  {
@@ -1896,7 +2599,9 @@ async function cmdProvision(rest) {
1896
2599
  },
1897
2600
  { execute, force }
1898
2601
  );
1899
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "provision-result", ...result }, null, 2));
2602
+ console.log(
2603
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "provision-result", ...result }, null, 2)
2604
+ );
1900
2605
  if (result.status === "executed" || result.status === "reused") {
1901
2606
  console.log("\n# Add these lines to your .env to use this seat:");
1902
2607
  for (const line of result.envVarLines) console.log(line);
@@ -1908,19 +2613,27 @@ async function cmdStatus(rest) {
1908
2613
  throw new Error("Usage: holoscript-agent status --config=<path-to-agents.json>");
1909
2614
  }
1910
2615
  const config = loadSupervisorConfig(cfgPath);
1911
- console.log(JSON.stringify({
1912
- config: cfgPath,
1913
- agentCount: config.agents.length,
1914
- enabled: config.agents.filter((a) => a.enabled !== false).map((a) => a.handle),
1915
- disabled: config.agents.filter((a) => a.enabled === false).map((a) => a.handle),
1916
- globalBudgetUsdPerDay: config.globalBudgetUsdPerDay ?? null,
1917
- defaultTickIntervalMs: config.defaultTickIntervalMs ?? null
1918
- }, null, 2));
2616
+ console.log(
2617
+ JSON.stringify(
2618
+ {
2619
+ config: cfgPath,
2620
+ agentCount: config.agents.length,
2621
+ enabled: config.agents.filter((a) => a.enabled !== false).map((a) => a.handle),
2622
+ disabled: config.agents.filter((a) => a.enabled === false).map((a) => a.handle),
2623
+ globalBudgetUsdPerDay: config.globalBudgetUsdPerDay ?? null,
2624
+ defaultTickIntervalMs: config.defaultTickIntervalMs ?? null
2625
+ },
2626
+ null,
2627
+ 2
2628
+ )
2629
+ );
1919
2630
  }
1920
2631
  async function cmdAblate(rest) {
1921
2632
  const specPath = rest.find((a) => a.startsWith("--spec="))?.split("=")[1];
1922
2633
  if (!specPath) {
1923
- throw new Error("Usage: holoscript-agent ablate --spec=<path-to-ablation.json> [--out-md=<path>] [--out-json=<path>]");
2634
+ throw new Error(
2635
+ "Usage: holoscript-agent ablate --spec=<path-to-ablation.json> [--out-md=<path>] [--out-json=<path>]"
2636
+ );
1924
2637
  }
1925
2638
  const outMd = rest.find((a) => a.startsWith("--out-md="))?.split("=")[1];
1926
2639
  const outJson = rest.find((a) => a.startsWith("--out-json="))?.split("=")[1];
@@ -1949,7 +2662,12 @@ async function cmdAblate(rest) {
1949
2662
  },
1950
2663
  pricer: p.pricePerCallUsd != null ? () => p.pricePerCallUsd : p.pricePerMtokInput != null && p.pricePerMtokOutput != null ? (u) => (u.promptTokens * p.pricePerMtokInput + u.completionTokens * p.pricePerMtokOutput) / 1e6 : void 0
1951
2664
  }));
1952
- const startMsg = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "ablation-start", task: spec.task.taskId, cells: providers.length });
2665
+ const startMsg = JSON.stringify({
2666
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2667
+ ev: "ablation-start",
2668
+ task: spec.task.taskId,
2669
+ cells: providers.length
2670
+ });
1953
2671
  console.log(startMsg);
1954
2672
  const matrix = await runAblation({
1955
2673
  task: spec.task,
@@ -1967,17 +2685,19 @@ async function cmdAblate(rest) {
1967
2685
  if (!outMd && !outJson) {
1968
2686
  console.log(renderAblationMarkdown(matrix));
1969
2687
  }
1970
- console.log(JSON.stringify({
1971
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1972
- ev: "ablation-done",
1973
- task: matrix.taskId,
1974
- cells: matrix.cells.length,
1975
- errors: matrix.cells.filter((c) => c.errorMessage).length,
1976
- totalCostUsd: matrix.totalCostUsd,
1977
- promptHash: matrix.promptHash,
1978
- outMd: outMd ?? null,
1979
- outJson: outJson ?? null
1980
- }));
2688
+ console.log(
2689
+ JSON.stringify({
2690
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2691
+ ev: "ablation-done",
2692
+ task: matrix.taskId,
2693
+ cells: matrix.cells.length,
2694
+ errors: matrix.cells.filter((c) => c.errorMessage).length,
2695
+ totalCostUsd: matrix.totalCostUsd,
2696
+ promptHash: matrix.promptHash,
2697
+ outMd: outMd ?? null,
2698
+ outJson: outJson ?? null
2699
+ })
2700
+ );
1981
2701
  }
1982
2702
  async function cmdWhoami() {
1983
2703
  const identity = loadIdentity();
@@ -1998,17 +2718,24 @@ async function buildProvider(identity) {
1998
2718
  return createOpenAIProvider({ defaultModel: identity.llmModel });
1999
2719
  case "gemini":
2000
2720
  return createGeminiProvider({ defaultModel: identity.llmModel });
2721
+ case "xai":
2722
+ return createXAIProvider({ defaultModel: identity.llmModel });
2723
+ case "openrouter":
2724
+ return createOpenRouterProvider({ defaultModel: identity.llmModel });
2001
2725
  case "mock":
2002
2726
  return createMockProvider();
2003
2727
  case "local-llm":
2004
2728
  return createLocalLLMProvider({
2005
2729
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
2006
- model: identity.llmModel
2730
+ model: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL ?? identity.llmModel,
2731
+ // Edge devices (Jetson ~15 tok/s) need more than the 120s default.
2732
+ // HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS overrides; default 300s.
2733
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
2007
2734
  });
2735
+ case "sovereign":
2736
+ return (await resolveSovereignProviderAsync(identity.llmModel ? { model: identity.llmModel } : {})).provider;
2008
2737
  default:
2009
- throw new Error(
2010
- `Provider "${p}" not yet wired in CLI \u2014 Phase 2 deliverable. Use anthropic | openai | gemini | local-llm | mock for now.`
2011
- );
2738
+ throw new Error(`Provider "${p}" not yet wired in CLI \u2014 add a case in buildProvider.`);
2012
2739
  }
2013
2740
  }
2014
2741
  function buildCommitHook(identity, mesh) {
@@ -2029,6 +2756,41 @@ function buildCommitHook(identity, mesh) {
2029
2756
  }
2030
2757
  };
2031
2758
  }
2759
+ function canonicalizeSigning(value) {
2760
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2761
+ if (Array.isArray(value))
2762
+ return `[${value.map(canonicalizeSigning).join(",")}]`;
2763
+ const obj = value;
2764
+ return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeSigning(obj[k])}`).join(",")}}`;
2765
+ }
2766
+ function buildRequestSigner(handle) {
2767
+ const seatsRoot = process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join4(homedir3(), ".holoscript-agent", "seats");
2768
+ const fp = createHash4("sha256").update(hostname2() + homedir3()).digest("hex").slice(0, 8);
2769
+ const seatId = `holoscript-${handle}-${fp}-x402`;
2770
+ const walletPath = join4(seatsRoot, seatId, "wallet.enc");
2771
+ const masterKeyPath = join4(seatsRoot, ".master-key");
2772
+ if (!existsSync4(walletPath) || !existsSync4(masterKeyPath)) return void 0;
2773
+ try {
2774
+ const blob = JSON.parse(readFileSync5(walletPath, "utf8"));
2775
+ const masterKey = readFileSync5(masterKeyPath);
2776
+ const iv = Buffer.from(blob.encrypted_privkey.iv, "base64");
2777
+ const ct = Buffer.from(blob.encrypted_privkey.ct, "base64");
2778
+ const tag = Buffer.from(blob.encrypted_privkey.tag, "base64");
2779
+ const decipher = createDecipheriv(blob.encrypted_privkey.alg ?? "aes-256-gcm", masterKey, iv);
2780
+ decipher.setAuthTag(tag);
2781
+ const privateKey = Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
2782
+ const wallet = new Wallet2(privateKey);
2783
+ return async (body) => {
2784
+ const nonce = randomBytes2(16).toString("hex");
2785
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2786
+ const payload = canonicalizeSigning({ body, nonce, timestamp });
2787
+ const signature = await wallet.signMessage(payload);
2788
+ return { body, signature, signer_address: blob.address, nonce, timestamp };
2789
+ };
2790
+ } catch {
2791
+ return void 0;
2792
+ }
2793
+ }
2032
2794
  function scopeTierFromEnv() {
2033
2795
  const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
2034
2796
  if (t === "cold" || t === "warm" || t === "hot") return t;
@@ -2072,8 +2834,8 @@ USAGE
2072
2834
 
2073
2835
  REQUIRED ENV
2074
2836
  HOLOSCRIPT_AGENT_HANDLE agent handle (e.g. "security-auditor")
2075
- HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | local-llm | mock
2076
- HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-7")
2837
+ HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | xai | openrouter | local-llm | sovereign | mock
2838
+ HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-8")
2077
2839
  HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
2078
2840
  HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
2079
2841
  HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
@@ -2092,10 +2854,18 @@ OPTIONAL ENV
2092
2854
  HOLOSCRIPT_AGENT_WORKING_DIR git repo to commit into (default process.cwd())
2093
2855
  HOLOSCRIPT_AGENT_COMMIT_SCOPE commit subject scope (default "agent(<handle>)")
2094
2856
  HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL local-llm provider base URL (default http://localhost:8080)
2857
+ HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL local-llm model id (e.g. "qwen3:4b-instruct"); overrides HOLOSCRIPT_AGENT_MODEL for the local provider
2858
+ HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS local-llm request timeout in ms (default 300000 \u2014 edge devices like Jetson need >120s)
2095
2859
  `);
2096
2860
  }
2097
2861
  main().catch((err) => {
2098
- console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "fatal", message: err instanceof Error ? err.message : String(err) }));
2862
+ console.error(
2863
+ JSON.stringify({
2864
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2865
+ ev: "fatal",
2866
+ message: err instanceof Error ? err.message : String(err)
2867
+ })
2868
+ );
2099
2869
  process.exit(1);
2100
2870
  });
2101
2871
  //# sourceMappingURL=index.js.map