@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/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 +73 -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/identity.js +2 -2
- package/dist/identity.js.map +1 -1
- package/dist/index.js +593 -94
- package/dist/index.js.map +1 -1
- package/dist/provision.js +39 -22
- package/dist/provision.js.map +1 -1
- package/dist/runner.js +289 -20
- 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 +401 -55
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +30 -2
- package/package.json +14 -11
- package/LICENSE +0 -21
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 =
|
|
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
|
|
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,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(
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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({
|
|
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 = {
|
|
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({
|
|
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({
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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, {
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
req.
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
|
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-
|
|
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) => {
|