@holoscript/holoscript-agent 2.0.2 → 2.0.4
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/bin/holoscript-agent.cjs +0 -0
- package/dist/brain.js +49 -1
- package/dist/brain.js.map +1 -1
- package/dist/holomesh-client.d.ts +21 -1
- package/dist/holomesh-client.js +31 -0
- 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 +301 -24
- package/dist/index.js.map +1 -1
- package/dist/runner.js +119 -1
- package/dist/runner.js.map +1 -1
- package/dist/supervisor.js +199 -2
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +16 -1
- package/package.json +11 -11
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { homedir as homedir3, hostname as hostname2 } from "os";
|
|
5
5
|
import { join as join4 } from "path";
|
|
6
6
|
import { createHash as createHash4, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
|
|
7
|
-
import { Wallet as
|
|
7
|
+
import { Wallet as Wallet3 } from "ethers";
|
|
8
8
|
import {
|
|
9
9
|
createAnthropicProvider,
|
|
10
10
|
createOpenAIProvider,
|
|
@@ -35,7 +35,7 @@ function loadIdentity(env = process.env) {
|
|
|
35
35
|
`HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(", ")}]`
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
|
-
const x402Bearer =
|
|
38
|
+
const x402Bearer = (env.HOLOSCRIPT_AGENT_X402_BEARER ?? "").trim();
|
|
39
39
|
const wallet = required(env, "HOLOSCRIPT_AGENT_WALLET");
|
|
40
40
|
if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
|
|
41
41
|
throw new Error(`HOLOSCRIPT_AGENT_WALLET is not a 0x-prefixed 40-hex address: ${wallet}`);
|
|
@@ -72,7 +72,7 @@ function identityForLog(id) {
|
|
|
72
72
|
handle: id.handle,
|
|
73
73
|
surface: id.surface,
|
|
74
74
|
wallet: `${id.wallet.slice(0, 6)}\u2026${id.wallet.slice(-4)}`,
|
|
75
|
-
bearer: `${id.x402Bearer.slice(0, 6)}\u2026
|
|
75
|
+
bearer: id.x402Bearer ? `${id.x402Bearer.slice(0, 6)}\u2026` : "(broker-resolved)",
|
|
76
76
|
provider: id.llmProvider,
|
|
77
77
|
model: id.llmModel,
|
|
78
78
|
brain: id.brainPath,
|
|
@@ -95,7 +95,8 @@ async function loadBrain(brainPath, scopeTier = "warm") {
|
|
|
95
95
|
requires,
|
|
96
96
|
prefers,
|
|
97
97
|
avoids,
|
|
98
|
-
reflect: extractReflect(raw)
|
|
98
|
+
reflect: extractReflect(raw),
|
|
99
|
+
onTaskActions: extractOnTaskActions(raw)
|
|
99
100
|
};
|
|
100
101
|
}
|
|
101
102
|
function extractReflect(brain) {
|
|
@@ -130,6 +131,53 @@ function extractIdentity(brain) {
|
|
|
130
131
|
const avoids = listField(identityBlock, "avoids") ?? [];
|
|
131
132
|
return { domain, capabilityTags, requires, prefers, avoids };
|
|
132
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
|
+
}
|
|
133
181
|
function sliceNamedBlock(src, name) {
|
|
134
182
|
const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
|
|
135
183
|
const match = re.exec(src);
|
|
@@ -570,6 +618,37 @@ var HolomeshClient = class {
|
|
|
570
618
|
async delegateTask(taskId, toAgentId) {
|
|
571
619
|
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
|
|
572
620
|
}
|
|
621
|
+
// ── Cognitive-verb knowledge surface (Phase 2.2 — recall / rag_query) ────────
|
|
622
|
+
/**
|
|
623
|
+
* Query the TEAM knowledge base (the `rag_query` cognitive verb). Bearer-only
|
|
624
|
+
* GET; `q` is the server-side search filter. Returns [] on any failure so a
|
|
625
|
+
* retrieval miss never breaks a tick.
|
|
626
|
+
*/
|
|
627
|
+
async queryTeamKnowledge(query, limit = 5) {
|
|
628
|
+
const qs = new URLSearchParams({ q: query, limit: String(limit) }).toString();
|
|
629
|
+
try {
|
|
630
|
+
const data = await this.req(
|
|
631
|
+
"GET",
|
|
632
|
+
`/team/${this.teamId}/knowledge?${qs}`
|
|
633
|
+
);
|
|
634
|
+
return data.entries ?? [];
|
|
635
|
+
} catch {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Query this agent's PRIVATE workspace knowledge (the `recall` cognitive verb).
|
|
641
|
+
* The endpoint has no server-side search param, so the caller filters by query
|
|
642
|
+
* client-side. Returns [] on any failure.
|
|
643
|
+
*/
|
|
644
|
+
async queryPrivateKnowledge() {
|
|
645
|
+
try {
|
|
646
|
+
const data = await this.req("GET", `/knowledge/private`);
|
|
647
|
+
return data.entries ?? [];
|
|
648
|
+
} catch {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
}
|
|
573
652
|
async req(method, path, body) {
|
|
574
653
|
const url = `${this.apiBase}${path}`;
|
|
575
654
|
const res = await this.fetchImpl(url, {
|
|
@@ -619,6 +698,44 @@ function priority(t) {
|
|
|
619
698
|
return map[String(t.priority).toLowerCase()] ?? 5;
|
|
620
699
|
}
|
|
621
700
|
|
|
701
|
+
// src/bearer-broker.ts
|
|
702
|
+
import { Wallet } from "ethers";
|
|
703
|
+
var RECOVERY_DOMAIN = { name: "HoloMesh", version: "1" };
|
|
704
|
+
var RECOVERY_TYPES = {
|
|
705
|
+
Recovery: [{ name: "nonce", type: "string" }]
|
|
706
|
+
};
|
|
707
|
+
async function resolveBearerViaBroker(opts) {
|
|
708
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
709
|
+
const wallet = new Wallet(opts.privateKey);
|
|
710
|
+
const address = wallet.address;
|
|
711
|
+
const base = opts.meshApiBase.replace(/\/+$/, "");
|
|
712
|
+
const chalRes = await doFetch(`${base}/key/challenge`, {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({ wallet_address: address })
|
|
716
|
+
});
|
|
717
|
+
if (!chalRes.ok) {
|
|
718
|
+
throw new Error(`bearer-broker: /key/challenge returned ${chalRes.status} for ${address}`);
|
|
719
|
+
}
|
|
720
|
+
const chal = await chalRes.json();
|
|
721
|
+
if (!chal.nonce) throw new Error("bearer-broker: /key/challenge returned no nonce");
|
|
722
|
+
const signature = await wallet.signTypedData(RECOVERY_DOMAIN, RECOVERY_TYPES, {
|
|
723
|
+
nonce: chal.nonce
|
|
724
|
+
});
|
|
725
|
+
const recRes = await doFetch(`${base}/key/recover`, {
|
|
726
|
+
method: "POST",
|
|
727
|
+
headers: { "Content-Type": "application/json" },
|
|
728
|
+
body: JSON.stringify({ wallet_address: address, nonce: chal.nonce, signature })
|
|
729
|
+
});
|
|
730
|
+
if (!recRes.ok) {
|
|
731
|
+
throw new Error(`bearer-broker: /key/recover returned ${recRes.status} for ${address}`);
|
|
732
|
+
}
|
|
733
|
+
const rec = await recRes.json();
|
|
734
|
+
const bearer = rec.agent?.api_key;
|
|
735
|
+
if (!bearer) throw new Error("bearer-broker: /key/recover returned no api_key");
|
|
736
|
+
return bearer;
|
|
737
|
+
}
|
|
738
|
+
|
|
622
739
|
// src/cael-builder.ts
|
|
623
740
|
import { createHash } from "crypto";
|
|
624
741
|
function sha(input) {
|
|
@@ -1030,6 +1147,109 @@ function errResult(id, message) {
|
|
|
1030
1147
|
return { type: "tool_result", tool_use_id: id, content: message, is_error: true };
|
|
1031
1148
|
}
|
|
1032
1149
|
|
|
1150
|
+
// src/cognitive-verbs.ts
|
|
1151
|
+
var DEFAULT_LIMIT = 5;
|
|
1152
|
+
var MAX_ENTRY_CHARS = 320;
|
|
1153
|
+
var MAX_INJECTED_CHARS = 2400;
|
|
1154
|
+
function strField(config, ...keys) {
|
|
1155
|
+
for (const k of keys) {
|
|
1156
|
+
const v = config[k];
|
|
1157
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
1158
|
+
}
|
|
1159
|
+
return "";
|
|
1160
|
+
}
|
|
1161
|
+
function numField(config, key, fallback) {
|
|
1162
|
+
const v = config[key];
|
|
1163
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
1164
|
+
}
|
|
1165
|
+
function formatEntries(entries) {
|
|
1166
|
+
let out = "";
|
|
1167
|
+
for (const e of entries) {
|
|
1168
|
+
const line = `- ${(e.content ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_ENTRY_CHARS)}`;
|
|
1169
|
+
if (out.length + line.length > MAX_INJECTED_CHARS) break;
|
|
1170
|
+
out += (out ? "\n" : "") + line;
|
|
1171
|
+
}
|
|
1172
|
+
return out;
|
|
1173
|
+
}
|
|
1174
|
+
async function augmentWithOnTaskCognition(deps) {
|
|
1175
|
+
let content = deps.systemPrompt;
|
|
1176
|
+
if (!deps.onTaskActions || deps.onTaskActions.length === 0) return content;
|
|
1177
|
+
for (const action of deps.onTaskActions) {
|
|
1178
|
+
try {
|
|
1179
|
+
switch (action.verb) {
|
|
1180
|
+
case "llm_call": {
|
|
1181
|
+
const prompt = strField(action.config, "prompt");
|
|
1182
|
+
if (prompt) {
|
|
1183
|
+
content += `
|
|
1184
|
+
|
|
1185
|
+
[Brain on_task directive]
|
|
1186
|
+
${prompt}`;
|
|
1187
|
+
deps.log({ ev: "on-task-llm-call", taskId: deps.task.id, promptLen: prompt.length });
|
|
1188
|
+
}
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
case "rag_query": {
|
|
1192
|
+
const query = strField(action.config, "query", "q") || deps.task.title;
|
|
1193
|
+
const limit = numField(action.config, "limit", DEFAULT_LIMIT);
|
|
1194
|
+
const entries = await deps.queryTeamKnowledge(query, limit);
|
|
1195
|
+
if (entries.length > 0) {
|
|
1196
|
+
content += `
|
|
1197
|
+
|
|
1198
|
+
[Retrieved knowledge for "${query}"]
|
|
1199
|
+
${formatEntries(entries)}`;
|
|
1200
|
+
}
|
|
1201
|
+
deps.log({ ev: "on-task-rag-query", taskId: deps.task.id, query, retrieved: entries.length });
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
case "recall": {
|
|
1205
|
+
const query = strField(action.config, "query", "q");
|
|
1206
|
+
const limit = numField(action.config, "limit", DEFAULT_LIMIT);
|
|
1207
|
+
const all = await deps.queryPrivateKnowledge();
|
|
1208
|
+
const needle = query.toLowerCase();
|
|
1209
|
+
const matched = (needle ? all.filter((e) => `${e.id ?? ""} ${e.content ?? ""}`.toLowerCase().includes(needle)) : all).slice(0, limit);
|
|
1210
|
+
if (matched.length > 0) {
|
|
1211
|
+
content += `
|
|
1212
|
+
|
|
1213
|
+
[Recalled memory${query ? ` for "${query}"` : ""}]
|
|
1214
|
+
${formatEntries(matched)}`;
|
|
1215
|
+
}
|
|
1216
|
+
deps.log({ ev: "on-task-recall", taskId: deps.task.id, query, recalled: matched.length });
|
|
1217
|
+
break;
|
|
1218
|
+
}
|
|
1219
|
+
case "plan": {
|
|
1220
|
+
if (!deps.plan) break;
|
|
1221
|
+
const goal = strField(action.config, "goal", "prompt", "of") || deps.task.title;
|
|
1222
|
+
const planText = await deps.plan(
|
|
1223
|
+
`Produce a short numbered plan (max 6 steps) to accomplish this task. Be concrete and specific to the goal; do not execute it.
|
|
1224
|
+
|
|
1225
|
+
Goal: ${goal}`
|
|
1226
|
+
);
|
|
1227
|
+
const trimmed = planText.trim().slice(0, MAX_INJECTED_CHARS);
|
|
1228
|
+
if (trimmed) {
|
|
1229
|
+
content += `
|
|
1230
|
+
|
|
1231
|
+
[Plan]
|
|
1232
|
+
${trimmed}`;
|
|
1233
|
+
deps.log({ ev: "on-task-plan", taskId: deps.task.id, planLen: trimmed.length });
|
|
1234
|
+
}
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
// `reflect` is handled post-artifact in runner.ts, not here.
|
|
1238
|
+
default:
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
deps.log({
|
|
1243
|
+
ev: "on-task-verb-error",
|
|
1244
|
+
taskId: deps.task.id,
|
|
1245
|
+
verb: action.verb,
|
|
1246
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return content;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1033
1253
|
// src/runner.ts
|
|
1034
1254
|
var RUNTIME_VERSION = "1.0.0";
|
|
1035
1255
|
var AgentRunner = class {
|
|
@@ -1112,8 +1332,23 @@ var AgentRunner = class {
|
|
|
1112
1332
|
log({ ev: "claim", taskId: target.id, title: target.title });
|
|
1113
1333
|
await mesh.claim(target.id);
|
|
1114
1334
|
const start = Date.now();
|
|
1335
|
+
const systemContent = await augmentWithOnTaskCognition({
|
|
1336
|
+
systemPrompt: brain.systemPrompt,
|
|
1337
|
+
onTaskActions: brain.onTaskActions ?? [],
|
|
1338
|
+
task: { id: target.id, title: target.title },
|
|
1339
|
+
queryTeamKnowledge: (q, limit) => mesh.queryTeamKnowledge(q, limit),
|
|
1340
|
+
queryPrivateKnowledge: () => mesh.queryPrivateKnowledge(),
|
|
1341
|
+
plan: async (prompt) => {
|
|
1342
|
+
const resp = await provider.complete(
|
|
1343
|
+
{ messages: [{ role: "user", content: prompt }], maxTokens: 512, temperature: 0.3 },
|
|
1344
|
+
identity.llmModel
|
|
1345
|
+
);
|
|
1346
|
+
return resp.content;
|
|
1347
|
+
},
|
|
1348
|
+
log
|
|
1349
|
+
});
|
|
1115
1350
|
const messages = [
|
|
1116
|
-
{ role: "system", content:
|
|
1351
|
+
{ role: "system", content: systemContent },
|
|
1117
1352
|
{ role: "user", content: buildTaskPrompt(target) }
|
|
1118
1353
|
];
|
|
1119
1354
|
let aggUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
@@ -2173,7 +2408,7 @@ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync a
|
|
|
2173
2408
|
import { join as join3 } from "path";
|
|
2174
2409
|
import { homedir as homedir2, hostname } from "os";
|
|
2175
2410
|
import { randomBytes, createCipheriv, createHash as createHash3 } from "crypto";
|
|
2176
|
-
import { Wallet } from "ethers";
|
|
2411
|
+
import { Wallet as Wallet2 } from "ethers";
|
|
2177
2412
|
var HANDLE_PATTERN2 = /^[a-z0-9_-]{1,64}$/i;
|
|
2178
2413
|
var EIP712_DOMAIN = { name: "HoloMesh", version: "1" };
|
|
2179
2414
|
var EIP712_TYPES = {
|
|
@@ -2222,7 +2457,7 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
2222
2457
|
};
|
|
2223
2458
|
return reused;
|
|
2224
2459
|
}
|
|
2225
|
-
const wallet =
|
|
2460
|
+
const wallet = Wallet2.createRandom();
|
|
2226
2461
|
mkdirSync4(seatDir, { recursive: true });
|
|
2227
2462
|
const masterKey = ensureMasterKey(seatsRoot);
|
|
2228
2463
|
const encryptedBlob = {
|
|
@@ -2445,11 +2680,31 @@ async function cmdRun(opts) {
|
|
|
2445
2680
|
dailyBudgetUsd: identity.budgetUsdPerDay,
|
|
2446
2681
|
pricer: defaultPricerForProvider(effectiveIdentity.llmProvider)
|
|
2447
2682
|
});
|
|
2683
|
+
const seat = loadSeatWallet(identity.handle);
|
|
2684
|
+
let bearer = identity.x402Bearer;
|
|
2685
|
+
if (!bearer) {
|
|
2686
|
+
if (!seat) {
|
|
2687
|
+
throw new Error(
|
|
2688
|
+
"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)."
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
bearer = await resolveBearerViaBroker({
|
|
2692
|
+
privateKey: seat.wallet.privateKey,
|
|
2693
|
+
meshApiBase: identity.meshApiBase
|
|
2694
|
+
});
|
|
2695
|
+
console.log(
|
|
2696
|
+
JSON.stringify({
|
|
2697
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2698
|
+
ev: "bearer-resolved-via-broker",
|
|
2699
|
+
wallet: `${seat.address.slice(0, 6)}\u2026${seat.address.slice(-4)}`
|
|
2700
|
+
})
|
|
2701
|
+
);
|
|
2702
|
+
}
|
|
2448
2703
|
const mesh = new HolomeshClient({
|
|
2449
2704
|
apiBase: identity.meshApiBase,
|
|
2450
|
-
bearer
|
|
2705
|
+
bearer,
|
|
2451
2706
|
teamId: identity.teamId,
|
|
2452
|
-
signer: buildRequestSigner(
|
|
2707
|
+
signer: buildRequestSigner(seat)
|
|
2453
2708
|
});
|
|
2454
2709
|
const commitHook = buildCommitHook(identity, mesh);
|
|
2455
2710
|
const auditLog = buildAuditLog();
|
|
@@ -2701,10 +2956,19 @@ async function cmdAblate(rest) {
|
|
|
2701
2956
|
}
|
|
2702
2957
|
async function cmdWhoami() {
|
|
2703
2958
|
const identity = loadIdentity();
|
|
2959
|
+
const seat = loadSeatWallet(identity.handle);
|
|
2960
|
+
let bearer = identity.x402Bearer;
|
|
2961
|
+
if (!bearer && seat) {
|
|
2962
|
+
bearer = await resolveBearerViaBroker({
|
|
2963
|
+
privateKey: seat.wallet.privateKey,
|
|
2964
|
+
meshApiBase: identity.meshApiBase
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2704
2967
|
const mesh = new HolomeshClient({
|
|
2705
2968
|
apiBase: identity.meshApiBase,
|
|
2706
|
-
bearer
|
|
2707
|
-
teamId: identity.teamId
|
|
2969
|
+
bearer,
|
|
2970
|
+
teamId: identity.teamId,
|
|
2971
|
+
signer: buildRequestSigner(seat)
|
|
2708
2972
|
});
|
|
2709
2973
|
const me = await mesh.whoAmI();
|
|
2710
2974
|
console.log(JSON.stringify({ identity: identityForLog(identity), me }, null, 2));
|
|
@@ -2763,12 +3027,12 @@ function canonicalizeSigning(value) {
|
|
|
2763
3027
|
const obj = value;
|
|
2764
3028
|
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeSigning(obj[k])}`).join(",")}}`;
|
|
2765
3029
|
}
|
|
2766
|
-
function
|
|
3030
|
+
function loadSeatWallet(handle) {
|
|
2767
3031
|
const seatsRoot = process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join4(homedir3(), ".holoscript-agent", "seats");
|
|
2768
3032
|
const fp = createHash4("sha256").update(hostname2() + homedir3()).digest("hex").slice(0, 8);
|
|
2769
|
-
const seatId = `holoscript-${handle}-${fp}-x402`;
|
|
3033
|
+
const seatId = process.env.HOLOSCRIPT_AGENT_SEAT_ID ?? `holoscript-${handle}-${fp}-x402`;
|
|
2770
3034
|
const walletPath = join4(seatsRoot, seatId, "wallet.enc");
|
|
2771
|
-
const masterKeyPath = join4(seatsRoot, ".master-key");
|
|
3035
|
+
const masterKeyPath = process.env.HOLOSCRIPT_AGENT_SEAT_MASTER_KEY ?? join4(seatsRoot, ".master-key");
|
|
2772
3036
|
if (!existsSync4(walletPath) || !existsSync4(masterKeyPath)) return void 0;
|
|
2773
3037
|
try {
|
|
2774
3038
|
const blob = JSON.parse(readFileSync5(walletPath, "utf8"));
|
|
@@ -2776,21 +3040,28 @@ function buildRequestSigner(handle) {
|
|
|
2776
3040
|
const iv = Buffer.from(blob.encrypted_privkey.iv, "base64");
|
|
2777
3041
|
const ct = Buffer.from(blob.encrypted_privkey.ct, "base64");
|
|
2778
3042
|
const tag = Buffer.from(blob.encrypted_privkey.tag, "base64");
|
|
2779
|
-
const decipher = createDecipheriv(
|
|
3043
|
+
const decipher = createDecipheriv(
|
|
3044
|
+
blob.encrypted_privkey.alg ?? "aes-256-gcm",
|
|
3045
|
+
masterKey,
|
|
3046
|
+
iv
|
|
3047
|
+
);
|
|
2780
3048
|
decipher.setAuthTag(tag);
|
|
2781
3049
|
const privateKey = Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
|
|
2782
|
-
|
|
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
|
-
};
|
|
3050
|
+
return { wallet: new Wallet3(privateKey), address: blob.address };
|
|
2790
3051
|
} catch {
|
|
2791
3052
|
return void 0;
|
|
2792
3053
|
}
|
|
2793
3054
|
}
|
|
3055
|
+
function buildRequestSigner(seat) {
|
|
3056
|
+
if (!seat) return void 0;
|
|
3057
|
+
return async (body) => {
|
|
3058
|
+
const nonce = randomBytes2(16).toString("hex");
|
|
3059
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3060
|
+
const payload = canonicalizeSigning({ body, nonce, timestamp });
|
|
3061
|
+
const signature = await seat.wallet.signMessage(payload);
|
|
3062
|
+
return { body, signature, signer_address: seat.address, nonce, timestamp };
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
2794
3065
|
function scopeTierFromEnv() {
|
|
2795
3066
|
const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
|
|
2796
3067
|
if (t === "cold" || t === "warm" || t === "hot") return t;
|
|
@@ -2838,11 +3109,17 @@ REQUIRED ENV
|
|
|
2838
3109
|
HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-8")
|
|
2839
3110
|
HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
|
|
2840
3111
|
HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
|
|
2841
|
-
HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
|
|
2842
3112
|
HOLOMESH_TEAM_ID target team id
|
|
2843
3113
|
ANTHROPIC_API_KEY | OPENAI_API_KEY | GEMINI_API_KEY per provider
|
|
2844
3114
|
|
|
2845
3115
|
OPTIONAL ENV
|
|
3116
|
+
HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer. OPTIONAL: when absent, the runner
|
|
3117
|
+
resolves it from the HoloKey broker by proving wallet
|
|
3118
|
+
ownership (POST /key/challenge \u2192 sign \u2192 /key/recover), so the
|
|
3119
|
+
bearer is never stored in plaintext .env. Requires a seat wallet.
|
|
3120
|
+
HOLOSCRIPT_AGENT_SEAT_ID override the computed seat-dir name (e.g. a sovereign x402 seat
|
|
3121
|
+
"sovereign-<surface>-<fp>-default-x402"); pairs with SEATS_ROOT
|
|
3122
|
+
HOLOSCRIPT_AGENT_SEAT_MASTER_KEY override the master-key path used to decrypt the seat wallet.enc
|
|
2846
3123
|
HOLOSCRIPT_AGENT_BUDGET_USD_DAY default 5
|
|
2847
3124
|
HOLOSCRIPT_AGENT_SCOPE_TIER cold | warm | hot (default warm)
|
|
2848
3125
|
HOLOSCRIPT_AGENT_TICK_MS daemon tick interval, default 60000
|