@holoscript/holoscript-agent 2.0.1 → 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/README.md +117 -0
- package/bin/holoscript-agent.cjs +3 -1
- package/dist/ablation.js +4 -1
- package/dist/ablation.js.map +1 -1
- package/dist/brain.js +25 -3
- package/dist/brain.js.map +1 -1
- package/dist/commit-hook.js +6 -2
- package/dist/commit-hook.js.map +1 -1
- package/dist/cost-guard.js +2 -0
- package/dist/cost-guard.js.map +1 -1
- package/dist/holomesh-client.d.ts +8 -1
- package/dist/holomesh-client.js +24 -25
- package/dist/holomesh-client.js.map +1 -1
- package/dist/index.js +436 -85
- package/dist/index.js.map +1 -1
- package/dist/provision.js +39 -22
- package/dist/provision.js.map +1 -1
- package/dist/runner.js +268 -19
- package/dist/runner.js.map +1 -1
- package/dist/supervisor-config.js +3 -1
- package/dist/supervisor-config.js.map +1 -1
- package/dist/supervisor.js +332 -54
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +14 -1
- package/package.json +4 -1
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 Wallet2 } 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
|
|
@@ -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
|
|
84
|
-
const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(
|
|
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,30 @@ async function loadBrain(brainPath, scopeTier = "warm") {
|
|
|
90
94
|
scopeTier,
|
|
91
95
|
requires,
|
|
92
96
|
prefers,
|
|
93
|
-
avoids
|
|
97
|
+
avoids,
|
|
98
|
+
reflect: extractReflect(raw)
|
|
94
99
|
};
|
|
95
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();
|
|
120
|
+
}
|
|
96
121
|
function extractIdentity(brain) {
|
|
97
122
|
const identityBlock = sliceNamedBlock(brain, "identity");
|
|
98
123
|
if (!identityBlock) {
|
|
@@ -158,6 +183,8 @@ function listField(block, key) {
|
|
|
158
183
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
159
184
|
import { dirname } from "path";
|
|
160
185
|
var ANTHROPIC_PRICING_USD_PER_MTOK = {
|
|
186
|
+
"claude-opus-4-8": { input: 10, output: 50 },
|
|
187
|
+
// 3× cheaper than 4.7 on total cost; A-020 2026-06-08
|
|
161
188
|
"claude-opus-4-7": { input: 5, output: 25 },
|
|
162
189
|
"claude-opus-4-6": { input: 5, output: 25 },
|
|
163
190
|
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
@@ -335,9 +362,7 @@ function pickProvider(opts) {
|
|
|
335
362
|
picked: candidates[0].name,
|
|
336
363
|
reason: "open-routing-default",
|
|
337
364
|
unsatisfiedRequires: [],
|
|
338
|
-
matchedPrefers: brain.prefers.filter(
|
|
339
|
-
(p) => satisfies(candidates[0].capabilities, p)
|
|
340
|
-
),
|
|
365
|
+
matchedPrefers: brain.prefers.filter((p) => satisfies(candidates[0].capabilities, p)),
|
|
341
366
|
excludedByAvoids,
|
|
342
367
|
alternatives: candidates.slice(1).map((c) => c.name)
|
|
343
368
|
};
|
|
@@ -351,7 +376,9 @@ function pickProvider(opts) {
|
|
|
351
376
|
alternatives: ordered.slice(1).map((c) => c.name)
|
|
352
377
|
};
|
|
353
378
|
}
|
|
354
|
-
const eligible = notAvoided.filter(
|
|
379
|
+
const eligible = notAvoided.filter(
|
|
380
|
+
(c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0
|
|
381
|
+
);
|
|
355
382
|
if (eligible.length === 0) {
|
|
356
383
|
if (envOverride !== void 0) {
|
|
357
384
|
const envCandidate = candidates.find((c) => c.name === envOverride);
|
|
@@ -443,9 +470,14 @@ var HolomeshClient = class {
|
|
|
443
470
|
this.bearer = opts.bearer;
|
|
444
471
|
this.teamId = opts.teamId;
|
|
445
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;
|
|
446
478
|
}
|
|
447
479
|
async heartbeat(payload) {
|
|
448
|
-
await this.req("POST", `/team/${this.teamId}/presence`, payload);
|
|
480
|
+
await this.req("POST", `/team/${this.teamId}/presence`, await this.signBody(payload));
|
|
449
481
|
}
|
|
450
482
|
async getOpenTasks() {
|
|
451
483
|
const data = await this.req(
|
|
@@ -455,28 +487,33 @@ var HolomeshClient = class {
|
|
|
455
487
|
return data.tasks ?? data.open ?? [];
|
|
456
488
|
}
|
|
457
489
|
async claim(taskId) {
|
|
458
|
-
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" }));
|
|
459
491
|
}
|
|
460
492
|
async joinTeam() {
|
|
461
493
|
return this.req(
|
|
462
494
|
"POST",
|
|
463
495
|
`/team/${this.teamId}/join`,
|
|
464
|
-
{}
|
|
496
|
+
await this.signBody({})
|
|
465
497
|
);
|
|
466
498
|
}
|
|
467
499
|
async sendMessageOnTask(taskId, body) {
|
|
468
|
-
await this.req("POST", `/team/${this.teamId}/message`, {
|
|
500
|
+
await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({
|
|
469
501
|
to: "team",
|
|
470
502
|
subject: `task:${taskId}`,
|
|
471
503
|
content: body
|
|
472
|
-
});
|
|
504
|
+
}));
|
|
473
505
|
}
|
|
474
506
|
async markDone(taskId, summary, commitHash) {
|
|
475
|
-
await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
507
|
+
await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({
|
|
476
508
|
action: "done",
|
|
477
509
|
summary,
|
|
478
|
-
|
|
479
|
-
|
|
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
|
+
}));
|
|
480
517
|
}
|
|
481
518
|
// POST CAEL audit records for this agent. Server validator at
|
|
482
519
|
// packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
|
|
@@ -510,39 +547,28 @@ var HolomeshClient = class {
|
|
|
510
547
|
}
|
|
511
548
|
/** Post a message to the team feed. */
|
|
512
549
|
async sendTeamMessage(content, messageType = "text") {
|
|
513
|
-
await this.req("POST", `/team/${this.teamId}/message`, {
|
|
514
|
-
content,
|
|
515
|
-
type: messageType
|
|
516
|
-
});
|
|
550
|
+
await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));
|
|
517
551
|
}
|
|
518
552
|
// ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
|
|
519
553
|
/** Switch team mode. Requires owner or founder role. */
|
|
520
554
|
async setTeamMode(mode, reason) {
|
|
521
|
-
return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
|
|
555
|
+
return this.req("POST", `/team/${this.teamId}/mode`, await this.signBody({ mode, reason }));
|
|
522
556
|
}
|
|
523
557
|
/** Update room preferences. Requires config:write permission. */
|
|
524
558
|
async patchRoomPrefs(prefs) {
|
|
525
|
-
return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
|
|
559
|
+
return this.req("PATCH", `/team/${this.teamId}/room`, await this.signBody(prefs));
|
|
526
560
|
}
|
|
527
561
|
/** Update a board task. */
|
|
528
562
|
async updateTask(taskId, updates) {
|
|
529
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
530
|
-
action: "update",
|
|
531
|
-
...updates
|
|
532
|
-
});
|
|
563
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "update", ...updates }));
|
|
533
564
|
}
|
|
534
565
|
/** Delete a board task. */
|
|
535
566
|
async deleteTask(taskId) {
|
|
536
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
537
|
-
action: "delete"
|
|
538
|
-
});
|
|
567
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delete" }));
|
|
539
568
|
}
|
|
540
569
|
/** Delegate a board task to another agent. */
|
|
541
570
|
async delegateTask(taskId, toAgentId) {
|
|
542
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
543
|
-
action: "delegate",
|
|
544
|
-
toAgentId
|
|
545
|
-
});
|
|
571
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
|
|
546
572
|
}
|
|
547
573
|
async req(method, path, body) {
|
|
548
574
|
const url = `${this.apiBase}${path}`;
|
|
@@ -611,7 +637,18 @@ function brainClassOf(brain) {
|
|
|
611
637
|
return "unknown";
|
|
612
638
|
}
|
|
613
639
|
function buildCaelRecord(input) {
|
|
614
|
-
const {
|
|
640
|
+
const {
|
|
641
|
+
identity,
|
|
642
|
+
brain,
|
|
643
|
+
task,
|
|
644
|
+
messages,
|
|
645
|
+
finalText,
|
|
646
|
+
usage,
|
|
647
|
+
costUsd,
|
|
648
|
+
spentUsd,
|
|
649
|
+
prevChain,
|
|
650
|
+
runtimeVersion
|
|
651
|
+
} = input;
|
|
615
652
|
const l0 = sha(brain.systemPrompt);
|
|
616
653
|
const l1 = sha(`${task.id}|${task.title}|${task.description ?? ""}`);
|
|
617
654
|
const l2 = sha(JSON.stringify(messages));
|
|
@@ -634,9 +671,9 @@ function buildCaelRecord(input) {
|
|
|
634
671
|
|
|
635
672
|
// src/tools.ts
|
|
636
673
|
import { readFile as readFile2, writeFile, readdir, mkdir, stat } from "fs/promises";
|
|
637
|
-
import { resolve, dirname as dirname2 } from "path";
|
|
674
|
+
import { resolve, dirname as dirname2, delimiter, isAbsolute, sep } from "path";
|
|
638
675
|
import { spawn } from "child_process";
|
|
639
|
-
var
|
|
676
|
+
var FLEET_READ_ROOTS = [
|
|
640
677
|
"/root/msc-paper-22",
|
|
641
678
|
// Paper 22 mechanization inputs (scp'd by deploy)
|
|
642
679
|
"/root/holoscript-mesh",
|
|
@@ -644,10 +681,23 @@ var ALLOWED_READ_ROOTS = [
|
|
|
644
681
|
"/root/agent-output"
|
|
645
682
|
// Read back what we wrote
|
|
646
683
|
];
|
|
647
|
-
var
|
|
684
|
+
var FLEET_WRITE_ROOTS = [
|
|
648
685
|
"/root/agent-output"
|
|
649
686
|
// Single write sink — keeps deliverables in one place
|
|
650
687
|
];
|
|
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
|
+
);
|
|
651
701
|
var BASH_READ_ONLY_PREFIXES = [
|
|
652
702
|
"ls ",
|
|
653
703
|
"ls\n",
|
|
@@ -673,7 +723,15 @@ var BASH_PRODUCTIVE_PREFIXES = [
|
|
|
673
723
|
"lean ",
|
|
674
724
|
"pnpm --filter",
|
|
675
725
|
"pnpm vitest",
|
|
676
|
-
"vitest run"
|
|
726
|
+
"vitest run",
|
|
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"
|
|
677
735
|
];
|
|
678
736
|
var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
|
|
679
737
|
function isProductiveBashCommand(cmd) {
|
|
@@ -684,7 +742,7 @@ function isProductiveBashCommand(cmd) {
|
|
|
684
742
|
var MESH_TOOLS = [
|
|
685
743
|
{
|
|
686
744
|
name: "read_file",
|
|
687
|
-
description:
|
|
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.`,
|
|
688
746
|
input_schema: {
|
|
689
747
|
type: "object",
|
|
690
748
|
properties: {
|
|
@@ -706,11 +764,11 @@ var MESH_TOOLS = [
|
|
|
706
764
|
},
|
|
707
765
|
{
|
|
708
766
|
name: "write_file",
|
|
709
|
-
description:
|
|
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).`,
|
|
710
768
|
input_schema: {
|
|
711
769
|
type: "object",
|
|
712
770
|
properties: {
|
|
713
|
-
path: { type: "string", description:
|
|
771
|
+
path: { type: "string", description: `Absolute path under a write root: ${ALLOWED_WRITE_ROOTS.join(", ")}` },
|
|
714
772
|
content: { type: "string", description: "File content to write (UTF-8)" }
|
|
715
773
|
},
|
|
716
774
|
required: ["path", "content"]
|
|
@@ -718,7 +776,7 @@ var MESH_TOOLS = [
|
|
|
718
776
|
},
|
|
719
777
|
{
|
|
720
778
|
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
|
|
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.",
|
|
722
780
|
input_schema: {
|
|
723
781
|
type: "object",
|
|
724
782
|
properties: {
|
|
@@ -727,22 +785,52 @@ var MESH_TOOLS = [
|
|
|
727
785
|
},
|
|
728
786
|
required: ["cmd"]
|
|
729
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
|
+
}
|
|
730
818
|
}
|
|
731
819
|
];
|
|
732
820
|
function isUnderRoot(absPath, root) {
|
|
733
821
|
const resolved = resolve(absPath);
|
|
734
822
|
const rootResolved = resolve(root);
|
|
735
|
-
return resolved === rootResolved || resolved.startsWith(rootResolved +
|
|
823
|
+
return resolved === rootResolved || resolved.startsWith(rootResolved + sep);
|
|
736
824
|
}
|
|
737
825
|
function checkReadAllowed(path) {
|
|
738
|
-
if (!path
|
|
826
|
+
if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
|
|
739
827
|
for (const root of ALLOWED_READ_ROOTS) {
|
|
740
828
|
if (isUnderRoot(path, root)) return null;
|
|
741
829
|
}
|
|
742
830
|
return `read denied \u2014 path "${path}" not under allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}`;
|
|
743
831
|
}
|
|
744
832
|
function checkWriteAllowed(path) {
|
|
745
|
-
if (!path
|
|
833
|
+
if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
|
|
746
834
|
for (const root of ALLOWED_WRITE_ROOTS) {
|
|
747
835
|
if (isUnderRoot(path, root)) return null;
|
|
748
836
|
}
|
|
@@ -797,11 +885,105 @@ async function runTool(use) {
|
|
|
797
885
|
return result.code === 0 ? okResult(use.id, result.stdout) : errResult(use.id, `exit=${result.code}
|
|
798
886
|
${result.stderr || result.stdout}`);
|
|
799
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
|
+
}
|
|
800
953
|
return errResult(use.id, `unknown tool: ${use.name}`);
|
|
801
954
|
} catch (err) {
|
|
802
955
|
return errResult(use.id, err instanceof Error ? err.message : String(err));
|
|
803
956
|
}
|
|
804
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
|
+
}
|
|
805
987
|
function runBash(cmd, cwd) {
|
|
806
988
|
if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
|
|
807
989
|
return Promise.resolve({
|
|
@@ -949,12 +1131,16 @@ var AgentRunner = class {
|
|
|
949
1131
|
finalText = finalText || `[tool-loop hit ${MAX_TOOL_ITERS}-iter cap before final text]`;
|
|
950
1132
|
break;
|
|
951
1133
|
}
|
|
1134
|
+
const activeTools = brain.requires.includes("local-llm") ? MESH_TOOLS.filter((t) => t.name === "write_file") : MESH_TOOLS;
|
|
952
1135
|
const resp = await provider.complete(
|
|
953
1136
|
{
|
|
954
1137
|
messages,
|
|
955
|
-
|
|
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,
|
|
956
1142
|
temperature: 0.4,
|
|
957
|
-
tools:
|
|
1143
|
+
tools: activeTools
|
|
958
1144
|
},
|
|
959
1145
|
identity.llmModel
|
|
960
1146
|
);
|
|
@@ -965,7 +1151,12 @@ var AgentRunner = class {
|
|
|
965
1151
|
totalTokens: aggUsage.totalTokens + resp.usage.totalTokens
|
|
966
1152
|
};
|
|
967
1153
|
if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
|
|
968
|
-
log({
|
|
1154
|
+
log({
|
|
1155
|
+
ev: "tool-call",
|
|
1156
|
+
taskId: target.id,
|
|
1157
|
+
iter: iters,
|
|
1158
|
+
tools: resp.toolUses.map((t) => t.name)
|
|
1159
|
+
});
|
|
969
1160
|
for (const u of resp.toolUses) {
|
|
970
1161
|
toolsCalled.add(u.name);
|
|
971
1162
|
if (u.name === "write_file") {
|
|
@@ -974,6 +1165,8 @@ var AgentRunner = class {
|
|
|
974
1165
|
} else if (u.name === "bash") {
|
|
975
1166
|
const cmd = String(u.input?.cmd ?? "");
|
|
976
1167
|
if (isProductiveBashCommand(cmd)) productiveCallCount++;
|
|
1168
|
+
} else if (u.name === "emit_hardware_receipt") {
|
|
1169
|
+
productiveCallCount++;
|
|
977
1170
|
}
|
|
978
1171
|
}
|
|
979
1172
|
messages.push({
|
|
@@ -1018,6 +1211,58 @@ var AgentRunner = class {
|
|
|
1018
1211
|
message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
|
|
1019
1212
|
};
|
|
1020
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
|
+
}
|
|
1021
1266
|
const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
|
|
1022
1267
|
log({
|
|
1023
1268
|
ev: "executed",
|
|
@@ -1027,7 +1272,11 @@ var AgentRunner = class {
|
|
|
1027
1272
|
tokens: aggUsage.totalTokens,
|
|
1028
1273
|
tool_iters: iters
|
|
1029
1274
|
});
|
|
1030
|
-
const response = {
|
|
1275
|
+
const response = {
|
|
1276
|
+
...lastResponse ?? { content: finalText, usage: aggUsage },
|
|
1277
|
+
content: finalText,
|
|
1278
|
+
usage: aggUsage
|
|
1279
|
+
};
|
|
1031
1280
|
const execResult = {
|
|
1032
1281
|
taskId: target.id,
|
|
1033
1282
|
responseText: response.content,
|
|
@@ -1061,10 +1310,32 @@ var AgentRunner = class {
|
|
|
1061
1310
|
});
|
|
1062
1311
|
const posted = await mesh.postAuditRecords(identity.handle, [caelRecord]);
|
|
1063
1312
|
this.prevCaelChain = caelRecord.fnv1a_chain;
|
|
1064
|
-
log({
|
|
1313
|
+
log({
|
|
1314
|
+
ev: "cael-posted",
|
|
1315
|
+
taskId: target.id,
|
|
1316
|
+
appended: posted.appended,
|
|
1317
|
+
rejected: posted.rejected
|
|
1318
|
+
});
|
|
1065
1319
|
} catch (err) {
|
|
1066
1320
|
log({ ev: "cael-post-error", message: err instanceof Error ? err.message : String(err) });
|
|
1067
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
|
+
}
|
|
1068
1339
|
if (this.opts.onTaskExecuted) {
|
|
1069
1340
|
await this.opts.onTaskExecuted(execResult, target);
|
|
1070
1341
|
} else {
|
|
@@ -1079,7 +1350,11 @@ ${response.content}`
|
|
|
1079
1350
|
await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
|
|
1080
1351
|
log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
|
|
1081
1352
|
} catch (err) {
|
|
1082
|
-
log({
|
|
1353
|
+
log({
|
|
1354
|
+
ev: "mark-done-error",
|
|
1355
|
+
taskId: target.id,
|
|
1356
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1357
|
+
});
|
|
1083
1358
|
}
|
|
1084
1359
|
return {
|
|
1085
1360
|
action: "executed",
|
|
@@ -1173,7 +1448,7 @@ function buildTaskPrompt(task) {
|
|
|
1173
1448
|
"Description:",
|
|
1174
1449
|
task.description ?? "(no description)",
|
|
1175
1450
|
"",
|
|
1176
|
-
"Produce the deliverable
|
|
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."
|
|
1177
1452
|
].join("\n");
|
|
1178
1453
|
}
|
|
1179
1454
|
function sleep(ms) {
|
|
@@ -1210,7 +1485,9 @@ function makeCommitHook(opts) {
|
|
|
1210
1485
|
const relPath = relativeTo(cwd, filePath);
|
|
1211
1486
|
const addRes = spawn2("git", ["add", relPath], { cwd, encoding: "utf8" });
|
|
1212
1487
|
if (addRes.status !== 0) {
|
|
1213
|
-
throw new Error(
|
|
1488
|
+
throw new Error(
|
|
1489
|
+
`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`
|
|
1490
|
+
);
|
|
1214
1491
|
}
|
|
1215
1492
|
const message = renderCommitMessage({ scope, task, identity, result });
|
|
1216
1493
|
const commitArgs = ["commit", "-m", message];
|
|
@@ -1219,7 +1496,9 @@ function makeCommitHook(opts) {
|
|
|
1219
1496
|
}
|
|
1220
1497
|
const commitRes = spawn2("git", commitArgs, { cwd, encoding: "utf8" });
|
|
1221
1498
|
if (commitRes.status !== 0) {
|
|
1222
|
-
throw new Error(
|
|
1499
|
+
throw new Error(
|
|
1500
|
+
`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`
|
|
1501
|
+
);
|
|
1223
1502
|
}
|
|
1224
1503
|
const hashRes = spawn2("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
1225
1504
|
const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
|
|
@@ -1442,7 +1721,10 @@ USR:${user}`).digest("hex").slice(0, 16);
|
|
|
1442
1721
|
}
|
|
1443
1722
|
function withTimeout(p, ms, label) {
|
|
1444
1723
|
return new Promise((resolve4, reject) => {
|
|
1445
|
-
const timer = setTimeout(
|
|
1724
|
+
const timer = setTimeout(
|
|
1725
|
+
() => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)),
|
|
1726
|
+
ms
|
|
1727
|
+
);
|
|
1446
1728
|
p.then(
|
|
1447
1729
|
(v) => {
|
|
1448
1730
|
clearTimeout(timer);
|
|
@@ -1700,7 +1982,9 @@ var Supervisor = class {
|
|
|
1700
1982
|
}
|
|
1701
1983
|
const wallet = process.env[spec.walletEnvKey];
|
|
1702
1984
|
if (!wallet || !/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
|
|
1703
|
-
throw new Error(
|
|
1985
|
+
throw new Error(
|
|
1986
|
+
`Missing or malformed wallet env var "${spec.walletEnvKey}" for agent "${spec.handle}"`
|
|
1987
|
+
);
|
|
1704
1988
|
}
|
|
1705
1989
|
return {
|
|
1706
1990
|
handle: spec.handle,
|
|
@@ -1829,7 +2113,9 @@ function validateAgent(entry, idx, seen) {
|
|
|
1829
2113
|
}
|
|
1830
2114
|
const budgetUsdPerDay = optionalNumber(entry, "budgetUsdPerDay");
|
|
1831
2115
|
if (budgetUsdPerDay != null && budgetUsdPerDay < 0) {
|
|
1832
|
-
throw new Error(
|
|
2116
|
+
throw new Error(
|
|
2117
|
+
`agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`
|
|
2118
|
+
);
|
|
1833
2119
|
}
|
|
1834
2120
|
const tickIntervalMs = optionalNumber(entry, "tickIntervalMs");
|
|
1835
2121
|
if (tickIntervalMs != null && tickIntervalMs < 5e3) {
|
|
@@ -1898,9 +2184,14 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
1898
2184
|
throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN2}`);
|
|
1899
2185
|
}
|
|
1900
2186
|
if (!req.founderBearer || req.founderBearer.trim().length === 0) {
|
|
1901
|
-
throw new Error(
|
|
2187
|
+
throw new Error(
|
|
2188
|
+
"founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)"
|
|
2189
|
+
);
|
|
1902
2190
|
}
|
|
1903
|
-
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
|
+
);
|
|
1904
2195
|
const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
|
|
1905
2196
|
const surface = req.handle;
|
|
1906
2197
|
const seatId = makeSeatId(surface);
|
|
@@ -1915,10 +2206,7 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
1915
2206
|
seatId,
|
|
1916
2207
|
seatDir,
|
|
1917
2208
|
willGenerateWallet: !existsSync3(walletPath),
|
|
1918
|
-
willCallEndpoints: [
|
|
1919
|
-
`POST ${meshApiBase}/register/challenge`,
|
|
1920
|
-
`POST ${meshApiBase}/register`
|
|
1921
|
-
]
|
|
2209
|
+
willCallEndpoints: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`]
|
|
1922
2210
|
};
|
|
1923
2211
|
}
|
|
1924
2212
|
if (existsSync3(walletPath) && !opts.force) {
|
|
@@ -1961,30 +2249,40 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
1961
2249
|
if (!challenge.nonce) {
|
|
1962
2250
|
throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
|
|
1963
2251
|
}
|
|
1964
|
-
const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
req.
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
signature
|
|
1974
|
-
}
|
|
1975
|
-
);
|
|
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
|
+
});
|
|
1976
2261
|
writeFileSync3(
|
|
1977
2262
|
regPath,
|
|
1978
|
-
JSON.stringify(
|
|
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
|
+
),
|
|
1979
2273
|
"utf8"
|
|
1980
2274
|
);
|
|
1981
2275
|
const agentId = registration.agent?.id;
|
|
1982
2276
|
const bearer = registration.agent?.api_key;
|
|
1983
2277
|
if (!agentId || !bearer) {
|
|
1984
|
-
throw new Error(
|
|
2278
|
+
throw new Error(
|
|
2279
|
+
`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`
|
|
2280
|
+
);
|
|
1985
2281
|
}
|
|
1986
2282
|
if (registration.wallet?.private_key) {
|
|
1987
|
-
console.warn(
|
|
2283
|
+
console.warn(
|
|
2284
|
+
"[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key)."
|
|
2285
|
+
);
|
|
1988
2286
|
}
|
|
1989
2287
|
let joinedTeam;
|
|
1990
2288
|
if (req.autoJoinTeamId) {
|
|
@@ -2044,7 +2342,12 @@ function encryptPrivateKey(privKey, masterKey) {
|
|
|
2044
2342
|
const iv = randomBytes(12);
|
|
2045
2343
|
const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
|
|
2046
2344
|
const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
|
|
2047
|
-
return {
|
|
2345
|
+
return {
|
|
2346
|
+
iv: iv.toString("base64"),
|
|
2347
|
+
ct: ct.toString("base64"),
|
|
2348
|
+
tag: cipher.getAuthTag().toString("base64"),
|
|
2349
|
+
alg: "aes-256-gcm"
|
|
2350
|
+
};
|
|
2048
2351
|
}
|
|
2049
2352
|
async function postJson(fetchImpl, url, bearer, body) {
|
|
2050
2353
|
const res = await fetchImpl(url, {
|
|
@@ -2145,7 +2448,8 @@ async function cmdRun(opts) {
|
|
|
2145
2448
|
const mesh = new HolomeshClient({
|
|
2146
2449
|
apiBase: identity.meshApiBase,
|
|
2147
2450
|
bearer: identity.x402Bearer,
|
|
2148
|
-
teamId: identity.teamId
|
|
2451
|
+
teamId: identity.teamId,
|
|
2452
|
+
signer: buildRequestSigner(identity.handle)
|
|
2149
2453
|
});
|
|
2150
2454
|
const commitHook = buildCommitHook(identity, mesh);
|
|
2151
2455
|
const auditLog = buildAuditLog();
|
|
@@ -2198,8 +2502,13 @@ function supervisorProviderFactory() {
|
|
|
2198
2502
|
case "local-llm":
|
|
2199
2503
|
return createLocalLLMProvider({
|
|
2200
2504
|
baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
|
|
2201
|
-
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
|
|
2202
2507
|
});
|
|
2508
|
+
case "sovereign":
|
|
2509
|
+
return resolveSovereignProviderAsync(spec.model ? { model: spec.model } : {}).then(
|
|
2510
|
+
(r) => r.provider
|
|
2511
|
+
);
|
|
2203
2512
|
case "mock":
|
|
2204
2513
|
return createMockProvider();
|
|
2205
2514
|
default:
|
|
@@ -2418,8 +2727,13 @@ async function buildProvider(identity) {
|
|
|
2418
2727
|
case "local-llm":
|
|
2419
2728
|
return createLocalLLMProvider({
|
|
2420
2729
|
baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
|
|
2421
|
-
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
|
|
2422
2734
|
});
|
|
2735
|
+
case "sovereign":
|
|
2736
|
+
return (await resolveSovereignProviderAsync(identity.llmModel ? { model: identity.llmModel } : {})).provider;
|
|
2423
2737
|
default:
|
|
2424
2738
|
throw new Error(`Provider "${p}" not yet wired in CLI \u2014 add a case in buildProvider.`);
|
|
2425
2739
|
}
|
|
@@ -2442,6 +2756,41 @@ function buildCommitHook(identity, mesh) {
|
|
|
2442
2756
|
}
|
|
2443
2757
|
};
|
|
2444
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
|
+
}
|
|
2445
2794
|
function scopeTierFromEnv() {
|
|
2446
2795
|
const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
|
|
2447
2796
|
if (t === "cold" || t === "warm" || t === "hot") return t;
|
|
@@ -2485,8 +2834,8 @@ USAGE
|
|
|
2485
2834
|
|
|
2486
2835
|
REQUIRED ENV
|
|
2487
2836
|
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-
|
|
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")
|
|
2490
2839
|
HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
|
|
2491
2840
|
HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
|
|
2492
2841
|
HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
|
|
@@ -2505,6 +2854,8 @@ OPTIONAL ENV
|
|
|
2505
2854
|
HOLOSCRIPT_AGENT_WORKING_DIR git repo to commit into (default process.cwd())
|
|
2506
2855
|
HOLOSCRIPT_AGENT_COMMIT_SCOPE commit subject scope (default "agent(<handle>)")
|
|
2507
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)
|
|
2508
2859
|
`);
|
|
2509
2860
|
}
|
|
2510
2861
|
main().catch((err) => {
|