@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.
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 = required(env, "HOLOSCRIPT_AGENT_X402_BEARER");
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,
@@ -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 = required(env, 'HOLOSCRIPT_AGENT_X402_BEARER');\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.slice(0, 6)}…`,\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;AACA,QAAM,aAAa,SAAS,KAAK,8BAA8B;AAC/D,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,GAAG,WAAW,MAAM,GAAG,CAAC,CAAC;AAAA,IACpC,UAAU,GAAG;AAAA,IACb,OAAO,GAAG;AAAA,IACV,OAAO,GAAG;AAAA,IACV,iBAAiB,GAAG;AAAA,EACtB;AACF;","names":[]}
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 Wallet2 } from "ethers";
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 = required(env, "HOLOSCRIPT_AGENT_X402_BEARER");
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: brain.systemPrompt },
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 = Wallet.createRandom();
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: identity.x402Bearer,
2576
+ bearer,
2451
2577
  teamId: identity.teamId,
2452
- signer: buildRequestSigner(identity.handle)
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: identity.x402Bearer,
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 buildRequestSigner(handle) {
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(blob.encrypted_privkey.alg ?? "aes-256-gcm", masterKey, iv);
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
- 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
- };
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