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