@holoscript/holoscript-agent 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/holoscript-agent.cjs +0 -0
- package/dist/brain.js +49 -1
- package/dist/brain.js.map +1 -1
- package/dist/identity.js +2 -2
- package/dist/identity.js.map +1 -1
- package/dist/index.js +172 -24
- package/dist/index.js.map +1 -1
- package/dist/runner.js +21 -1
- package/dist/runner.js.map +1 -1
- package/dist/supervisor.js +70 -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/bin/holoscript-agent.cjs
CHANGED
|
File without changes
|
package/dist/brain.js
CHANGED
|
@@ -13,7 +13,8 @@ async function loadBrain(brainPath, scopeTier = "warm") {
|
|
|
13
13
|
requires,
|
|
14
14
|
prefers,
|
|
15
15
|
avoids,
|
|
16
|
-
reflect: extractReflect(raw)
|
|
16
|
+
reflect: extractReflect(raw),
|
|
17
|
+
onTaskActions: extractOnTaskActions(raw)
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
function extractReflect(brain) {
|
|
@@ -48,6 +49,53 @@ function extractIdentity(brain) {
|
|
|
48
49
|
const avoids = listField(identityBlock, "avoids") ?? [];
|
|
49
50
|
return { domain, capabilityTags, requires, prefers, avoids };
|
|
50
51
|
}
|
|
52
|
+
function extractOnTaskActions(brain) {
|
|
53
|
+
const block = sliceNamedBlock(brain, "on_task");
|
|
54
|
+
if (!block) return [];
|
|
55
|
+
const VERBS = ["recall", "rag_query", "llm_call", "plan", "reflect"];
|
|
56
|
+
const entries = [];
|
|
57
|
+
for (const verb of VERBS) {
|
|
58
|
+
const re = new RegExp(`\\b${verb}\\s*\\{`, "g");
|
|
59
|
+
let m;
|
|
60
|
+
while ((m = re.exec(block)) !== null) {
|
|
61
|
+
const start = m.index + m[0].length;
|
|
62
|
+
let depth = 1;
|
|
63
|
+
let end = -1;
|
|
64
|
+
for (let i = start; i < block.length; i++) {
|
|
65
|
+
if (block[i] === "{") depth++;
|
|
66
|
+
else if (block[i] === "}") {
|
|
67
|
+
depth--;
|
|
68
|
+
if (depth === 0) {
|
|
69
|
+
end = i;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (end < 0) continue;
|
|
75
|
+
entries.push({ verb, config: parseKVBlock(block.slice(start, end)), _pos: m.index });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return entries.sort((a, b) => a._pos - b._pos).map(({ _pos: _ignored, ...rest }) => rest);
|
|
79
|
+
}
|
|
80
|
+
function parseKVBlock(block) {
|
|
81
|
+
const out = {};
|
|
82
|
+
const strRe = /\b(\w+)\s*:\s*"([^"]*)"/g;
|
|
83
|
+
let m;
|
|
84
|
+
while ((m = strRe.exec(block)) !== null) out[m[1]] = m[2];
|
|
85
|
+
const arrRe = /\b(\w+)\s*:\s*\[([^\]]*)\]/g;
|
|
86
|
+
while ((m = arrRe.exec(block)) !== null) {
|
|
87
|
+
out[m[1]] = m[2].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter((s) => s.length > 0);
|
|
88
|
+
}
|
|
89
|
+
const boolRe = /\b(\w+)\s*:\s*(true|false)\b/g;
|
|
90
|
+
while ((m = boolRe.exec(block)) !== null) {
|
|
91
|
+
if (!(m[1] in out)) out[m[1]] = m[2] === "true";
|
|
92
|
+
}
|
|
93
|
+
const numRe = /\b(\w+)\s*:\s*(-?\d+(?:\.\d+)?)\b/g;
|
|
94
|
+
while ((m = numRe.exec(block)) !== null) {
|
|
95
|
+
if (!(m[1] in out)) out[m[1]] = parseFloat(m[2]);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
51
99
|
function sliceNamedBlock(src, name) {
|
|
52
100
|
const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
|
|
53
101
|
const match = re.exec(src);
|
package/dist/brain.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/brain.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { RuntimeBrainConfig } from './types.js';\n\nexport async function loadBrain(\n brainPath: string,\n scopeTier: 'cold' | 'warm' | 'hot' = 'warm'\n): Promise<RuntimeBrainConfig> {\n const raw = await readFile(brainPath, 'utf8');\n const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);\n // For .hsplus brains: the file begins with a free-text instruction block\n // (the actual system prompt for the LLM) followed by HoloScript structured\n // sections (#version, #target, identity {}, state {}, etc.). Sending the\n // full file bloats the context by ~1500+ tokens of metadata the LLM does\n // not need and — on constrained-context local models (qwen3:4b, num_ctx=2048)\n // — causes the CRITICAL tool-calling rules to be truncated before the model\n // sees them, resulting in plain-text replies with no tool calls.\n // Extract only the preamble: everything before the first HoloScript directive.\n const systemPrompt = extractSystemPromptPreamble(raw);\n return {\n brainPath,\n systemPrompt,\n capabilityTags,\n domain,\n scopeTier,\n requires,\n prefers,\n avoids,\n reflect: extractReflect(raw),\n };\n}\n\n/**\n * Extract the brain's `reflect` cognitive verb (W.736) if it declares one, e.g.\n * reflect { of: \"the produced artifact\", criteria: \"valid HoloScript\", escalate_on_fail: true }\n * Returns the evaluation criteria + whether a failed self-evaluation escalates to\n * the fleet (the `local_first` directive). Absent → undefined (no reflect gate).\n * Uses sliceNamedBlock so both `reflect {` and `reflect: {` forms parse, mirroring\n * identity. This is the one cognitive verb the lightweight runner can execute with\n * just its LLM provider (no engine/trait runtime) — recall/rag_query/plan need\n * trait-backed stores and run in the core/engine path, not here.\n */\nfunction extractReflect(brain: string): { criteria: string; escalateOnFail: boolean } | undefined {\n const block = sliceNamedBlock(brain, 'reflect');\n if (block === undefined) return undefined;\n const criteria =\n scalarField(block, 'criteria') ??\n scalarField(block, 'scorer') ??\n scalarField(block, 'of') ??\n 'correctness, completeness, and valid HoloScript syntax';\n const escRaw =\n scalarField(block, 'escalate_on_fail') ??\n scalarField(block, 'escalateOnFail') ??\n scalarField(block, 'escalate');\n // escRaw may be `true` or `true, nextField...` (unquoted scalar runs to the\n // segment end) — take the first comma-delimited token before comparing.\n return { criteria, escalateOnFail: (escRaw ?? '').split(',')[0].trim().toLowerCase() === 'true' };\n}\n\n/**\n * Extract the free-text instruction preamble from a .hsplus brain file.\n * Stops at the first line that begins a HoloScript structured section:\n * `#version`, `#target`, `#mode`, or a block keyword (`identity {`,\n * `state {`, `computed {`, `traits [`, `capabilities {`, `directives {`,\n * `behavior `). Falls back to the full file content for plain-text brains\n * (no HoloScript sections detected).\n */\nfunction extractSystemPromptPreamble(src: string): string {\n const lines = src.split('\\n');\n const BLOCK_START = /^(#version|#target|#mode|identity\\s*\\{|state\\s*\\{|computed\\s*\\{|traits\\s*\\[|capabilities\\s*\\{|directives\\s*\\{|behavior\\s)/;\n let cutLine = -1;\n for (let i = 0; i < lines.length; i++) {\n if (BLOCK_START.test(lines[i].trim())) {\n cutLine = i;\n break;\n }\n }\n if (cutLine <= 0) return src; // no HoloScript sections — whole file is prompt\n return lines.slice(0, cutLine).join('\\n').trimEnd();\n}\n\nfunction extractIdentity(brain: string): {\n domain: string;\n capabilityTags: string[];\n requires: string[];\n prefers: string[];\n avoids: string[];\n} {\n const identityBlock = sliceNamedBlock(brain, 'identity');\n if (!identityBlock) {\n // No identity block — open routing (backward-compatible default).\n // Brains without explicit requires/prefers/avoids match any provider.\n return { domain: 'unknown', capabilityTags: [], requires: [], prefers: [], avoids: [] };\n }\n const domain = scalarField(identityBlock, 'domain') ?? 'unknown';\n const capabilityTags = listField(identityBlock, 'capability_tags') ?? [];\n // Universal+segregated routing fields (founder ruling 2026-05-06): brains\n // declare capability requirements as data; router matches against the\n // provider's `capabilities` manifest at session start. Empty (omitted) =\n // open routing — preserves today's behavior for unmigrated brains.\n const requires = listField(identityBlock, 'requires') ?? [];\n const prefers = listField(identityBlock, 'prefers') ?? [];\n const avoids = listField(identityBlock, 'avoids') ?? [];\n return { domain, capabilityTags, requires, prefers, avoids };\n}\n\nfunction sliceNamedBlock(src: string, name: string): string | undefined {\n // Accept both `identity {` and `identity: {` — brain compositions in\n // .ai-ecosystem use both forms (lean-theorist + antigravity-hot use the\n // colon variant; security-auditor + others use the bare form). Without\n // both-form tolerance the colon-form brains parse to empty\n // capability_tags, breaking task scoring entirely (silent claim-blackhole\n // observed 2026-04-25 on W01 H200 lean-theorist).\n const re = new RegExp(`\\\\b${name}\\\\s*:?\\\\s*\\\\{`, 'g');\n const match = re.exec(src);\n if (!match) return undefined;\n const headerEnd = match.index + match[0].length; // position just past the `{`\n let depth = 1;\n for (let i = headerEnd; i < src.length; i++) {\n const ch = src[i];\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return src.slice(headerEnd, i);\n }\n }\n return undefined;\n}\n\nfunction scalarField(block: string, key: string): string | undefined {\n const idx = block.indexOf(`${key}:`);\n if (idx < 0) return undefined;\n const after = block.slice(idx + key.length + 1).trimStart();\n if (after.startsWith('\"')) {\n const end = after.indexOf('\"', 1);\n if (end > 0) return after.slice(1, end);\n }\n const eol = after.indexOf('\\n');\n return after.slice(0, eol < 0 ? undefined : eol).trim();\n}\n\nfunction listField(block: string, key: string): string[] | undefined {\n const idx = block.indexOf(`${key}:`);\n if (idx < 0) return undefined;\n const after = block.slice(idx + key.length + 1).trimStart();\n if (!after.startsWith('[')) return undefined;\n let depth = 0;\n let end = -1;\n for (let i = 0; i < after.length; i++) {\n if (after[i] === '[') depth++;\n else if (after[i] === ']') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end < 0) return undefined;\n const inner = after.slice(1, end);\n return inner\n .split(',')\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter((s) => s.length > 0);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/brain.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { OnTaskAction, RuntimeBrainConfig } from './types.js';\n\nexport async function loadBrain(\n brainPath: string,\n scopeTier: 'cold' | 'warm' | 'hot' = 'warm'\n): Promise<RuntimeBrainConfig> {\n const raw = await readFile(brainPath, 'utf8');\n const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);\n // For .hsplus brains: the file begins with a free-text instruction block\n // (the actual system prompt for the LLM) followed by HoloScript structured\n // sections (#version, #target, identity {}, state {}, etc.). Sending the\n // full file bloats the context by ~1500+ tokens of metadata the LLM does\n // not need and — on constrained-context local models (qwen3:4b, num_ctx=2048)\n // — causes the CRITICAL tool-calling rules to be truncated before the model\n // sees them, resulting in plain-text replies with no tool calls.\n // Extract only the preamble: everything before the first HoloScript directive.\n const systemPrompt = extractSystemPromptPreamble(raw);\n return {\n brainPath,\n systemPrompt,\n capabilityTags,\n domain,\n scopeTier,\n requires,\n prefers,\n avoids,\n reflect: extractReflect(raw),\n onTaskActions: extractOnTaskActions(raw),\n };\n}\n\n/**\n * Extract the brain's `reflect` cognitive verb (W.736) if it declares one, e.g.\n * reflect { of: \"the produced artifact\", criteria: \"valid HoloScript\", escalate_on_fail: true }\n * Returns the evaluation criteria + whether a failed self-evaluation escalates to\n * the fleet (the `local_first` directive). Absent → undefined (no reflect gate).\n * Uses sliceNamedBlock so both `reflect {` and `reflect: {` forms parse, mirroring\n * identity. This is the one cognitive verb the lightweight runner can execute with\n * just its LLM provider (no engine/trait runtime) — recall/rag_query/plan need\n * trait-backed stores and run in the core/engine path, not here.\n */\nfunction extractReflect(brain: string): { criteria: string; escalateOnFail: boolean } | undefined {\n const block = sliceNamedBlock(brain, 'reflect');\n if (block === undefined) return undefined;\n const criteria =\n scalarField(block, 'criteria') ??\n scalarField(block, 'scorer') ??\n scalarField(block, 'of') ??\n 'correctness, completeness, and valid HoloScript syntax';\n const escRaw =\n scalarField(block, 'escalate_on_fail') ??\n scalarField(block, 'escalateOnFail') ??\n scalarField(block, 'escalate');\n // escRaw may be `true` or `true, nextField...` (unquoted scalar runs to the\n // segment end) — take the first comma-delimited token before comparing.\n return { criteria, escalateOnFail: (escRaw ?? '').split(',')[0].trim().toLowerCase() === 'true' };\n}\n\n/**\n * Extract the free-text instruction preamble from a .hsplus brain file.\n * Stops at the first line that begins a HoloScript structured section:\n * `#version`, `#target`, `#mode`, or a block keyword (`identity {`,\n * `state {`, `computed {`, `traits [`, `capabilities {`, `directives {`,\n * `behavior `). Falls back to the full file content for plain-text brains\n * (no HoloScript sections detected).\n */\nfunction extractSystemPromptPreamble(src: string): string {\n const lines = src.split('\\n');\n const BLOCK_START = /^(#version|#target|#mode|identity\\s*\\{|state\\s*\\{|computed\\s*\\{|traits\\s*\\[|capabilities\\s*\\{|directives\\s*\\{|behavior\\s)/;\n let cutLine = -1;\n for (let i = 0; i < lines.length; i++) {\n if (BLOCK_START.test(lines[i].trim())) {\n cutLine = i;\n break;\n }\n }\n if (cutLine <= 0) return src; // no HoloScript sections — whole file is prompt\n return lines.slice(0, cutLine).join('\\n').trimEnd();\n}\n\nfunction extractIdentity(brain: string): {\n domain: string;\n capabilityTags: string[];\n requires: string[];\n prefers: string[];\n avoids: string[];\n} {\n const identityBlock = sliceNamedBlock(brain, 'identity');\n if (!identityBlock) {\n // No identity block — open routing (backward-compatible default).\n // Brains without explicit requires/prefers/avoids match any provider.\n return { domain: 'unknown', capabilityTags: [], requires: [], prefers: [], avoids: [] };\n }\n const domain = scalarField(identityBlock, 'domain') ?? 'unknown';\n const capabilityTags = listField(identityBlock, 'capability_tags') ?? [];\n // Universal+segregated routing fields (founder ruling 2026-05-06): brains\n // declare capability requirements as data; router matches against the\n // provider's `capabilities` manifest at session start. Empty (omitted) =\n // open routing — preserves today's behavior for unmigrated brains.\n const requires = listField(identityBlock, 'requires') ?? [];\n const prefers = listField(identityBlock, 'prefers') ?? [];\n const avoids = listField(identityBlock, 'avoids') ?? [];\n return { domain, capabilityTags, requires, prefers, avoids };\n}\n\n/**\n * Parse the `behavior on_task { … }` block into an ordered sequence of\n * cognitive verb calls (Phase 2.1). Each verb's config is extracted with a\n * lightweight regex KV parser — no full parser dependency. Only verbs whose\n * keys match known cognitive verbs are included; unknown keywords are skipped.\n *\n * Currently wired in AgentRunner: `llm_call` (prompt augmentation) and\n * `reflect` (extracted separately by extractReflect via sliceNamedBlock).\n * Future verbs (`recall`, `rag_query`, `plan`) are parsed so they appear in\n * the returned sequence but are logged-and-deferred by the runner until\n * Phase 2.2 (trait-backed stores, see idea-seeds.md).\n */\nfunction extractOnTaskActions(brain: string): OnTaskAction[] {\n // `sliceNamedBlock` with 'on_task' matches `on_task {` inside `behavior on_task {`\n const block = sliceNamedBlock(brain, 'on_task');\n if (!block) return [];\n\n const VERBS: OnTaskAction['verb'][] = ['recall', 'rag_query', 'llm_call', 'plan', 'reflect'];\n const entries: Array<OnTaskAction & { _pos: number }> = [];\n\n for (const verb of VERBS) {\n const re = new RegExp(`\\\\b${verb}\\\\s*\\\\{`, 'g');\n let m: RegExpExecArray | null;\n while ((m = re.exec(block)) !== null) {\n const start = m.index + m[0].length;\n let depth = 1;\n let end = -1;\n for (let i = start; i < block.length; i++) {\n if (block[i] === '{') depth++;\n else if (block[i] === '}') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end < 0) continue;\n entries.push({ verb, config: parseKVBlock(block.slice(start, end)), _pos: m.index });\n }\n }\n\n // Sort by authored position so verbs execute in the order the brain declared them.\n return entries.sort((a, b) => a._pos - b._pos).map(({ _pos: _ignored, ...rest }) => rest);\n}\n\n/** Lightweight key-value extractor for cognitive verb config blocks. */\nfunction parseKVBlock(block: string): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n // String: key: \"value\"\n const strRe = /\\b(\\w+)\\s*:\\s*\"([^\"]*)\"/g;\n let m: RegExpExecArray | null;\n while ((m = strRe.exec(block)) !== null) out[m[1]] = m[2];\n // Array: key: [\"a\", \"b\"] — must run before bool/num to claim the array form of limit etc.\n const arrRe = /\\b(\\w+)\\s*:\\s*\\[([^\\]]*)\\]/g;\n while ((m = arrRe.exec(block)) !== null) {\n out[m[1]] = m[2]\n .split(',')\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter((s) => s.length > 0);\n }\n // Boolean: key: true | false (only when not already set by string/array)\n const boolRe = /\\b(\\w+)\\s*:\\s*(true|false)\\b/g;\n while ((m = boolRe.exec(block)) !== null) {\n if (!(m[1] in out)) out[m[1]] = m[2] === 'true';\n }\n // Number: key: 123 or key: -0.5 (only when not already set)\n const numRe = /\\b(\\w+)\\s*:\\s*(-?\\d+(?:\\.\\d+)?)\\b/g;\n while ((m = numRe.exec(block)) !== null) {\n if (!(m[1] in out)) out[m[1]] = parseFloat(m[2]);\n }\n return out;\n}\n\nfunction sliceNamedBlock(src: string, name: string): string | undefined {\n // Accept both `identity {` and `identity: {` — brain compositions in\n // .ai-ecosystem use both forms (lean-theorist + antigravity-hot use the\n // colon variant; security-auditor + others use the bare form). Without\n // both-form tolerance the colon-form brains parse to empty\n // capability_tags, breaking task scoring entirely (silent claim-blackhole\n // observed 2026-04-25 on W01 H200 lean-theorist).\n const re = new RegExp(`\\\\b${name}\\\\s*:?\\\\s*\\\\{`, 'g');\n const match = re.exec(src);\n if (!match) return undefined;\n const headerEnd = match.index + match[0].length; // position just past the `{`\n let depth = 1;\n for (let i = headerEnd; i < src.length; i++) {\n const ch = src[i];\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return src.slice(headerEnd, i);\n }\n }\n return undefined;\n}\n\nfunction scalarField(block: string, key: string): string | undefined {\n const idx = block.indexOf(`${key}:`);\n if (idx < 0) return undefined;\n const after = block.slice(idx + key.length + 1).trimStart();\n if (after.startsWith('\"')) {\n const end = after.indexOf('\"', 1);\n if (end > 0) return after.slice(1, end);\n }\n const eol = after.indexOf('\\n');\n return after.slice(0, eol < 0 ? undefined : eol).trim();\n}\n\nfunction listField(block: string, key: string): string[] | undefined {\n const idx = block.indexOf(`${key}:`);\n if (idx < 0) return undefined;\n const after = block.slice(idx + key.length + 1).trimStart();\n if (!after.startsWith('[')) return undefined;\n let depth = 0;\n let end = -1;\n for (let i = 0; i < after.length; i++) {\n if (after[i] === '[') depth++;\n else if (after[i] === ']') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n if (end < 0) return undefined;\n const inner = after.slice(1, end);\n return inner\n .split(',')\n .map((s) => s.trim().replace(/^[\"']|[\"']$/g, ''))\n .filter((s) => s.length > 0);\n}\n"],"mappings":"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","names":[]}
|
package/dist/identity.js
CHANGED
|
@@ -17,7 +17,7 @@ function loadIdentity(env = process.env) {
|
|
|
17
17
|
`HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(", ")}]`
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
|
-
const x402Bearer =
|
|
20
|
+
const x402Bearer = (env.HOLOSCRIPT_AGENT_X402_BEARER ?? "").trim();
|
|
21
21
|
const wallet = required(env, "HOLOSCRIPT_AGENT_WALLET");
|
|
22
22
|
if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
|
|
23
23
|
throw new Error(`HOLOSCRIPT_AGENT_WALLET is not a 0x-prefixed 40-hex address: ${wallet}`);
|
|
@@ -54,7 +54,7 @@ function identityForLog(id) {
|
|
|
54
54
|
handle: id.handle,
|
|
55
55
|
surface: id.surface,
|
|
56
56
|
wallet: `${id.wallet.slice(0, 6)}\u2026${id.wallet.slice(-4)}`,
|
|
57
|
-
bearer: `${id.x402Bearer.slice(0, 6)}\u2026
|
|
57
|
+
bearer: id.x402Bearer ? `${id.x402Bearer.slice(0, 6)}\u2026` : "(broker-resolved)",
|
|
58
58
|
provider: id.llmProvider,
|
|
59
59
|
model: id.llmModel,
|
|
60
60
|
brain: id.brainPath,
|
package/dist/identity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/identity.ts"],"sourcesContent":["import type { LLMProviderName } from '@holoscript/llm-provider';\nimport type { AgentIdentity } from './types.js';\n\nconst VALID_PROVIDERS: ReadonlySet<LLMProviderName> = new Set([\n 'anthropic',\n 'openai',\n 'gemini',\n 'xai',\n 'openrouter',\n 'mock',\n 'bitnet',\n 'local-llm',\n]);\n\nexport function loadIdentity(env: NodeJS.ProcessEnv = process.env): AgentIdentity {\n const handle = required(env, 'HOLOSCRIPT_AGENT_HANDLE');\n const provider = required(env, 'HOLOSCRIPT_AGENT_PROVIDER');\n if (!VALID_PROVIDERS.has(provider as LLMProviderName)) {\n throw new Error(\n `HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(', ')}]`\n );\n }\n const x402Bearer =
|
|
1
|
+
{"version":3,"sources":["../src/identity.ts"],"sourcesContent":["import type { LLMProviderName } from '@holoscript/llm-provider';\nimport type { AgentIdentity } from './types.js';\n\nconst VALID_PROVIDERS: ReadonlySet<LLMProviderName> = new Set([\n 'anthropic',\n 'openai',\n 'gemini',\n 'xai',\n 'openrouter',\n 'mock',\n 'bitnet',\n 'local-llm',\n]);\n\nexport function loadIdentity(env: NodeJS.ProcessEnv = process.env): AgentIdentity {\n const handle = required(env, 'HOLOSCRIPT_AGENT_HANDLE');\n const provider = required(env, 'HOLOSCRIPT_AGENT_PROVIDER');\n if (!VALID_PROVIDERS.has(provider as LLMProviderName)) {\n throw new Error(\n `HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(', ')}]`\n );\n }\n // Bearer is OPTIONAL: when absent, the runner resolves it from the HoloKey\n // broker by proving wallet ownership (bearer-broker.ts) — the edge holds only\n // its wallet, not a plaintext bearer in .env. cmdRun fails clearly if neither\n // an env bearer nor a resolvable seat wallet is available.\n const x402Bearer = (env.HOLOSCRIPT_AGENT_X402_BEARER ?? '').trim();\n const wallet = required(env, 'HOLOSCRIPT_AGENT_WALLET');\n if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {\n throw new Error(`HOLOSCRIPT_AGENT_WALLET is not a 0x-prefixed 40-hex address: ${wallet}`);\n }\n const budgetRaw = env.HOLOSCRIPT_AGENT_BUDGET_USD_DAY ?? '5';\n const budget = Number(budgetRaw);\n if (!Number.isFinite(budget) || budget < 0) {\n throw new Error(\n `HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`\n );\n }\n\n return {\n handle,\n surface: env.HOLOSCRIPT_AGENT_SURFACE ?? handle,\n wallet,\n x402Bearer,\n llmProvider: provider as LLMProviderName,\n llmModel: required(env, 'HOLOSCRIPT_AGENT_MODEL'),\n brainPath: required(env, 'HOLOSCRIPT_AGENT_BRAIN'),\n budgetUsdPerDay: budget,\n teamId: required(env, 'HOLOMESH_TEAM_ID'),\n meshApiBase: env.HOLOMESH_API_BASE ?? 'https://mcp.holoscript.net/api/holomesh',\n };\n}\n\nfunction required(env: NodeJS.ProcessEnv, key: string): string {\n const v = env[key];\n if (!v || v.trim().length === 0) {\n throw new Error(`Missing required env var: ${key}`);\n }\n return v.trim();\n}\n\nexport function identityForLog(id: AgentIdentity): Record<string, string | number> {\n return {\n handle: id.handle,\n surface: id.surface,\n wallet: `${id.wallet.slice(0, 6)}…${id.wallet.slice(-4)}`,\n bearer: id.x402Bearer ? `${id.x402Bearer.slice(0, 6)}…` : '(broker-resolved)',\n provider: id.llmProvider,\n model: id.llmModel,\n brain: id.brainPath,\n budgetUsdPerDay: id.budgetUsdPerDay,\n };\n}\n"],"mappings":";AAGA,IAAM,kBAAgD,oBAAI,IAAI;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,aAAa,MAAyB,QAAQ,KAAoB;AAChF,QAAM,SAAS,SAAS,KAAK,yBAAyB;AACtD,QAAM,WAAW,SAAS,KAAK,2BAA2B;AAC1D,MAAI,CAAC,gBAAgB,IAAI,QAA2B,GAAG;AACrD,UAAM,IAAI;AAAA,MACR,6BAA6B,QAAQ,YAAY,CAAC,GAAG,eAAe,EAAE,KAAK,IAAI,CAAC;AAAA,IAClF;AAAA,EACF;AAKA,QAAM,cAAc,IAAI,gCAAgC,IAAI,KAAK;AACjE,QAAM,SAAS,SAAS,KAAK,yBAAyB;AACtD,MAAI,CAAC,sBAAsB,KAAK,MAAM,GAAG;AACvC,UAAM,IAAI,MAAM,gEAAgE,MAAM,EAAE;AAAA,EAC1F;AACA,QAAM,YAAY,IAAI,mCAAmC;AACzD,QAAM,SAAS,OAAO,SAAS;AAC/B,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,SAAS,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,qEAAqE,SAAS;AAAA,IAChF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,IAAI,4BAA4B;AAAA,IACzC;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,UAAU,SAAS,KAAK,wBAAwB;AAAA,IAChD,WAAW,SAAS,KAAK,wBAAwB;AAAA,IACjD,iBAAiB;AAAA,IACjB,QAAQ,SAAS,KAAK,kBAAkB;AAAA,IACxC,aAAa,IAAI,qBAAqB;AAAA,EACxC;AACF;AAEA,SAAS,SAAS,KAAwB,KAAqB;AAC7D,QAAM,IAAI,IAAI,GAAG;AACjB,MAAI,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG;AAC/B,UAAM,IAAI,MAAM,6BAA6B,GAAG,EAAE;AAAA,EACpD;AACA,SAAO,EAAE,KAAK;AAChB;AAEO,SAAS,eAAe,IAAoD;AACjF,SAAO;AAAA,IACL,QAAQ,GAAG;AAAA,IACX,SAAS,GAAG;AAAA,IACZ,QAAQ,GAAG,GAAG,OAAO,MAAM,GAAG,CAAC,CAAC,SAAI,GAAG,OAAO,MAAM,EAAE,CAAC;AAAA,IACvD,QAAQ,GAAG,aAAa,GAAG,GAAG,WAAW,MAAM,GAAG,CAAC,CAAC,WAAM;AAAA,IAC1D,UAAU,GAAG;AAAA,IACb,OAAO,GAAG;AAAA,IACV,OAAO,GAAG;AAAA,IACV,iBAAiB,GAAG;AAAA,EACtB;AACF;","names":[]}
|
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);
|
|
@@ -619,6 +667,44 @@ function priority(t) {
|
|
|
619
667
|
return map[String(t.priority).toLowerCase()] ?? 5;
|
|
620
668
|
}
|
|
621
669
|
|
|
670
|
+
// src/bearer-broker.ts
|
|
671
|
+
import { Wallet } from "ethers";
|
|
672
|
+
var RECOVERY_DOMAIN = { name: "HoloMesh", version: "1" };
|
|
673
|
+
var RECOVERY_TYPES = {
|
|
674
|
+
Recovery: [{ name: "nonce", type: "string" }]
|
|
675
|
+
};
|
|
676
|
+
async function resolveBearerViaBroker(opts) {
|
|
677
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
678
|
+
const wallet = new Wallet(opts.privateKey);
|
|
679
|
+
const address = wallet.address;
|
|
680
|
+
const base = opts.meshApiBase.replace(/\/+$/, "");
|
|
681
|
+
const chalRes = await doFetch(`${base}/key/challenge`, {
|
|
682
|
+
method: "POST",
|
|
683
|
+
headers: { "Content-Type": "application/json" },
|
|
684
|
+
body: JSON.stringify({ wallet_address: address })
|
|
685
|
+
});
|
|
686
|
+
if (!chalRes.ok) {
|
|
687
|
+
throw new Error(`bearer-broker: /key/challenge returned ${chalRes.status} for ${address}`);
|
|
688
|
+
}
|
|
689
|
+
const chal = await chalRes.json();
|
|
690
|
+
if (!chal.nonce) throw new Error("bearer-broker: /key/challenge returned no nonce");
|
|
691
|
+
const signature = await wallet.signTypedData(RECOVERY_DOMAIN, RECOVERY_TYPES, {
|
|
692
|
+
nonce: chal.nonce
|
|
693
|
+
});
|
|
694
|
+
const recRes = await doFetch(`${base}/key/recover`, {
|
|
695
|
+
method: "POST",
|
|
696
|
+
headers: { "Content-Type": "application/json" },
|
|
697
|
+
body: JSON.stringify({ wallet_address: address, nonce: chal.nonce, signature })
|
|
698
|
+
});
|
|
699
|
+
if (!recRes.ok) {
|
|
700
|
+
throw new Error(`bearer-broker: /key/recover returned ${recRes.status} for ${address}`);
|
|
701
|
+
}
|
|
702
|
+
const rec = await recRes.json();
|
|
703
|
+
const bearer = rec.agent?.api_key;
|
|
704
|
+
if (!bearer) throw new Error("bearer-broker: /key/recover returned no api_key");
|
|
705
|
+
return bearer;
|
|
706
|
+
}
|
|
707
|
+
|
|
622
708
|
// src/cael-builder.ts
|
|
623
709
|
import { createHash } from "crypto";
|
|
624
710
|
function sha(input) {
|
|
@@ -1112,8 +1198,28 @@ var AgentRunner = class {
|
|
|
1112
1198
|
log({ ev: "claim", taskId: target.id, title: target.title });
|
|
1113
1199
|
await mesh.claim(target.id);
|
|
1114
1200
|
const start = Date.now();
|
|
1201
|
+
let systemContent = brain.systemPrompt;
|
|
1202
|
+
if (brain.onTaskActions && brain.onTaskActions.length > 0) {
|
|
1203
|
+
const llmCallAction = brain.onTaskActions.find((a) => a.verb === "llm_call");
|
|
1204
|
+
const deferredVerbs = brain.onTaskActions.filter((a) => a.verb === "recall" || a.verb === "rag_query" || a.verb === "plan").map((a) => a.verb);
|
|
1205
|
+
if (deferredVerbs.length > 0) {
|
|
1206
|
+
log({
|
|
1207
|
+
ev: "on-task-deferred",
|
|
1208
|
+
taskId: target.id,
|
|
1209
|
+
verbs: deferredVerbs,
|
|
1210
|
+
note: "trait-backed dispatch deferred to Phase 2.2 (idea-seeds.md)"
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
if (llmCallAction && typeof llmCallAction.config.prompt === "string" && llmCallAction.config.prompt.length > 0) {
|
|
1214
|
+
systemContent = `${brain.systemPrompt}
|
|
1215
|
+
|
|
1216
|
+
[Brain on_task directive]
|
|
1217
|
+
${llmCallAction.config.prompt}`;
|
|
1218
|
+
log({ ev: "on-task-llm-call", taskId: target.id, promptLen: llmCallAction.config.prompt.length });
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1115
1221
|
const messages = [
|
|
1116
|
-
{ role: "system", content:
|
|
1222
|
+
{ role: "system", content: systemContent },
|
|
1117
1223
|
{ role: "user", content: buildTaskPrompt(target) }
|
|
1118
1224
|
];
|
|
1119
1225
|
let aggUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
@@ -2173,7 +2279,7 @@ import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync a
|
|
|
2173
2279
|
import { join as join3 } from "path";
|
|
2174
2280
|
import { homedir as homedir2, hostname } from "os";
|
|
2175
2281
|
import { randomBytes, createCipheriv, createHash as createHash3 } from "crypto";
|
|
2176
|
-
import { Wallet } from "ethers";
|
|
2282
|
+
import { Wallet as Wallet2 } from "ethers";
|
|
2177
2283
|
var HANDLE_PATTERN2 = /^[a-z0-9_-]{1,64}$/i;
|
|
2178
2284
|
var EIP712_DOMAIN = { name: "HoloMesh", version: "1" };
|
|
2179
2285
|
var EIP712_TYPES = {
|
|
@@ -2222,7 +2328,7 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
2222
2328
|
};
|
|
2223
2329
|
return reused;
|
|
2224
2330
|
}
|
|
2225
|
-
const wallet =
|
|
2331
|
+
const wallet = Wallet2.createRandom();
|
|
2226
2332
|
mkdirSync4(seatDir, { recursive: true });
|
|
2227
2333
|
const masterKey = ensureMasterKey(seatsRoot);
|
|
2228
2334
|
const encryptedBlob = {
|
|
@@ -2445,11 +2551,31 @@ async function cmdRun(opts) {
|
|
|
2445
2551
|
dailyBudgetUsd: identity.budgetUsdPerDay,
|
|
2446
2552
|
pricer: defaultPricerForProvider(effectiveIdentity.llmProvider)
|
|
2447
2553
|
});
|
|
2554
|
+
const seat = loadSeatWallet(identity.handle);
|
|
2555
|
+
let bearer = identity.x402Bearer;
|
|
2556
|
+
if (!bearer) {
|
|
2557
|
+
if (!seat) {
|
|
2558
|
+
throw new Error(
|
|
2559
|
+
"No HOLOSCRIPT_AGENT_X402_BEARER set and no seat wallet found to resolve it from the HoloKey broker. Provide a bearer, or point at the seat wallet via HOLOSCRIPT_AGENT_SEATS_ROOT + HOLOSCRIPT_AGENT_SEAT_ID (+ HOLOSCRIPT_AGENT_SEAT_MASTER_KEY)."
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
bearer = await resolveBearerViaBroker({
|
|
2563
|
+
privateKey: seat.wallet.privateKey,
|
|
2564
|
+
meshApiBase: identity.meshApiBase
|
|
2565
|
+
});
|
|
2566
|
+
console.log(
|
|
2567
|
+
JSON.stringify({
|
|
2568
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2569
|
+
ev: "bearer-resolved-via-broker",
|
|
2570
|
+
wallet: `${seat.address.slice(0, 6)}\u2026${seat.address.slice(-4)}`
|
|
2571
|
+
})
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
2448
2574
|
const mesh = new HolomeshClient({
|
|
2449
2575
|
apiBase: identity.meshApiBase,
|
|
2450
|
-
bearer
|
|
2576
|
+
bearer,
|
|
2451
2577
|
teamId: identity.teamId,
|
|
2452
|
-
signer: buildRequestSigner(
|
|
2578
|
+
signer: buildRequestSigner(seat)
|
|
2453
2579
|
});
|
|
2454
2580
|
const commitHook = buildCommitHook(identity, mesh);
|
|
2455
2581
|
const auditLog = buildAuditLog();
|
|
@@ -2701,10 +2827,19 @@ async function cmdAblate(rest) {
|
|
|
2701
2827
|
}
|
|
2702
2828
|
async function cmdWhoami() {
|
|
2703
2829
|
const identity = loadIdentity();
|
|
2830
|
+
const seat = loadSeatWallet(identity.handle);
|
|
2831
|
+
let bearer = identity.x402Bearer;
|
|
2832
|
+
if (!bearer && seat) {
|
|
2833
|
+
bearer = await resolveBearerViaBroker({
|
|
2834
|
+
privateKey: seat.wallet.privateKey,
|
|
2835
|
+
meshApiBase: identity.meshApiBase
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2704
2838
|
const mesh = new HolomeshClient({
|
|
2705
2839
|
apiBase: identity.meshApiBase,
|
|
2706
|
-
bearer
|
|
2707
|
-
teamId: identity.teamId
|
|
2840
|
+
bearer,
|
|
2841
|
+
teamId: identity.teamId,
|
|
2842
|
+
signer: buildRequestSigner(seat)
|
|
2708
2843
|
});
|
|
2709
2844
|
const me = await mesh.whoAmI();
|
|
2710
2845
|
console.log(JSON.stringify({ identity: identityForLog(identity), me }, null, 2));
|
|
@@ -2763,12 +2898,12 @@ function canonicalizeSigning(value) {
|
|
|
2763
2898
|
const obj = value;
|
|
2764
2899
|
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${canonicalizeSigning(obj[k])}`).join(",")}}`;
|
|
2765
2900
|
}
|
|
2766
|
-
function
|
|
2901
|
+
function loadSeatWallet(handle) {
|
|
2767
2902
|
const seatsRoot = process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join4(homedir3(), ".holoscript-agent", "seats");
|
|
2768
2903
|
const fp = createHash4("sha256").update(hostname2() + homedir3()).digest("hex").slice(0, 8);
|
|
2769
|
-
const seatId = `holoscript-${handle}-${fp}-x402`;
|
|
2904
|
+
const seatId = process.env.HOLOSCRIPT_AGENT_SEAT_ID ?? `holoscript-${handle}-${fp}-x402`;
|
|
2770
2905
|
const walletPath = join4(seatsRoot, seatId, "wallet.enc");
|
|
2771
|
-
const masterKeyPath = join4(seatsRoot, ".master-key");
|
|
2906
|
+
const masterKeyPath = process.env.HOLOSCRIPT_AGENT_SEAT_MASTER_KEY ?? join4(seatsRoot, ".master-key");
|
|
2772
2907
|
if (!existsSync4(walletPath) || !existsSync4(masterKeyPath)) return void 0;
|
|
2773
2908
|
try {
|
|
2774
2909
|
const blob = JSON.parse(readFileSync5(walletPath, "utf8"));
|
|
@@ -2776,21 +2911,28 @@ function buildRequestSigner(handle) {
|
|
|
2776
2911
|
const iv = Buffer.from(blob.encrypted_privkey.iv, "base64");
|
|
2777
2912
|
const ct = Buffer.from(blob.encrypted_privkey.ct, "base64");
|
|
2778
2913
|
const tag = Buffer.from(blob.encrypted_privkey.tag, "base64");
|
|
2779
|
-
const decipher = createDecipheriv(
|
|
2914
|
+
const decipher = createDecipheriv(
|
|
2915
|
+
blob.encrypted_privkey.alg ?? "aes-256-gcm",
|
|
2916
|
+
masterKey,
|
|
2917
|
+
iv
|
|
2918
|
+
);
|
|
2780
2919
|
decipher.setAuthTag(tag);
|
|
2781
2920
|
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
|
-
};
|
|
2921
|
+
return { wallet: new Wallet3(privateKey), address: blob.address };
|
|
2790
2922
|
} catch {
|
|
2791
2923
|
return void 0;
|
|
2792
2924
|
}
|
|
2793
2925
|
}
|
|
2926
|
+
function buildRequestSigner(seat) {
|
|
2927
|
+
if (!seat) return void 0;
|
|
2928
|
+
return async (body) => {
|
|
2929
|
+
const nonce = randomBytes2(16).toString("hex");
|
|
2930
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2931
|
+
const payload = canonicalizeSigning({ body, nonce, timestamp });
|
|
2932
|
+
const signature = await seat.wallet.signMessage(payload);
|
|
2933
|
+
return { body, signature, signer_address: seat.address, nonce, timestamp };
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2794
2936
|
function scopeTierFromEnv() {
|
|
2795
2937
|
const t = (process.env.HOLOSCRIPT_AGENT_SCOPE_TIER ?? "warm").toLowerCase();
|
|
2796
2938
|
if (t === "cold" || t === "warm" || t === "hot") return t;
|
|
@@ -2838,11 +2980,17 @@ REQUIRED ENV
|
|
|
2838
2980
|
HOLOSCRIPT_AGENT_MODEL model id (e.g. "claude-opus-4-8")
|
|
2839
2981
|
HOLOSCRIPT_AGENT_BRAIN path to .hsplus brain composition
|
|
2840
2982
|
HOLOSCRIPT_AGENT_WALLET 0x\u2026 wallet address
|
|
2841
|
-
HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer (W.087 vertex B)
|
|
2842
2983
|
HOLOMESH_TEAM_ID target team id
|
|
2843
2984
|
ANTHROPIC_API_KEY | OPENAI_API_KEY | GEMINI_API_KEY per provider
|
|
2844
2985
|
|
|
2845
2986
|
OPTIONAL ENV
|
|
2987
|
+
HOLOSCRIPT_AGENT_X402_BEARER per-surface mesh bearer. OPTIONAL: when absent, the runner
|
|
2988
|
+
resolves it from the HoloKey broker by proving wallet
|
|
2989
|
+
ownership (POST /key/challenge \u2192 sign \u2192 /key/recover), so the
|
|
2990
|
+
bearer is never stored in plaintext .env. Requires a seat wallet.
|
|
2991
|
+
HOLOSCRIPT_AGENT_SEAT_ID override the computed seat-dir name (e.g. a sovereign x402 seat
|
|
2992
|
+
"sovereign-<surface>-<fp>-default-x402"); pairs with SEATS_ROOT
|
|
2993
|
+
HOLOSCRIPT_AGENT_SEAT_MASTER_KEY override the master-key path used to decrypt the seat wallet.enc
|
|
2846
2994
|
HOLOSCRIPT_AGENT_BUDGET_USD_DAY default 5
|
|
2847
2995
|
HOLOSCRIPT_AGENT_SCOPE_TIER cold | warm | hot (default warm)
|
|
2848
2996
|
HOLOSCRIPT_AGENT_TICK_MS daemon tick interval, default 60000
|