@holoscript/holoscript-agent 2.0.1 → 2.0.3

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,8 +1,10 @@
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 Wallet3 } from "ethers";
6
8
  import {
7
9
  createAnthropicProvider,
8
10
  createOpenAIProvider,
@@ -10,7 +12,8 @@ import {
10
12
  createMockProvider,
11
13
  createLocalLLMProvider,
12
14
  createXAIProvider,
13
- createOpenRouterProvider
15
+ createOpenRouterProvider,
16
+ resolveSovereignProviderAsync
14
17
  } from "@holoscript/llm-provider";
15
18
 
16
19
  // src/identity.ts
@@ -32,7 +35,7 @@ function loadIdentity(env = process.env) {
32
35
  `HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(", ")}]`
33
36
  );
34
37
  }
35
- const x402Bearer = required(env, "HOLOSCRIPT_AGENT_X402_BEARER");
38
+ const x402Bearer = (env.HOLOSCRIPT_AGENT_X402_BEARER ?? "").trim();
36
39
  const wallet = required(env, "HOLOSCRIPT_AGENT_WALLET");
37
40
  if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
38
41
  throw new Error(`HOLOSCRIPT_AGENT_WALLET is not a 0x-prefixed 40-hex address: ${wallet}`);
@@ -69,7 +72,7 @@ function identityForLog(id) {
69
72
  handle: id.handle,
70
73
  surface: id.surface,
71
74
  wallet: `${id.wallet.slice(0, 6)}\u2026${id.wallet.slice(-4)}`,
72
- bearer: `${id.x402Bearer.slice(0, 6)}\u2026`,
75
+ bearer: id.x402Bearer ? `${id.x402Bearer.slice(0, 6)}\u2026` : "(broker-resolved)",
73
76
  provider: id.llmProvider,
74
77
  model: id.llmModel,
75
78
  brain: id.brainPath,
@@ -80,8 +83,9 @@ function identityForLog(id) {
80
83
  // src/brain.ts
81
84
  import { readFile } from "fs/promises";
82
85
  async function loadBrain(brainPath, scopeTier = "warm") {
83
- const systemPrompt = await readFile(brainPath, "utf8");
84
- const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(systemPrompt);
86
+ const raw = await readFile(brainPath, "utf8");
87
+ const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);
88
+ const systemPrompt = extractSystemPromptPreamble(raw);
85
89
  return {
86
90
  brainPath,
87
91
  systemPrompt,
@@ -90,9 +94,31 @@ async function loadBrain(brainPath, scopeTier = "warm") {
90
94
  scopeTier,
91
95
  requires,
92
96
  prefers,
93
- avoids
97
+ avoids,
98
+ reflect: extractReflect(raw),
99
+ onTaskActions: extractOnTaskActions(raw)
94
100
  };
95
101
  }
102
+ function extractReflect(brain) {
103
+ const block = sliceNamedBlock(brain, "reflect");
104
+ if (block === void 0) return void 0;
105
+ const criteria = scalarField(block, "criteria") ?? scalarField(block, "scorer") ?? scalarField(block, "of") ?? "correctness, completeness, and valid HoloScript syntax";
106
+ const escRaw = scalarField(block, "escalate_on_fail") ?? scalarField(block, "escalateOnFail") ?? scalarField(block, "escalate");
107
+ return { criteria, escalateOnFail: (escRaw ?? "").split(",")[0].trim().toLowerCase() === "true" };
108
+ }
109
+ function extractSystemPromptPreamble(src) {
110
+ const lines = src.split("\n");
111
+ const BLOCK_START = /^(#version|#target|#mode|identity\s*\{|state\s*\{|computed\s*\{|traits\s*\[|capabilities\s*\{|directives\s*\{|behavior\s)/;
112
+ let cutLine = -1;
113
+ for (let i = 0; i < lines.length; i++) {
114
+ if (BLOCK_START.test(lines[i].trim())) {
115
+ cutLine = i;
116
+ break;
117
+ }
118
+ }
119
+ if (cutLine <= 0) return src;
120
+ return lines.slice(0, cutLine).join("\n").trimEnd();
121
+ }
96
122
  function extractIdentity(brain) {
97
123
  const identityBlock = sliceNamedBlock(brain, "identity");
98
124
  if (!identityBlock) {
@@ -105,6 +131,53 @@ function extractIdentity(brain) {
105
131
  const avoids = listField(identityBlock, "avoids") ?? [];
106
132
  return { domain, capabilityTags, requires, prefers, avoids };
107
133
  }
134
+ function extractOnTaskActions(brain) {
135
+ const block = sliceNamedBlock(brain, "on_task");
136
+ if (!block) return [];
137
+ const VERBS = ["recall", "rag_query", "llm_call", "plan", "reflect"];
138
+ const entries = [];
139
+ for (const verb of VERBS) {
140
+ const re = new RegExp(`\\b${verb}\\s*\\{`, "g");
141
+ let m;
142
+ while ((m = re.exec(block)) !== null) {
143
+ const start = m.index + m[0].length;
144
+ let depth = 1;
145
+ let end = -1;
146
+ for (let i = start; i < block.length; i++) {
147
+ if (block[i] === "{") depth++;
148
+ else if (block[i] === "}") {
149
+ depth--;
150
+ if (depth === 0) {
151
+ end = i;
152
+ break;
153
+ }
154
+ }
155
+ }
156
+ if (end < 0) continue;
157
+ entries.push({ verb, config: parseKVBlock(block.slice(start, end)), _pos: m.index });
158
+ }
159
+ }
160
+ return entries.sort((a, b) => a._pos - b._pos).map(({ _pos: _ignored, ...rest }) => rest);
161
+ }
162
+ function parseKVBlock(block) {
163
+ const out = {};
164
+ const strRe = /\b(\w+)\s*:\s*"([^"]*)"/g;
165
+ let m;
166
+ while ((m = strRe.exec(block)) !== null) out[m[1]] = m[2];
167
+ const arrRe = /\b(\w+)\s*:\s*\[([^\]]*)\]/g;
168
+ while ((m = arrRe.exec(block)) !== null) {
169
+ out[m[1]] = m[2].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter((s) => s.length > 0);
170
+ }
171
+ const boolRe = /\b(\w+)\s*:\s*(true|false)\b/g;
172
+ while ((m = boolRe.exec(block)) !== null) {
173
+ if (!(m[1] in out)) out[m[1]] = m[2] === "true";
174
+ }
175
+ const numRe = /\b(\w+)\s*:\s*(-?\d+(?:\.\d+)?)\b/g;
176
+ while ((m = numRe.exec(block)) !== null) {
177
+ if (!(m[1] in out)) out[m[1]] = parseFloat(m[2]);
178
+ }
179
+ return out;
180
+ }
108
181
  function sliceNamedBlock(src, name) {
109
182
  const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
110
183
  const match = re.exec(src);
@@ -158,6 +231,8 @@ function listField(block, key) {
158
231
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
159
232
  import { dirname } from "path";
160
233
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
234
+ "claude-opus-4-8": { input: 10, output: 50 },
235
+ // 3× cheaper than 4.7 on total cost; A-020 2026-06-08
161
236
  "claude-opus-4-7": { input: 5, output: 25 },
162
237
  "claude-opus-4-6": { input: 5, output: 25 },
163
238
  "claude-sonnet-4-6": { input: 3, output: 15 },
@@ -335,9 +410,7 @@ function pickProvider(opts) {
335
410
  picked: candidates[0].name,
336
411
  reason: "open-routing-default",
337
412
  unsatisfiedRequires: [],
338
- matchedPrefers: brain.prefers.filter(
339
- (p) => satisfies(candidates[0].capabilities, p)
340
- ),
413
+ matchedPrefers: brain.prefers.filter((p) => satisfies(candidates[0].capabilities, p)),
341
414
  excludedByAvoids,
342
415
  alternatives: candidates.slice(1).map((c) => c.name)
343
416
  };
@@ -351,7 +424,9 @@ function pickProvider(opts) {
351
424
  alternatives: ordered.slice(1).map((c) => c.name)
352
425
  };
353
426
  }
354
- const eligible = notAvoided.filter((c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0);
427
+ const eligible = notAvoided.filter(
428
+ (c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0
429
+ );
355
430
  if (eligible.length === 0) {
356
431
  if (envOverride !== void 0) {
357
432
  const envCandidate = candidates.find((c) => c.name === envOverride);
@@ -443,9 +518,14 @@ var HolomeshClient = class {
443
518
  this.bearer = opts.bearer;
444
519
  this.teamId = opts.teamId;
445
520
  this.fetchImpl = opts.fetchImpl ?? fetch;
521
+ this.signer = opts.signer;
522
+ }
523
+ /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
524
+ async signBody(body) {
525
+ return this.signer ? await this.signer(body) : body;
446
526
  }
447
527
  async heartbeat(payload) {
448
- await this.req("POST", `/team/${this.teamId}/presence`, payload);
528
+ await this.req("POST", `/team/${this.teamId}/presence`, await this.signBody(payload));
449
529
  }
450
530
  async getOpenTasks() {
451
531
  const data = await this.req(
@@ -455,28 +535,33 @@ var HolomeshClient = class {
455
535
  return data.tasks ?? data.open ?? [];
456
536
  }
457
537
  async claim(taskId) {
458
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, { action: "claim" });
538
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "claim" }));
459
539
  }
460
540
  async joinTeam() {
461
541
  return this.req(
462
542
  "POST",
463
543
  `/team/${this.teamId}/join`,
464
- {}
544
+ await this.signBody({})
465
545
  );
466
546
  }
467
547
  async sendMessageOnTask(taskId, body) {
468
- await this.req("POST", `/team/${this.teamId}/message`, {
548
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({
469
549
  to: "team",
470
550
  subject: `task:${taskId}`,
471
551
  content: body
472
- });
552
+ }));
473
553
  }
474
554
  async markDone(taskId, summary, commitHash) {
475
- await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
555
+ await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({
476
556
  action: "done",
477
557
  summary,
478
- commitHash
479
- });
558
+ // verification_evidence required by server before task can be closed.
559
+ verification_evidence: summary,
560
+ // Exclude commitHash when undefined — JSON.stringify drops undefined but
561
+ // canonicalizeSigning preserves it as the literal string "undefined",
562
+ // causing a signature-mismatch vs what the server sees after JSON.parse.
563
+ ...commitHash !== void 0 ? { commitHash } : {}
564
+ }));
480
565
  }
481
566
  // POST CAEL audit records for this agent. Server validator at
482
567
  // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
@@ -510,39 +595,28 @@ var HolomeshClient = class {
510
595
  }
511
596
  /** Post a message to the team feed. */
512
597
  async sendTeamMessage(content, messageType = "text") {
513
- await this.req("POST", `/team/${this.teamId}/message`, {
514
- content,
515
- type: messageType
516
- });
598
+ await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));
517
599
  }
518
600
  // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
519
601
  /** Switch team mode. Requires owner or founder role. */
520
602
  async setTeamMode(mode, reason) {
521
- return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
603
+ return this.req("POST", `/team/${this.teamId}/mode`, await this.signBody({ mode, reason }));
522
604
  }
523
605
  /** Update room preferences. Requires config:write permission. */
524
606
  async patchRoomPrefs(prefs) {
525
- return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
607
+ return this.req("PATCH", `/team/${this.teamId}/room`, await this.signBody(prefs));
526
608
  }
527
609
  /** Update a board task. */
528
610
  async updateTask(taskId, updates) {
529
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
530
- action: "update",
531
- ...updates
532
- });
611
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "update", ...updates }));
533
612
  }
534
613
  /** Delete a board task. */
535
614
  async deleteTask(taskId) {
536
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
537
- action: "delete"
538
- });
615
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delete" }));
539
616
  }
540
617
  /** Delegate a board task to another agent. */
541
618
  async delegateTask(taskId, toAgentId) {
542
- return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
543
- action: "delegate",
544
- toAgentId
545
- });
619
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
546
620
  }
547
621
  async req(method, path, body) {
548
622
  const url = `${this.apiBase}${path}`;
@@ -593,6 +667,44 @@ function priority(t) {
593
667
  return map[String(t.priority).toLowerCase()] ?? 5;
594
668
  }
595
669
 
670
+ // src/bearer-broker.ts
671
+ import { Wallet } from "ethers";
672
+ var RECOVERY_DOMAIN = { name: "HoloMesh", version: "1" };
673
+ var RECOVERY_TYPES = {
674
+ Recovery: [{ name: "nonce", type: "string" }]
675
+ };
676
+ async function resolveBearerViaBroker(opts) {
677
+ const doFetch = opts.fetchImpl ?? fetch;
678
+ const wallet = new Wallet(opts.privateKey);
679
+ const address = wallet.address;
680
+ const base = opts.meshApiBase.replace(/\/+$/, "");
681
+ const chalRes = await doFetch(`${base}/key/challenge`, {
682
+ method: "POST",
683
+ headers: { "Content-Type": "application/json" },
684
+ body: JSON.stringify({ wallet_address: address })
685
+ });
686
+ if (!chalRes.ok) {
687
+ throw new Error(`bearer-broker: /key/challenge returned ${chalRes.status} for ${address}`);
688
+ }
689
+ const chal = await chalRes.json();
690
+ if (!chal.nonce) throw new Error("bearer-broker: /key/challenge returned no nonce");
691
+ const signature = await wallet.signTypedData(RECOVERY_DOMAIN, RECOVERY_TYPES, {
692
+ nonce: chal.nonce
693
+ });
694
+ const recRes = await doFetch(`${base}/key/recover`, {
695
+ method: "POST",
696
+ headers: { "Content-Type": "application/json" },
697
+ body: JSON.stringify({ wallet_address: address, nonce: chal.nonce, signature })
698
+ });
699
+ if (!recRes.ok) {
700
+ throw new Error(`bearer-broker: /key/recover returned ${recRes.status} for ${address}`);
701
+ }
702
+ const rec = await recRes.json();
703
+ const bearer = rec.agent?.api_key;
704
+ if (!bearer) throw new Error("bearer-broker: /key/recover returned no api_key");
705
+ return bearer;
706
+ }
707
+
596
708
  // src/cael-builder.ts
597
709
  import { createHash } from "crypto";
598
710
  function sha(input) {
@@ -611,7 +723,18 @@ function brainClassOf(brain) {
611
723
  return "unknown";
612
724
  }
613
725
  function buildCaelRecord(input) {
614
- const { identity, brain, task, messages, finalText, usage, costUsd, spentUsd, prevChain, runtimeVersion } = input;
726
+ const {
727
+ identity,
728
+ brain,
729
+ task,
730
+ messages,
731
+ finalText,
732
+ usage,
733
+ costUsd,
734
+ spentUsd,
735
+ prevChain,
736
+ runtimeVersion
737
+ } = input;
615
738
  const l0 = sha(brain.systemPrompt);
616
739
  const l1 = sha(`${task.id}|${task.title}|${task.description ?? ""}`);
617
740
  const l2 = sha(JSON.stringify(messages));
@@ -634,9 +757,9 @@ function buildCaelRecord(input) {
634
757
 
635
758
  // src/tools.ts
636
759
  import { readFile as readFile2, writeFile, readdir, mkdir, stat } from "fs/promises";
637
- import { resolve, dirname as dirname2 } from "path";
760
+ import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
638
761
  import { spawn } from "child_process";
639
- var ALLOWED_READ_ROOTS = [
762
+ var FLEET_READ_ROOTS = [
640
763
  "/root/msc-paper-22",
641
764
  // Paper 22 mechanization inputs (scp'd by deploy)
642
765
  "/root/holoscript-mesh",
@@ -644,10 +767,23 @@ var ALLOWED_READ_ROOTS = [
644
767
  "/root/agent-output"
645
768
  // Read back what we wrote
646
769
  ];
647
- var ALLOWED_WRITE_ROOTS = [
770
+ var FLEET_WRITE_ROOTS = [
648
771
  "/root/agent-output"
649
772
  // Single write sink — keeps deliverables in one place
650
773
  ];
774
+ function parseRootsEnv(raw, fallback) {
775
+ if (!raw) return fallback;
776
+ const roots = raw.split(delimiter).map((r) => r.trim()).filter((r) => r.length > 0 && isAbsolute(r));
777
+ return roots.length > 0 ? roots : fallback;
778
+ }
779
+ var ALLOWED_READ_ROOTS = parseRootsEnv(
780
+ process.env.HOLOSCRIPT_AGENT_READ_ROOTS,
781
+ FLEET_READ_ROOTS
782
+ );
783
+ var ALLOWED_WRITE_ROOTS = parseRootsEnv(
784
+ process.env.HOLOSCRIPT_AGENT_WRITE_ROOTS,
785
+ FLEET_WRITE_ROOTS
786
+ );
651
787
  var BASH_READ_ONLY_PREFIXES = [
652
788
  "ls ",
653
789
  "ls\n",
@@ -673,7 +809,15 @@ var BASH_PRODUCTIVE_PREFIXES = [
673
809
  "lean ",
674
810
  "pnpm --filter",
675
811
  "pnpm vitest",
676
- "vitest run"
812
+ "vitest run",
813
+ // Robotics / edge-node (Jetson) productive commands — without these, every
814
+ // ros2/colcon/tegrastats task fails the W.107 artifact gate and is abandoned
815
+ // as no-artifact. (jetson-orin-01 lane.)
816
+ "ros2 launch",
817
+ "ros2 topic pub",
818
+ "ros2 service call",
819
+ "colcon build",
820
+ "tegrastats"
677
821
  ];
678
822
  var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
679
823
  function isProductiveBashCommand(cmd) {
@@ -684,7 +828,7 @@ function isProductiveBashCommand(cmd) {
684
828
  var MESH_TOOLS = [
685
829
  {
686
830
  name: "read_file",
687
- 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).",
831
+ 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.`,
688
832
  input_schema: {
689
833
  type: "object",
690
834
  properties: {
@@ -706,11 +850,11 @@ var MESH_TOOLS = [
706
850
  },
707
851
  {
708
852
  name: "write_file",
709
- 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.",
853
+ 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).`,
710
854
  input_schema: {
711
855
  type: "object",
712
856
  properties: {
713
- path: { type: "string", description: "Absolute path under /root/agent-output/" },
857
+ path: { type: "string", description: `Absolute path under a write root: ${ALLOWED_WRITE_ROOTS.join(", ")}` },
714
858
  content: { type: "string", description: "File content to write (UTF-8)" }
715
859
  },
716
860
  required: ["path", "content"]
@@ -718,7 +862,7 @@ var MESH_TOOLS = [
718
862
  },
719
863
  {
720
864
  name: "bash",
721
- 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.",
865
+ 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.",
722
866
  input_schema: {
723
867
  type: "object",
724
868
  properties: {
@@ -727,22 +871,52 @@ var MESH_TOOLS = [
727
871
  },
728
872
  required: ["cmd"]
729
873
  }
874
+ },
875
+ {
876
+ name: "emit_hardware_receipt",
877
+ 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).",
878
+ input_schema: {
879
+ type: "object",
880
+ properties: {
881
+ device_kind: {
882
+ type: "string",
883
+ description: 'Device identifier, e.g. "jetson-orin-nano-super", "raspberry-pi-5"'
884
+ },
885
+ accelerator: {
886
+ description: 'Accelerator string, e.g. "NVIDIA CUDA 8.7", or null for CPU-only'
887
+ },
888
+ runtime_name: { type: "string", description: 'Inference runtime, e.g. "Ollama", "llama.cpp"' },
889
+ runtime_version: { type: "string", description: 'Runtime version, e.g. "0.30.8"' },
890
+ host_os: { type: "string", description: 'OS + firmware, e.g. "JetPack 6.2.1 / Ubuntu 22.04"' },
891
+ composition_id: { type: "string", description: 'Brain composition reference, e.g. "jetson-orin-brain"' },
892
+ measurements: {
893
+ type: "array",
894
+ description: "Pre-parsed measurements. Each item: {metric: string, value: number, unit: string}",
895
+ items: { type: "object" }
896
+ },
897
+ tegrastats_output: {
898
+ type: "string",
899
+ description: "Raw tegrastats output line(s) \u2014 tool auto-parses GPU%, RAM, temp, power"
900
+ }
901
+ },
902
+ required: ["device_kind", "runtime_name", "runtime_version", "host_os"]
903
+ }
730
904
  }
731
905
  ];
732
906
  function isUnderRoot(absPath, root) {
733
907
  const resolved = resolve(absPath);
734
908
  const rootResolved = resolve(root);
735
- return resolved === rootResolved || resolved.startsWith(rootResolved + "/");
909
+ return resolved === rootResolved || resolved.startsWith(rootResolved + sep);
736
910
  }
737
911
  function checkReadAllowed(path) {
738
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
912
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
739
913
  for (const root of ALLOWED_READ_ROOTS) {
740
914
  if (isUnderRoot(path, root)) return null;
741
915
  }
742
916
  return `read denied \u2014 path "${path}" not under allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}`;
743
917
  }
744
918
  function checkWriteAllowed(path) {
745
- if (!path.startsWith("/")) return `path must be absolute, got "${path}"`;
919
+ if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
746
920
  for (const root of ALLOWED_WRITE_ROOTS) {
747
921
  if (isUnderRoot(path, root)) return null;
748
922
  }
@@ -797,11 +971,105 @@ async function runTool(use) {
797
971
  return result.code === 0 ? okResult(use.id, result.stdout) : errResult(use.id, `exit=${result.code}
798
972
  ${result.stderr || result.stdout}`);
799
973
  }
974
+ if (use.name === "emit_hardware_receipt") {
975
+ const deviceKind = String(use.input.device_kind ?? "unknown-device");
976
+ const accelerator = use.input.accelerator === null || use.input.accelerator === "null" ? null : String(use.input.accelerator ?? "").trim() || null;
977
+ const runtimeName = String(use.input.runtime_name ?? "Ollama");
978
+ const runtimeVersion = String(use.input.runtime_version ?? "unknown");
979
+ const hostOs = String(use.input.host_os ?? "unknown");
980
+ const compositionId = String(use.input.composition_id ?? "unknown");
981
+ let measurements = [];
982
+ if (Array.isArray(use.input.measurements)) {
983
+ for (const m of use.input.measurements) {
984
+ const metric = String(m.metric ?? "");
985
+ const value = Number(m.value ?? 0);
986
+ const unit = String(m.unit ?? "");
987
+ if (metric && Number.isFinite(value)) {
988
+ measurements.push({ metric, value, unit, method: "measured" });
989
+ }
990
+ }
991
+ }
992
+ if (typeof use.input.tegrastats_output === "string" && use.input.tegrastats_output.length > 0) {
993
+ measurements = [...measurements, ...parseTegrastats(use.input.tegrastats_output)];
994
+ }
995
+ if (measurements.length === 0) {
996
+ measurements.push({ metric: "agent-tick", value: 1, unit: "count", method: "presence" });
997
+ }
998
+ const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
999
+ const receipt = {
1000
+ schemaVersion: "holoscript.hardware-receipt-metadata.v1",
1001
+ target: {
1002
+ id: `${deviceKind}-${Date.now()}`,
1003
+ kind: deviceKind,
1004
+ architecture: /jetson|orin|nano|agx|xavier/i.test(deviceKind) ? "arm64" : "unknown",
1005
+ artifactKind: "measurement-trace"
1006
+ },
1007
+ device: {
1008
+ vendor: /jetson|orin|nvidia/i.test(deviceKind) ? "nvidia" : "unknown",
1009
+ model: deviceKind,
1010
+ accelerator
1011
+ },
1012
+ runtime: { name: runtimeName, version: runtimeVersion, hostOS: hostOs },
1013
+ compilerVersion: "holoscript-agent-1.0.0",
1014
+ constraints: [],
1015
+ measuredResults: measurements,
1016
+ replayInputs: [
1017
+ { kind: "composition-ref", uri: `compositions/${compositionId}`, sha256: "unknown" }
1018
+ ],
1019
+ provenance: {
1020
+ capturedAt,
1021
+ sourceCompositionHash: compositionId
1022
+ },
1023
+ owner: {
1024
+ agent: process.env.HOLOSCRIPT_AGENT_HANDLE ?? "unknown",
1025
+ ...process.env.HOLOMESH_TEAM_ID ? { team: process.env.HOLOMESH_TEAM_ID } : {}
1026
+ }
1027
+ };
1028
+ const ts = capturedAt.replace(/[:.]/g, "-");
1029
+ const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
1030
+ const denied = checkWriteAllowed(outPath);
1031
+ if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
1032
+ await mkdir(dirname2(outPath), { recursive: true });
1033
+ await writeFile(outPath, JSON.stringify(receipt, null, 2), "utf8");
1034
+ return okResult(
1035
+ use.id,
1036
+ `Hardware receipt written to ${outPath} \u2014 ${measurements.length} measurements, accelerator=${accelerator ?? "none"}`
1037
+ );
1038
+ }
800
1039
  return errResult(use.id, `unknown tool: ${use.name}`);
801
1040
  } catch (err) {
802
1041
  return errResult(use.id, err instanceof Error ? err.message : String(err));
803
1042
  }
804
1043
  }
1044
+ function parseTegrastats(raw) {
1045
+ const results = [];
1046
+ const m = (pattern, metric, unit, transform) => {
1047
+ const match = raw.match(pattern);
1048
+ if (match?.[1]) {
1049
+ const value = transform ? transform(match[1]) : Number(match[1]);
1050
+ if (Number.isFinite(value)) results.push({ metric, value, unit, method: "tegrastats" });
1051
+ }
1052
+ };
1053
+ const ram = raw.match(/RAM\s+(\d+)\/(\d+)MB/);
1054
+ if (ram) {
1055
+ const used = Number(ram[1]);
1056
+ const total = Number(ram[2]);
1057
+ results.push({ metric: "ram-used", value: used, unit: "MB", method: "tegrastats" });
1058
+ results.push({ metric: "ram-total", value: total, unit: "MB", method: "tegrastats" });
1059
+ if (total > 0)
1060
+ results.push({ metric: "ram-pct", value: Math.round(used / total * 100), unit: "%", method: "tegrastats" });
1061
+ }
1062
+ m(/GR3D_FREQ\s+(\d+)%/, "gpu-util", "%");
1063
+ m(/EMC_FREQ\s+(\d+)%/, "emc-freq-pct", "%");
1064
+ m(/tj@([\d.]+)C/, "temp-tj", "C", parseFloat);
1065
+ m(/cpu@([\d.]+)C/, "temp-cpu", "C", parseFloat);
1066
+ m(/gpu@([\d.]+)C/, "temp-gpu", "C", parseFloat);
1067
+ m(/VDD_SOC\s+(\d+)mW/, "power-soc", "mW");
1068
+ m(/VDD_CPU_CV\s+(\d+)mW/, "power-cpu-cv", "mW");
1069
+ m(/VDD_IN\s+(\d+)mW/, "power-total", "mW");
1070
+ m(/CPU\s+\[(\d+)%/, "cpu-util-core0", "%");
1071
+ return results;
1072
+ }
805
1073
  function runBash(cmd, cwd) {
806
1074
  if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
807
1075
  return Promise.resolve({
@@ -930,8 +1198,28 @@ var AgentRunner = class {
930
1198
  log({ ev: "claim", taskId: target.id, title: target.title });
931
1199
  await mesh.claim(target.id);
932
1200
  const start = Date.now();
1201
+ let systemContent = brain.systemPrompt;
1202
+ if (brain.onTaskActions && brain.onTaskActions.length > 0) {
1203
+ const llmCallAction = brain.onTaskActions.find((a) => a.verb === "llm_call");
1204
+ const deferredVerbs = brain.onTaskActions.filter((a) => a.verb === "recall" || a.verb === "rag_query" || a.verb === "plan").map((a) => a.verb);
1205
+ if (deferredVerbs.length > 0) {
1206
+ log({
1207
+ ev: "on-task-deferred",
1208
+ taskId: target.id,
1209
+ verbs: deferredVerbs,
1210
+ note: "trait-backed dispatch deferred to Phase 2.2 (idea-seeds.md)"
1211
+ });
1212
+ }
1213
+ if (llmCallAction && typeof llmCallAction.config.prompt === "string" && llmCallAction.config.prompt.length > 0) {
1214
+ systemContent = `${brain.systemPrompt}
1215
+
1216
+ [Brain on_task directive]
1217
+ ${llmCallAction.config.prompt}`;
1218
+ log({ ev: "on-task-llm-call", taskId: target.id, promptLen: llmCallAction.config.prompt.length });
1219
+ }
1220
+ }
933
1221
  const messages = [
934
- { role: "system", content: brain.systemPrompt },
1222
+ { role: "system", content: systemContent },
935
1223
  { role: "user", content: buildTaskPrompt(target) }
936
1224
  ];
937
1225
  let aggUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
@@ -949,12 +1237,16 @@ var AgentRunner = class {
949
1237
  finalText = finalText || `[tool-loop hit ${MAX_TOOL_ITERS}-iter cap before final text]`;
950
1238
  break;
951
1239
  }
1240
+ const activeTools = brain.requires.includes("local-llm") ? MESH_TOOLS.filter((t) => t.name === "write_file") : MESH_TOOLS;
952
1241
  const resp = await provider.complete(
953
1242
  {
954
1243
  messages,
955
- maxTokens: 4096,
1244
+ // 8192 for local thinking models (qwen3:4b uses ~3800 tokens on thinking
1245
+ // before the tool-call JSON; 4096 cuts off mid-generation). Frontier
1246
+ // models ignore this ceiling and stop naturally earlier.
1247
+ maxTokens: 8192,
956
1248
  temperature: 0.4,
957
- tools: MESH_TOOLS
1249
+ tools: activeTools
958
1250
  },
959
1251
  identity.llmModel
960
1252
  );
@@ -965,7 +1257,12 @@ var AgentRunner = class {
965
1257
  totalTokens: aggUsage.totalTokens + resp.usage.totalTokens
966
1258
  };
967
1259
  if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
968
- log({ ev: "tool-call", taskId: target.id, iter: iters, tools: resp.toolUses.map((t) => t.name) });
1260
+ log({
1261
+ ev: "tool-call",
1262
+ taskId: target.id,
1263
+ iter: iters,
1264
+ tools: resp.toolUses.map((t) => t.name)
1265
+ });
969
1266
  for (const u of resp.toolUses) {
970
1267
  toolsCalled.add(u.name);
971
1268
  if (u.name === "write_file") {
@@ -974,6 +1271,8 @@ var AgentRunner = class {
974
1271
  } else if (u.name === "bash") {
975
1272
  const cmd = String(u.input?.cmd ?? "");
976
1273
  if (isProductiveBashCommand(cmd)) productiveCallCount++;
1274
+ } else if (u.name === "emit_hardware_receipt") {
1275
+ productiveCallCount++;
977
1276
  }
978
1277
  }
979
1278
  messages.push({
@@ -1018,6 +1317,58 @@ var AgentRunner = class {
1018
1317
  message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
1019
1318
  };
1020
1319
  }
1320
+ let reflectVerdict;
1321
+ if (brain.reflect) {
1322
+ try {
1323
+ const reflectResp = await provider.complete(
1324
+ {
1325
+ messages: [
1326
+ {
1327
+ role: "system",
1328
+ content: "You are a strict reviewer. Evaluate the work against the criteria; do not rewrite it."
1329
+ },
1330
+ {
1331
+ role: "user",
1332
+ content: `Reflect on the artifact produced for this task. Evaluate it for: ${brain.reflect.criteria}.
1333
+
1334
+ --- artifact / final response ---
1335
+ ${finalText.slice(0, 4e3)}
1336
+ --- end ---
1337
+
1338
+ Give a one-line reason, then end with exactly "VERDICT: PASS" or "VERDICT: FAIL".`
1339
+ }
1340
+ ],
1341
+ maxTokens: 512,
1342
+ temperature: 0.1
1343
+ },
1344
+ identity.llmModel
1345
+ );
1346
+ aggUsage = {
1347
+ promptTokens: aggUsage.promptTokens + reflectResp.usage.promptTokens,
1348
+ completionTokens: aggUsage.completionTokens + reflectResp.usage.completionTokens,
1349
+ totalTokens: aggUsage.totalTokens + reflectResp.usage.totalTokens
1350
+ };
1351
+ const verdictMatch = /VERDICT:\s*(PASS|FAIL)/i.exec(reflectResp.content);
1352
+ const pass = verdictMatch ? verdictMatch[1].toUpperCase() === "PASS" : true;
1353
+ reflectVerdict = {
1354
+ pass,
1355
+ reason: reflectResp.content.replace(/VERDICT:\s*(PASS|FAIL)/i, "").trim().slice(0, 300)
1356
+ };
1357
+ log({
1358
+ ev: "reflect",
1359
+ taskId: target.id,
1360
+ pass,
1361
+ escalateOnFail: brain.reflect.escalateOnFail,
1362
+ reason: reflectVerdict.reason.slice(0, 120)
1363
+ });
1364
+ } catch (err) {
1365
+ log({
1366
+ ev: "reflect-error",
1367
+ taskId: target.id,
1368
+ message: err instanceof Error ? err.message : String(err)
1369
+ });
1370
+ }
1371
+ }
1021
1372
  const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
1022
1373
  log({
1023
1374
  ev: "executed",
@@ -1027,7 +1378,11 @@ var AgentRunner = class {
1027
1378
  tokens: aggUsage.totalTokens,
1028
1379
  tool_iters: iters
1029
1380
  });
1030
- const response = { ...lastResponse ?? { content: finalText, usage: aggUsage }, content: finalText, usage: aggUsage };
1381
+ const response = {
1382
+ ...lastResponse ?? { content: finalText, usage: aggUsage },
1383
+ content: finalText,
1384
+ usage: aggUsage
1385
+ };
1031
1386
  const execResult = {
1032
1387
  taskId: target.id,
1033
1388
  responseText: response.content,
@@ -1061,10 +1416,32 @@ var AgentRunner = class {
1061
1416
  });
1062
1417
  const posted = await mesh.postAuditRecords(identity.handle, [caelRecord]);
1063
1418
  this.prevCaelChain = caelRecord.fnv1a_chain;
1064
- log({ ev: "cael-posted", taskId: target.id, appended: posted.appended, rejected: posted.rejected });
1419
+ log({
1420
+ ev: "cael-posted",
1421
+ taskId: target.id,
1422
+ appended: posted.appended,
1423
+ rejected: posted.rejected
1424
+ });
1065
1425
  } catch (err) {
1066
1426
  log({ ev: "cael-post-error", message: err instanceof Error ? err.message : String(err) });
1067
1427
  }
1428
+ if (reflectVerdict && !reflectVerdict.pass && brain.reflect?.escalateOnFail) {
1429
+ try {
1430
+ await mesh.sendMessageOnTask(
1431
+ target.id,
1432
+ `[${identity.handle}] reflect gate FAILED \u2014 escalating to the fleet instead of marking done. Reason: ${reflectVerdict.reason}`
1433
+ );
1434
+ } catch {
1435
+ }
1436
+ log({ ev: "reflect-escalate", taskId: target.id, reason: reflectVerdict.reason.slice(0, 120) });
1437
+ return {
1438
+ action: "reflect-escalate",
1439
+ taskId: target.id,
1440
+ spentUsd: costGuard.getState().spentUsd,
1441
+ remainingUsd: costGuard.getRemainingUsd(),
1442
+ message: `reflect self-evaluation failed; escalated to fleet (reason: ${reflectVerdict.reason.slice(0, 120)})`
1443
+ };
1444
+ }
1068
1445
  if (this.opts.onTaskExecuted) {
1069
1446
  await this.opts.onTaskExecuted(execResult, target);
1070
1447
  } else {
@@ -1079,7 +1456,11 @@ ${response.content}`
1079
1456
  await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
1080
1457
  log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
1081
1458
  } catch (err) {
1082
- log({ ev: "mark-done-error", taskId: target.id, message: err instanceof Error ? err.message : String(err) });
1459
+ log({
1460
+ ev: "mark-done-error",
1461
+ taskId: target.id,
1462
+ message: err instanceof Error ? err.message : String(err)
1463
+ });
1083
1464
  }
1084
1465
  return {
1085
1466
  action: "executed",
@@ -1173,7 +1554,7 @@ function buildTaskPrompt(task) {
1173
1554
  "Description:",
1174
1555
  task.description ?? "(no description)",
1175
1556
  "",
1176
- "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."
1557
+ "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."
1177
1558
  ].join("\n");
1178
1559
  }
1179
1560
  function sleep(ms) {
@@ -1210,7 +1591,9 @@ function makeCommitHook(opts) {
1210
1591
  const relPath = relativeTo(cwd, filePath);
1211
1592
  const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
1212
1593
  if (addRes.status !== 0) {
1213
- throw new Error(`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`);
1594
+ throw new Error(
1595
+ `git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`
1596
+ );
1214
1597
  }
1215
1598
  const message = renderCommitMessage({ scope, task, identity, result });
1216
1599
  const commitArgs = ["commit", "-m", message];
@@ -1219,7 +1602,9 @@ function makeCommitHook(opts) {
1219
1602
  }
1220
1603
  const commitRes = spawn2("git", commitArgs, { cwd, encoding: "utf8" });
1221
1604
  if (commitRes.status !== 0) {
1222
- throw new Error(`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`);
1605
+ throw new Error(
1606
+ `git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`
1607
+ );
1223
1608
  }
1224
1609
  const hashRes = spawn2("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
1225
1610
  const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
@@ -1442,7 +1827,10 @@ USR:${user}`).digest("hex").slice(0, 16);
1442
1827
  }
1443
1828
  function withTimeout(p, ms, label) {
1444
1829
  return new Promise((resolve4, reject) => {
1445
- const timer = setTimeout(() => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)), ms);
1830
+ const timer = setTimeout(
1831
+ () => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)),
1832
+ ms
1833
+ );
1446
1834
  p.then(
1447
1835
  (v) => {
1448
1836
  clearTimeout(timer);
@@ -1700,7 +2088,9 @@ var Supervisor = class {
1700
2088
  }
1701
2089
  const wallet = process.env[spec.walletEnvKey];
1702
2090
  if (!wallet || !/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
1703
- throw new Error(`Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`);
2091
+ throw new Error(
2092
+ `Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`
2093
+ );
1704
2094
  }
1705
2095
  return {
1706
2096
  handle: spec.handle,
@@ -1829,7 +2219,9 @@ function validateAgent(entry, idx, seen) {
1829
2219
  }
1830
2220
  const budgetUsdPerDay = optionalNumber(entry, "budgetUsdPerDay");
1831
2221
  if (budgetUsdPerDay != null && budgetUsdPerDay < 0) {
1832
- throw new Error(`agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`);
2222
+ throw new Error(
2223
+ `agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`
2224
+ );
1833
2225
  }
1834
2226
  const tickIntervalMs = optionalNumber(entry, "tickIntervalMs");
1835
2227
  if (tickIntervalMs != null && tickIntervalMs < 5e3) {
@@ -1887,7 +2279,7 @@ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync a
1887
2279
  import { join as join3 } from "path";
1888
2280
  import { homedir as homedir2, hostname } from "os";
1889
2281
  import { randomBytes, createCipheriv, createHash as createHash3 } from "crypto";
1890
- import { Wallet } from "ethers";
2282
+ import { Wallet as Wallet2 } from "ethers";
1891
2283
  var HANDLE_PATTERN2 = /^[a-z0-9_-]{1,64}$/i;
1892
2284
  var EIP712_DOMAIN = { name: "HoloMesh", version: "1" };
1893
2285
  var EIP712_TYPES = {
@@ -1898,9 +2290,14 @@ async function provisionAgent(req, opts = { execute: false }) {
1898
2290
  throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN2}`);
1899
2291
  }
1900
2292
  if (!req.founderBearer || req.founderBearer.trim().length === 0) {
1901
- throw new Error("founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)");
2293
+ throw new Error(
2294
+ "founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)"
2295
+ );
1902
2296
  }
1903
- const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(/\/$/, "");
2297
+ const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(
2298
+ /\/$/,
2299
+ ""
2300
+ );
1904
2301
  const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
1905
2302
  const surface = req.handle;
1906
2303
  const seatId = makeSeatId(surface);
@@ -1915,10 +2312,7 @@ async function provisionAgent(req, opts = { execute: false }) {
1915
2312
  seatId,
1916
2313
  seatDir,
1917
2314
  willGenerateWallet: !existsSync3(walletPath),
1918
- willCallEndpoints: [
1919
- `POST ${meshApiBase}/register/challenge`,
1920
- `POST ${meshApiBase}/register`
1921
- ]
2315
+ willCallEndpoints: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`]
1922
2316
  };
1923
2317
  }
1924
2318
  if (existsSync3(walletPath) && !opts.force) {
@@ -1934,7 +2328,7 @@ async function provisionAgent(req, opts = { execute: false }) {
1934
2328
  };
1935
2329
  return reused;
1936
2330
  }
1937
- const wallet = Wallet.createRandom();
2331
+ const wallet = Wallet2.createRandom();
1938
2332
  mkdirSync4(seatDir, { recursive: true });
1939
2333
  const masterKey = ensureMasterKey(seatsRoot);
1940
2334
  const encryptedBlob = {
@@ -1961,30 +2355,40 @@ async function provisionAgent(req, opts = { execute: false }) {
1961
2355
  if (!challenge.nonce) {
1962
2356
  throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
1963
2357
  }
1964
- const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, { nonce: challenge.nonce });
1965
- const registration = await postJson(
1966
- fetchImpl,
1967
- `${meshApiBase}/register`,
1968
- req.founderBearer,
1969
- {
1970
- name: req.handle,
1971
- wallet_address: wallet.address,
1972
- nonce: challenge.nonce,
1973
- signature
1974
- }
1975
- );
2358
+ const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
2359
+ nonce: challenge.nonce
2360
+ });
2361
+ const registration = await postJson(fetchImpl, `${meshApiBase}/register`, req.founderBearer, {
2362
+ name: req.handle,
2363
+ wallet_address: wallet.address,
2364
+ nonce: challenge.nonce,
2365
+ signature
2366
+ });
1976
2367
  writeFileSync3(
1977
2368
  regPath,
1978
- JSON.stringify({ status: 201, response: registration, registered_at: (/* @__PURE__ */ new Date()).toISOString(), flow: "x402" }, null, 2),
2369
+ JSON.stringify(
2370
+ {
2371
+ status: 201,
2372
+ response: registration,
2373
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2374
+ flow: "x402"
2375
+ },
2376
+ null,
2377
+ 2
2378
+ ),
1979
2379
  "utf8"
1980
2380
  );
1981
2381
  const agentId = registration.agent?.id;
1982
2382
  const bearer = registration.agent?.api_key;
1983
2383
  if (!agentId || !bearer) {
1984
- throw new Error(`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`);
2384
+ throw new Error(
2385
+ `/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`
2386
+ );
1985
2387
  }
1986
2388
  if (registration.wallet?.private_key) {
1987
- console.warn("[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key).");
2389
+ console.warn(
2390
+ "[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key)."
2391
+ );
1988
2392
  }
1989
2393
  let joinedTeam;
1990
2394
  if (req.autoJoinTeamId) {
@@ -2044,7 +2448,12 @@ function encryptPrivateKey(privKey, masterKey) {
2044
2448
  const iv = randomBytes(12);
2045
2449
  const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
2046
2450
  const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
2047
- return { iv: iv.toString("base64"), ct: ct.toString("base64"), tag: cipher.getAuthTag().toString("base64"), alg: "aes-256-gcm" };
2451
+ return {
2452
+ iv: iv.toString("base64"),
2453
+ ct: ct.toString("base64"),
2454
+ tag: cipher.getAuthTag().toString("base64"),
2455
+ alg: "aes-256-gcm"
2456
+ };
2048
2457
  }
2049
2458
  async function postJson(fetchImpl, url, bearer, body) {
2050
2459
  const res = await fetchImpl(url, {
@@ -2142,10 +2551,31 @@ async function cmdRun(opts) {
2142
2551
  dailyBudgetUsd: identity.budgetUsdPerDay,
2143
2552
  pricer: defaultPricerForProvider(effectiveIdentity.llmProvider)
2144
2553
  });
2554
+ const seat = loadSeatWallet(identity.handle);
2555
+ let bearer = identity.x402Bearer;
2556
+ if (!bearer) {
2557
+ if (!seat) {
2558
+ throw new Error(
2559
+ "No HOLOSCRIPT_AGENT_X402_BEARER set and no seat wallet found to resolve it from the HoloKey broker. Provide a bearer, or point at the seat wallet via HOLOSCRIPT_AGENT_SEATS_ROOT + HOLOSCRIPT_AGENT_SEAT_ID (+ HOLOSCRIPT_AGENT_SEAT_MASTER_KEY)."
2560
+ );
2561
+ }
2562
+ bearer = await resolveBearerViaBroker({
2563
+ privateKey: seat.wallet.privateKey,
2564
+ meshApiBase: identity.meshApiBase
2565
+ });
2566
+ console.log(
2567
+ JSON.stringify({
2568
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2569
+ ev: "bearer-resolved-via-broker",
2570
+ wallet: `${seat.address.slice(0, 6)}\u2026${seat.address.slice(-4)}`
2571
+ })
2572
+ );
2573
+ }
2145
2574
  const mesh = new HolomeshClient({
2146
2575
  apiBase: identity.meshApiBase,
2147
- bearer: identity.x402Bearer,
2148
- teamId: identity.teamId
2576
+ bearer,
2577
+ teamId: identity.teamId,
2578
+ signer: buildRequestSigner(seat)
2149
2579
  });
2150
2580
  const commitHook = buildCommitHook(identity, mesh);
2151
2581
  const auditLog = buildAuditLog();
@@ -2198,8 +2628,13 @@ function supervisorProviderFactory() {
2198
2628
  case "local-llm":
2199
2629
  return createLocalLLMProvider({
2200
2630
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
2201
- model: spec.model
2631
+ model: spec.model,
2632
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
2202
2633
  });
2634
+ case "sovereign":
2635
+ return resolveSovereignProviderAsync(spec.model ? { model: spec.model } : {}).then(
2636
+ (r) => r.provider
2637
+ );
2203
2638
  case "mock":
2204
2639
  return createMockProvider();
2205
2640
  default:
@@ -2392,10 +2827,19 @@ async function cmdAblate(rest) {
2392
2827
  }
2393
2828
  async function cmdWhoami() {
2394
2829
  const identity = loadIdentity();
2830
+ const seat = loadSeatWallet(identity.handle);
2831
+ let bearer = identity.x402Bearer;
2832
+ if (!bearer && seat) {
2833
+ bearer = await resolveBearerViaBroker({
2834
+ privateKey: seat.wallet.privateKey,
2835
+ meshApiBase: identity.meshApiBase
2836
+ });
2837
+ }
2395
2838
  const mesh = new HolomeshClient({
2396
2839
  apiBase: identity.meshApiBase,
2397
- bearer: identity.x402Bearer,
2398
- teamId: identity.teamId
2840
+ bearer,
2841
+ teamId: identity.teamId,
2842
+ signer: buildRequestSigner(seat)
2399
2843
  });
2400
2844
  const me = await mesh.whoAmI();
2401
2845
  console.log(JSON.stringify({ identity: identityForLog(identity), me }, null, 2));
@@ -2418,8 +2862,13 @@ async function buildProvider(identity) {
2418
2862
  case "local-llm":
2419
2863
  return createLocalLLMProvider({
2420
2864
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
2421
- model: identity.llmModel
2865
+ model: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL ?? identity.llmModel,
2866
+ // Edge devices (Jetson ~15 tok/s) need more than the 120s default.
2867
+ // HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS overrides; default 300s.
2868
+ timeoutMs: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS ? Number(process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS) : 3e5
2422
2869
  });
2870
+ case "sovereign":
2871
+ return (await resolveSovereignProviderAsync(identity.llmModel ? { model: identity.llmModel } : {})).provider;
2423
2872
  default:
2424
2873
  throw new Error(`Provider "${p}" not yet wired in CLI \u2014 add a case in buildProvider.`);
2425
2874
  }
@@ -2442,6 +2891,48 @@ function buildCommitHook(identity, mesh) {
2442
2891
  }
2443
2892
  };
2444
2893
  }
2894
+ function canonicalizeSigning(value) {
2895
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2896
+ if (Array.isArray(value))
2897
+ return `[${value.map(canonicalizeSigning).join(",")}]`;
2898
+ const obj = value;
2899
+ return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeSigning(obj[k])}`).join(",")}}`;
2900
+ }
2901
+ function loadSeatWallet(handle) {
2902
+ const seatsRoot = process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join4(homedir3(), ".holoscript-agent", "seats");
2903
+ const fp = createHash4("sha256").update(hostname2() + homedir3()).digest("hex").slice(0, 8);
2904
+ const seatId = process.env.HOLOSCRIPT_AGENT_SEAT_ID ?? `holoscript-${handle}-${fp}-x402`;
2905
+ const walletPath = join4(seatsRoot, seatId, "wallet.enc");
2906
+ const masterKeyPath = process.env.HOLOSCRIPT_AGENT_SEAT_MASTER_KEY ?? join4(seatsRoot, ".master-key");
2907
+ if (!existsSync4(walletPath) || !existsSync4(masterKeyPath)) return void 0;
2908
+ try {
2909
+ const blob = JSON.parse(readFileSync5(walletPath, "utf8"));
2910
+ const masterKey = readFileSync5(masterKeyPath);
2911
+ const iv = Buffer.from(blob.encrypted_privkey.iv, "base64");
2912
+ const ct = Buffer.from(blob.encrypted_privkey.ct, "base64");
2913
+ const tag = Buffer.from(blob.encrypted_privkey.tag, "base64");
2914
+ const decipher = createDecipheriv(
2915
+ blob.encrypted_privkey.alg ?? "aes-256-gcm",
2916
+ masterKey,
2917
+ iv
2918
+ );
2919
+ decipher.setAuthTag(tag);
2920
+ const privateKey = Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
2921
+ return { wallet: new Wallet3(privateKey), address: blob.address };
2922
+ } catch {
2923
+ return void 0;
2924
+ }
2925
+ }
2926
+ function buildRequestSigner(seat) {
2927
+ if (!seat) return void 0;
2928
+ return async (body) => {
2929
+ const nonce = randomBytes2(16).toString("hex");
2930
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2931
+ const payload = canonicalizeSigning({ body, nonce, timestamp });
2932
+ const signature = await seat.wallet.signMessage(payload);
2933
+ return { body, signature, signer_address: seat.address, nonce, timestamp };
2934
+ };
2935
+ }
2445
2936
  function scopeTierFromEnv() {
2446
2937
  const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
2447
2938
  if (t === "cold" || t === "warm" || t === "hot") return t;
@@ -2485,15 +2976,21 @@ USAGE
2485
2976
 
2486
2977
  REQUIRED ENV
2487
2978
  HOLOSCRIPT_AGENT_HANDLE agent handle (e.g. "security-auditor")
2488
- HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | local-llm | mock
2489
- HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-7")
2979
+ HOLOSCRIPT_AGENT_PROVIDER anthropic | openai | gemini | xai | openrouter | local-llm | sovereign | mock
2980
+ HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-8")
2490
2981
  HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
2491
2982
  HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
2492
- HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
2493
2983
  HOLOMESH_TEAM_ID target team id
2494
2984
  ANTHROPIC_API_KEY | OPENAI_API_KEY | GEMINI_API_KEY per provider
2495
2985
 
2496
2986
  OPTIONAL ENV
2987
+ HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer. OPTIONAL: when absent, the runner
2988
+ resolves it from the HoloKey broker by proving wallet
2989
+ ownership (POST /key/challenge \u2192 sign \u2192 /key/recover), so the
2990
+ bearer is never stored in plaintext .env. Requires a seat wallet.
2991
+ HOLOSCRIPT_AGENT_SEAT_ID override the computed seat-dir name (e.g. a sovereign x402 seat
2992
+ "sovereign-<surface>-<fp>-default-x402"); pairs with SEATS_ROOT
2993
+ HOLOSCRIPT_AGENT_SEAT_MASTER_KEY override the master-key path used to decrypt the seat wallet.enc
2497
2994
  HOLOSCRIPT_AGENT_BUDGET_USD_DAY default 5
2498
2995
  HOLOSCRIPT_AGENT_SCOPE_TIER cold | warm | hot (default warm)
2499
2996
  HOLOSCRIPT_AGENT_TICK_MS daemon tick interval, default 60000
@@ -2505,6 +3002,8 @@ OPTIONAL ENV
2505
3002
  HOLOSCRIPT_AGENT_WORKING_DIR git repo to commit into (default process.cwd())
2506
3003
  HOLOSCRIPT_AGENT_COMMIT_SCOPE commit subject scope (default "agent(<handle>)")
2507
3004
  HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL local-llm provider base URL (default http://localhost:8080)
3005
+ HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL local-llm model id (e.g. "qwen3:4b-instruct"); overrides HOLOSCRIPT_AGENT_MODEL for the local provider
3006
+ HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS local-llm request timeout in ms (default 300000 \u2014 edge devices like Jetson need >120s)
2508
3007
  `);
2509
3008
  }
2510
3009
  main().catch((err) => {