@holoscript/holoscript-agent 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/bin/holoscript-agent.cjs +3 -1
- package/dist/ablation.js +4 -1
- package/dist/ablation.js.map +1 -1
- package/dist/brain.js +25 -3
- package/dist/brain.js.map +1 -1
- package/dist/commit-hook.js +6 -2
- package/dist/commit-hook.js.map +1 -1
- package/dist/cost-guard.js +2 -0
- package/dist/cost-guard.js.map +1 -1
- package/dist/holomesh-client.d.ts +8 -1
- package/dist/holomesh-client.js +24 -25
- package/dist/holomesh-client.js.map +1 -1
- package/dist/index.js +436 -85
- package/dist/index.js.map +1 -1
- package/dist/provision.js +39 -22
- package/dist/provision.js.map +1 -1
- package/dist/runner.js +268 -19
- package/dist/runner.js.map +1 -1
- package/dist/supervisor-config.js +3 -1
- package/dist/supervisor-config.js.map +1 -1
- package/dist/supervisor.js +332 -54
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +14 -1
- package/package.json +4 -1
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @holoscript/holoscript-agent
|
|
2
|
+
|
|
3
|
+
Headless agent runtime for the HoloMesh. Mount a `.hsplus` **brain**, point it at any
|
|
4
|
+
LLM provider (cloud, or a **local Ollama** model on an edge device), and it runs the
|
|
5
|
+
heartbeat → claim → execute loop against a HoloMesh team board — cost-guarded, identity-signed,
|
|
6
|
+
and provider-agnostic.
|
|
7
|
+
|
|
8
|
+
It is the runtime behind the sovereign **edge agent** path: a Jetson (or any Ollama host) runs
|
|
9
|
+
its brain at $0 marginal token cost via `--provider local-llm`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g @holoscript/holoscript-agent
|
|
15
|
+
# runtime deps: @holoscript/llm-provider, ethers (both public on npm)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quickstart — run a brain on a local model
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. point at your local Ollama + a tool-calling model
|
|
22
|
+
export HOLOSCRIPT_AGENT_PROVIDER=local-llm
|
|
23
|
+
export HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL=http://localhost:11434 # or http://holojetson.local:11434
|
|
24
|
+
export HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL=qwen3:4b-instruct # a proven tool-caller; avoid qwen2.5* (emits prose, not tool_calls)
|
|
25
|
+
|
|
26
|
+
# 2. identity (the bearer name MUST equal the agent's registered handle)
|
|
27
|
+
export HOLOSCRIPT_AGENT_HANDLE=my-edge-agent
|
|
28
|
+
export HOLOSCRIPT_AGENT_BRAIN=./my-brain.hsplus
|
|
29
|
+
export HOLOSCRIPT_AGENT_WALLET=0x…
|
|
30
|
+
export HOLOSCRIPT_AGENT_X402_BEARER=… # per-surface mesh bearer
|
|
31
|
+
export HOLOMESH_TEAM_ID=team_…
|
|
32
|
+
|
|
33
|
+
holoscript-agent whoami # verify identity resolves end-to-end
|
|
34
|
+
holoscript-agent tick # one claim+execute cycle (good for CI/cron/smoke)
|
|
35
|
+
holoscript-agent run # the daemon: heartbeat + claim + execute loop
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`run` blocks; `tick` does a single cycle and exits.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
| Command | Purpose |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `run` | start the daemon (heartbeat + claim + execute loop) |
|
|
45
|
+
| `tick` | single tick, then exit (CI / cron / smoke tests) |
|
|
46
|
+
| `whoami` | verify the identity tuple resolves (`/me` + env) |
|
|
47
|
+
| `supervise --config=<agents.json>` | run N agents from one config (multi-agent daemon) |
|
|
48
|
+
| `status --config=<path>` | print + validate a parsed supervise config |
|
|
49
|
+
| `provision --handle=<name> [--execute]` | provision a fresh x402 seat for a brain (dry-run by default) |
|
|
50
|
+
| `ablate --spec=<path> [--out-md] [--out-json]` | run a cross-LLM ablation matrix |
|
|
51
|
+
| `audit [rollup\|query\|tail]` | query the per-agent audit log |
|
|
52
|
+
| `help` | full env + flag reference |
|
|
53
|
+
|
|
54
|
+
## Providers
|
|
55
|
+
|
|
56
|
+
`HOLOSCRIPT_AGENT_PROVIDER` ∈ `anthropic | openai | gemini | xai | openrouter | local-llm | sovereign | mock`.
|
|
57
|
+
|
|
58
|
+
- **`local-llm`** — Ollama-compatible. Set `HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL` + `HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL`. Timeout defaults to 300s (edge devices are slow); override with `HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS`.
|
|
59
|
+
- **`sovereign`** — sovereign-first auto-resolution (serving fleet → cloud → local Ollama → BYOK frontier keys); `HOLOSCRIPT_AGENT_MODEL` overrides the pick.
|
|
60
|
+
- **cloud** (`anthropic`/`openai`/`gemini`/`xai`/`openrouter`) — set the matching `*_API_KEY`.
|
|
61
|
+
|
|
62
|
+
## Environment
|
|
63
|
+
|
|
64
|
+
**Required**
|
|
65
|
+
|
|
66
|
+
| Var | Meaning |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `HOLOSCRIPT_AGENT_HANDLE` | agent handle — **must equal the registered bearer name** (else CAEL/audit POSTs 403) |
|
|
69
|
+
| `HOLOSCRIPT_AGENT_PROVIDER` | one of the providers above |
|
|
70
|
+
| `HOLOSCRIPT_AGENT_MODEL` | model id (e.g. `claude-opus-4-8`); for `local-llm` prefer `HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL` |
|
|
71
|
+
| `HOLOSCRIPT_AGENT_BRAIN` | path to the `.hsplus` brain composition |
|
|
72
|
+
| `HOLOSCRIPT_AGENT_WALLET` | `0x…` wallet address |
|
|
73
|
+
| `HOLOSCRIPT_AGENT_X402_BEARER` | per-surface mesh bearer |
|
|
74
|
+
| `HOLOMESH_TEAM_ID` | target team id |
|
|
75
|
+
| `ANTHROPIC_API_KEY` \| `OPENAI_API_KEY` \| `GEMINI_API_KEY` \| … | per cloud provider |
|
|
76
|
+
|
|
77
|
+
**Optional** (defaults in parentheses) — full list via `holoscript-agent help`:
|
|
78
|
+
|
|
79
|
+
| Var | Meaning |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL` | local-llm base URL (`http://localhost:8080`) |
|
|
82
|
+
| `HOLOSCRIPT_AGENT_LOCAL_LLM_MODEL` | local-llm model id; overrides `HOLOSCRIPT_AGENT_MODEL` for the local provider |
|
|
83
|
+
| `HOLOSCRIPT_AGENT_LOCAL_LLM_TIMEOUT_MS` | local request timeout (`300000`) |
|
|
84
|
+
| `HOLOSCRIPT_AGENT_BUDGET_USD_DAY` | daily spend cap (`5`) |
|
|
85
|
+
| `HOLOSCRIPT_AGENT_TICK_MS` | daemon tick interval (`60000`) |
|
|
86
|
+
| `HOLOSCRIPT_AGENT_SCOPE_TIER` | `cold \| warm \| hot` (`warm`) |
|
|
87
|
+
| `HOLOMESH_API_BASE` | mesh API base (`https://mcp.holoscript.net/api/holomesh`) |
|
|
88
|
+
| `HOLOSCRIPT_AGENT_COMMIT_RESPONSES` | `1`/`true` → write responses as memos and git-commit them |
|
|
89
|
+
|
|
90
|
+
## Multi-agent (`supervise`)
|
|
91
|
+
|
|
92
|
+
Run a fleet from one config:
|
|
93
|
+
|
|
94
|
+
```jsonc
|
|
95
|
+
// agents.json
|
|
96
|
+
{
|
|
97
|
+
"agents": [
|
|
98
|
+
{ "handle": "edge-1", "provider": "local-llm", "model": "qwen3:4b-instruct", "brain": "./brains/edge.hsplus" },
|
|
99
|
+
{ "handle": "planner", "provider": "sovereign", "brain": "./brains/planner.hsplus" }
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
holoscript-agent status --config=agents.json # validate first
|
|
106
|
+
holoscript-agent supervise --config=agents.json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Subpath exports
|
|
110
|
+
|
|
111
|
+
`@holoscript/holoscript-agent` and its subpaths: `./runner`, `./brain`, `./cost-guard`,
|
|
112
|
+
`./identity`, `./commit-hook`, `./ablation`, `./supervisor`, `./supervisor-config`,
|
|
113
|
+
`./provision`, `./audit-log` — for embedding the runtime instead of using the CLI.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/bin/holoscript-agent.cjs
CHANGED
|
@@ -6,7 +6,9 @@ const { pathToFileURL } = require('node:url');
|
|
|
6
6
|
const target = join(__dirname, '..', 'dist', 'index.js');
|
|
7
7
|
|
|
8
8
|
if (!existsSync(target)) {
|
|
9
|
-
console.error(
|
|
9
|
+
console.error(
|
|
10
|
+
'[holoscript-agent] build output is missing. Run `pnpm --filter @holoscript/holoscript-agent run build` first.'
|
|
11
|
+
);
|
|
10
12
|
process.exit(1);
|
|
11
13
|
}
|
|
12
14
|
|
package/dist/ablation.js
CHANGED
|
@@ -157,7 +157,10 @@ USR:${user}`).digest("hex").slice(0, 16);
|
|
|
157
157
|
}
|
|
158
158
|
function withTimeout(p, ms, label) {
|
|
159
159
|
return new Promise((resolve, reject) => {
|
|
160
|
-
const timer = setTimeout(
|
|
160
|
+
const timer = setTimeout(
|
|
161
|
+
() => reject(new Error(`ablation cell "${label}" timed out after ${ms}ms`)),
|
|
162
|
+
ms
|
|
163
|
+
);
|
|
161
164
|
p.then(
|
|
162
165
|
(v) => {
|
|
163
166
|
clearTimeout(timer);
|
package/dist/ablation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ablation.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type {\n ILLMProvider,\n LLMCompletionRequest,\n LLMProviderName,\n TokenUsage,\n} from '@holoscript/llm-provider';\nimport type { CostGuard } from './cost-guard.js';\nimport type { AuditLog } from './audit-log.js';\nimport type { AgentIdentity } from './types.js';\n\nexport interface AblationProviderSpec {\n label: string;\n provider: LLMProviderName;\n model: string;\n build: () => Promise<ILLMProvider> | ILLMProvider;\n pricer?: (usage: TokenUsage) => number;\n}\n\nexport interface AblationTaskSpec {\n taskId: string;\n taskTitle: string;\n systemPrompt: string;\n userPrompt: string;\n brainPath?: string;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface AblationCell {\n label: string;\n provider: LLMProviderName;\n model: string;\n responseText: string;\n usage: TokenUsage;\n costUsd: number;\n durationMs: number;\n finishReason: string;\n errorMessage?: string;\n}\n\nexport interface AblationMatrix {\n taskId: string;\n taskTitle: string;\n brainPath?: string;\n promptHash: string;\n cells: AblationCell[];\n totalCostUsd: number;\n startedAt: string;\n completedAt: string;\n budgetExhausted: boolean;\n}\n\nexport interface RunAblationOptions {\n task: AblationTaskSpec;\n providers: AblationProviderSpec[];\n costGuard?: CostGuard;\n timeoutPerCellMs?: number;\n auditLog?: AuditLog;\n matrixId?: string;\n identityFor?: (spec: AblationProviderSpec) => AgentIdentity;\n}\n\nexport async function runAblation(opts: RunAblationOptions): Promise<AblationMatrix> {\n const { task, providers, costGuard } = opts;\n const startedAt = new Date().toISOString();\n const promptHash = hashPrompt(task.systemPrompt, task.userPrompt);\n\n const request: LLMCompletionRequest = {\n messages: [\n { role: 'system', content: task.systemPrompt },\n { role: 'user', content: task.userPrompt },\n ],\n maxTokens: task.maxTokens ?? 2048,\n temperature: task.temperature ?? 0.4,\n };\n\n const cells: AblationCell[] = [];\n let budgetExhausted = false;\n const matrixId = opts.matrixId ?? `mx_${promptHash}_${Date.now()}`;\n\n for (const spec of providers) {\n if (costGuard?.isOverBudget()) {\n budgetExhausted = true;\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: '',\n usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n costUsd: 0,\n durationMs: 0,\n finishReason: 'error',\n errorMessage: 'budget-exhausted-before-cell',\n });\n continue;\n }\n\n const t0 = Date.now();\n try {\n const provider = await spec.build();\n const cellPromise = provider.complete(request, spec.model);\n const response = opts.timeoutPerCellMs\n ? await withTimeout(cellPromise, opts.timeoutPerCellMs, spec.label)\n : await cellPromise;\n const durationMs = Date.now() - t0;\n\n const costUsd = spec.pricer\n ? spec.pricer(response.usage)\n : costGuard?.recordUsage(spec.model, response.usage).costUsd ?? 0;\n if (spec.pricer && costGuard) {\n costGuard.recordUsage(spec.model, response.usage);\n }\n\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: response.content,\n usage: response.usage,\n costUsd,\n durationMs,\n finishReason: response.finishReason,\n });\n recordAblationCellIfWired(opts, spec, {\n matrixId,\n promptHash,\n promptTokens: response.usage.promptTokens,\n completionTokens: response.usage.completionTokens,\n costUsd,\n durationMs,\n finishReason: response.finishReason,\n });\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: '',\n usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n costUsd: 0,\n durationMs: Date.now() - t0,\n finishReason: 'error',\n errorMessage,\n });\n recordAblationCellIfWired(opts, spec, {\n matrixId,\n promptHash,\n promptTokens: 0,\n completionTokens: 0,\n costUsd: 0,\n durationMs: Date.now() - t0,\n finishReason: 'error',\n errorMessage,\n });\n }\n }\n\n return {\n taskId: task.taskId,\n taskTitle: task.taskTitle,\n brainPath: task.brainPath,\n promptHash,\n cells,\n totalCostUsd: cells.reduce((sum, c) => sum + c.costUsd, 0),\n startedAt,\n completedAt: new Date().toISOString(),\n budgetExhausted,\n };\n}\n\nexport function renderAblationMarkdown(matrix: AblationMatrix): string {\n const header = [\n `# Ablation: ${matrix.taskTitle}`,\n '',\n `- task_id: \\`${matrix.taskId}\\``,\n `- prompt_hash: \\`${matrix.promptHash}\\``,\n matrix.brainPath ? `- brain: \\`${matrix.brainPath}\\`` : '- brain: _(none)_',\n `- started: ${matrix.startedAt}`,\n `- completed: ${matrix.completedAt}`,\n `- total_cost_usd: $${matrix.totalCostUsd.toFixed(4)}`,\n matrix.budgetExhausted ? `- **budget_exhausted: true** (some cells skipped)` : '',\n '',\n '| Label | Provider | Model | Tokens (in/out) | Cost (USD) | Duration (ms) | Finish | Excerpt |',\n '|---|---|---|---|---|---|---|---|',\n ].filter((line) => line !== '');\n\n const rows = matrix.cells.map((c) => {\n const tokens = `${c.usage.promptTokens}/${c.usage.completionTokens}`;\n const excerpt = c.errorMessage\n ? `_error: ${truncate(c.errorMessage, 80)}_`\n : truncate(escapeMd(c.responseText.replace(/\\n/g, ' ')), 80);\n return `| ${c.label} | ${c.provider} | ${c.model} | ${tokens} | $${c.costUsd.toFixed(4)} | ${c.durationMs} | ${c.finishReason} | ${excerpt} |`;\n });\n\n return [...header, ...rows, ''].join('\\n');\n}\n\nfunction recordAblationCellIfWired(\n opts: RunAblationOptions,\n spec: AblationProviderSpec,\n cell: {\n matrixId: string;\n promptHash: string;\n promptTokens: number;\n completionTokens: number;\n costUsd: number;\n durationMs: number;\n finishReason: string;\n errorMessage?: string;\n }\n): void {\n if (!opts.auditLog) return;\n const identity = opts.identityFor?.(spec) ?? {\n handle: `ablation:${spec.label}`,\n surface: `ablation:${spec.label}`,\n wallet: '0x0000000000000000000000000000000000000000',\n x402Bearer: '',\n llmProvider: spec.provider,\n llmModel: spec.model,\n brainPath: opts.task.brainPath ?? '(none)',\n budgetUsdPerDay: 0,\n teamId: '(ablation)',\n meshApiBase: '(ablation)',\n };\n try {\n opts.auditLog.recordAblationCell({\n identity,\n matrixId: cell.matrixId,\n label: spec.label,\n taskId: opts.task.taskId,\n taskTitle: opts.task.taskTitle,\n promptHash: cell.promptHash,\n promptTokens: cell.promptTokens,\n completionTokens: cell.completionTokens,\n costUsd: cell.costUsd,\n durationMs: cell.durationMs,\n finishReason: cell.finishReason,\n errorMessage: cell.errorMessage,\n });\n } catch {\n // Audit log write must never break the ablation matrix output.\n }\n}\n\nfunction hashPrompt(system: string, user: string): string {\n return createHash('sha256').update(`SYS:${system}\\nUSR:${user}`).digest('hex').slice(0, 16);\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, label: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(`ablation cell \"${label}\" timed out after ${ms}ms`)), ms);\n p.then(\n (v) => {\n clearTimeout(timer);\n resolve(v);\n },\n (e) => {\n clearTimeout(timer);\n reject(e);\n }\n );\n });\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, max - 1)}…`;\n}\n\nfunction escapeMd(s: string): string {\n return s.replace(/\\|/g, '\\\\|');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/ablation.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type {\n ILLMProvider,\n LLMCompletionRequest,\n LLMProviderName,\n TokenUsage,\n} from '@holoscript/llm-provider';\nimport type { CostGuard } from './cost-guard.js';\nimport type { AuditLog } from './audit-log.js';\nimport type { AgentIdentity } from './types.js';\n\nexport interface AblationProviderSpec {\n label: string;\n provider: LLMProviderName;\n model: string;\n build: () => Promise<ILLMProvider> | ILLMProvider;\n pricer?: (usage: TokenUsage) => number;\n}\n\nexport interface AblationTaskSpec {\n taskId: string;\n taskTitle: string;\n systemPrompt: string;\n userPrompt: string;\n brainPath?: string;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface AblationCell {\n label: string;\n provider: LLMProviderName;\n model: string;\n responseText: string;\n usage: TokenUsage;\n costUsd: number;\n durationMs: number;\n finishReason: string;\n errorMessage?: string;\n}\n\nexport interface AblationMatrix {\n taskId: string;\n taskTitle: string;\n brainPath?: string;\n promptHash: string;\n cells: AblationCell[];\n totalCostUsd: number;\n startedAt: string;\n completedAt: string;\n budgetExhausted: boolean;\n}\n\nexport interface RunAblationOptions {\n task: AblationTaskSpec;\n providers: AblationProviderSpec[];\n costGuard?: CostGuard;\n timeoutPerCellMs?: number;\n auditLog?: AuditLog;\n matrixId?: string;\n identityFor?: (spec: AblationProviderSpec) => AgentIdentity;\n}\n\nexport async function runAblation(opts: RunAblationOptions): Promise<AblationMatrix> {\n const { task, providers, costGuard } = opts;\n const startedAt = new Date().toISOString();\n const promptHash = hashPrompt(task.systemPrompt, task.userPrompt);\n\n const request: LLMCompletionRequest = {\n messages: [\n { role: 'system', content: task.systemPrompt },\n { role: 'user', content: task.userPrompt },\n ],\n maxTokens: task.maxTokens ?? 2048,\n temperature: task.temperature ?? 0.4,\n };\n\n const cells: AblationCell[] = [];\n let budgetExhausted = false;\n const matrixId = opts.matrixId ?? `mx_${promptHash}_${Date.now()}`;\n\n for (const spec of providers) {\n if (costGuard?.isOverBudget()) {\n budgetExhausted = true;\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: '',\n usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n costUsd: 0,\n durationMs: 0,\n finishReason: 'error',\n errorMessage: 'budget-exhausted-before-cell',\n });\n continue;\n }\n\n const t0 = Date.now();\n try {\n const provider = await spec.build();\n const cellPromise = provider.complete(request, spec.model);\n const response = opts.timeoutPerCellMs\n ? await withTimeout(cellPromise, opts.timeoutPerCellMs, spec.label)\n : await cellPromise;\n const durationMs = Date.now() - t0;\n\n const costUsd = spec.pricer\n ? spec.pricer(response.usage)\n : (costGuard?.recordUsage(spec.model, response.usage).costUsd ?? 0);\n if (spec.pricer && costGuard) {\n costGuard.recordUsage(spec.model, response.usage);\n }\n\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: response.content,\n usage: response.usage,\n costUsd,\n durationMs,\n finishReason: response.finishReason,\n });\n recordAblationCellIfWired(opts, spec, {\n matrixId,\n promptHash,\n promptTokens: response.usage.promptTokens,\n completionTokens: response.usage.completionTokens,\n costUsd,\n durationMs,\n finishReason: response.finishReason,\n });\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n cells.push({\n label: spec.label,\n provider: spec.provider,\n model: spec.model,\n responseText: '',\n usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n costUsd: 0,\n durationMs: Date.now() - t0,\n finishReason: 'error',\n errorMessage,\n });\n recordAblationCellIfWired(opts, spec, {\n matrixId,\n promptHash,\n promptTokens: 0,\n completionTokens: 0,\n costUsd: 0,\n durationMs: Date.now() - t0,\n finishReason: 'error',\n errorMessage,\n });\n }\n }\n\n return {\n taskId: task.taskId,\n taskTitle: task.taskTitle,\n brainPath: task.brainPath,\n promptHash,\n cells,\n totalCostUsd: cells.reduce((sum, c) => sum + c.costUsd, 0),\n startedAt,\n completedAt: new Date().toISOString(),\n budgetExhausted,\n };\n}\n\nexport function renderAblationMarkdown(matrix: AblationMatrix): string {\n const header = [\n `# Ablation: ${matrix.taskTitle}`,\n '',\n `- task_id: \\`${matrix.taskId}\\``,\n `- prompt_hash: \\`${matrix.promptHash}\\``,\n matrix.brainPath ? `- brain: \\`${matrix.brainPath}\\`` : '- brain: _(none)_',\n `- started: ${matrix.startedAt}`,\n `- completed: ${matrix.completedAt}`,\n `- total_cost_usd: $${matrix.totalCostUsd.toFixed(4)}`,\n matrix.budgetExhausted ? `- **budget_exhausted: true** (some cells skipped)` : '',\n '',\n '| Label | Provider | Model | Tokens (in/out) | Cost (USD) | Duration (ms) | Finish | Excerpt |',\n '|---|---|---|---|---|---|---|---|',\n ].filter((line) => line !== '');\n\n const rows = matrix.cells.map((c) => {\n const tokens = `${c.usage.promptTokens}/${c.usage.completionTokens}`;\n const excerpt = c.errorMessage\n ? `_error: ${truncate(c.errorMessage, 80)}_`\n : truncate(escapeMd(c.responseText.replace(/\\n/g, ' ')), 80);\n return `| ${c.label} | ${c.provider} | ${c.model} | ${tokens} | $${c.costUsd.toFixed(4)} | ${c.durationMs} | ${c.finishReason} | ${excerpt} |`;\n });\n\n return [...header, ...rows, ''].join('\\n');\n}\n\nfunction recordAblationCellIfWired(\n opts: RunAblationOptions,\n spec: AblationProviderSpec,\n cell: {\n matrixId: string;\n promptHash: string;\n promptTokens: number;\n completionTokens: number;\n costUsd: number;\n durationMs: number;\n finishReason: string;\n errorMessage?: string;\n }\n): void {\n if (!opts.auditLog) return;\n const identity = opts.identityFor?.(spec) ?? {\n handle: `ablation:${spec.label}`,\n surface: `ablation:${spec.label}`,\n wallet: '0x0000000000000000000000000000000000000000',\n x402Bearer: '',\n llmProvider: spec.provider,\n llmModel: spec.model,\n brainPath: opts.task.brainPath ?? '(none)',\n budgetUsdPerDay: 0,\n teamId: '(ablation)',\n meshApiBase: '(ablation)',\n };\n try {\n opts.auditLog.recordAblationCell({\n identity,\n matrixId: cell.matrixId,\n label: spec.label,\n taskId: opts.task.taskId,\n taskTitle: opts.task.taskTitle,\n promptHash: cell.promptHash,\n promptTokens: cell.promptTokens,\n completionTokens: cell.completionTokens,\n costUsd: cell.costUsd,\n durationMs: cell.durationMs,\n finishReason: cell.finishReason,\n errorMessage: cell.errorMessage,\n });\n } catch {\n // Audit log write must never break the ablation matrix output.\n }\n}\n\nfunction hashPrompt(system: string, user: string): string {\n return createHash('sha256').update(`SYS:${system}\\nUSR:${user}`).digest('hex').slice(0, 16);\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number, label: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`ablation cell \"${label}\" timed out after ${ms}ms`)),\n ms\n );\n p.then(\n (v) => {\n clearTimeout(timer);\n resolve(v);\n },\n (e) => {\n clearTimeout(timer);\n reject(e);\n }\n );\n });\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, max - 1)}…`;\n}\n\nfunction escapeMd(s: string): string {\n return s.replace(/\\|/g, '\\\\|');\n}\n"],"mappings":"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","names":[]}
|
package/dist/brain.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/brain.ts
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
async function loadBrain(brainPath, scopeTier = "warm") {
|
|
4
|
-
const
|
|
5
|
-
const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(
|
|
4
|
+
const raw = await readFile(brainPath, "utf8");
|
|
5
|
+
const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(raw);
|
|
6
|
+
const systemPrompt = extractSystemPromptPreamble(raw);
|
|
6
7
|
return {
|
|
7
8
|
brainPath,
|
|
8
9
|
systemPrompt,
|
|
@@ -11,9 +12,30 @@ async function loadBrain(brainPath, scopeTier = "warm") {
|
|
|
11
12
|
scopeTier,
|
|
12
13
|
requires,
|
|
13
14
|
prefers,
|
|
14
|
-
avoids
|
|
15
|
+
avoids,
|
|
16
|
+
reflect: extractReflect(raw)
|
|
15
17
|
};
|
|
16
18
|
}
|
|
19
|
+
function extractReflect(brain) {
|
|
20
|
+
const block = sliceNamedBlock(brain, "reflect");
|
|
21
|
+
if (block === void 0) return void 0;
|
|
22
|
+
const criteria = scalarField(block, "criteria") ?? scalarField(block, "scorer") ?? scalarField(block, "of") ?? "correctness, completeness, and valid HoloScript syntax";
|
|
23
|
+
const escRaw = scalarField(block, "escalate_on_fail") ?? scalarField(block, "escalateOnFail") ?? scalarField(block, "escalate");
|
|
24
|
+
return { criteria, escalateOnFail: (escRaw ?? "").split(",")[0].trim().toLowerCase() === "true" };
|
|
25
|
+
}
|
|
26
|
+
function extractSystemPromptPreamble(src) {
|
|
27
|
+
const lines = src.split("\n");
|
|
28
|
+
const BLOCK_START = /^(#version|#target|#mode|identity\s*\{|state\s*\{|computed\s*\{|traits\s*\[|capabilities\s*\{|directives\s*\{|behavior\s)/;
|
|
29
|
+
let cutLine = -1;
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
if (BLOCK_START.test(lines[i].trim())) {
|
|
32
|
+
cutLine = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (cutLine <= 0) return src;
|
|
37
|
+
return lines.slice(0, cutLine).join("\n").trimEnd();
|
|
38
|
+
}
|
|
17
39
|
function extractIdentity(brain) {
|
|
18
40
|
const identityBlock = sliceNamedBlock(brain, "identity");
|
|
19
41
|
if (!identityBlock) {
|
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
|
|
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":[]}
|
package/dist/commit-hook.js
CHANGED
|
@@ -25,7 +25,9 @@ function makeCommitHook(opts) {
|
|
|
25
25
|
const relPath = relativeTo(cwd, filePath);
|
|
26
26
|
const addRes = spawn("git", ["add", relPath], { cwd, encoding: "utf8" });
|
|
27
27
|
if (addRes.status !== 0) {
|
|
28
|
-
throw new Error(
|
|
28
|
+
throw new Error(
|
|
29
|
+
`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`
|
|
30
|
+
);
|
|
29
31
|
}
|
|
30
32
|
const message = renderCommitMessage({ scope, task, identity, result });
|
|
31
33
|
const commitArgs = ["commit", "-m", message];
|
|
@@ -34,7 +36,9 @@ function makeCommitHook(opts) {
|
|
|
34
36
|
}
|
|
35
37
|
const commitRes = spawn("git", commitArgs, { cwd, encoding: "utf8" });
|
|
36
38
|
if (commitRes.status !== 0) {
|
|
37
|
-
throw new Error(
|
|
39
|
+
throw new Error(
|
|
40
|
+
`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`
|
|
41
|
+
);
|
|
38
42
|
}
|
|
39
43
|
const hashRes = spawn("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
40
44
|
const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
|
package/dist/commit-hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commit-hook.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport type { AgentIdentity, BoardTask, ExecutionResult } from './types.js';\n\nexport interface CommitHookOptions {\n outputDir: string;\n workingDir?: string;\n authorName?: string;\n authorEmail?: string;\n scope?: string;\n spawn?: typeof spawnSync;\n now?: () => Date;\n}\n\nexport interface CommitHookResult {\n filePath: string;\n commitHash?: string;\n staged: string[];\n message: string;\n}\n\nconst SAFE_HANDLE = /^[a-z0-9_-]{1,64}$/i;\n\nexport function makeCommitHook(opts: CommitHookOptions) {\n if (!opts.outputDir || opts.outputDir.trim().length === 0) {\n throw new Error('CommitHookOptions.outputDir is required');\n }\n const spawn = opts.spawn ?? spawnSync;\n const cwd = opts.workingDir ?? process.cwd();\n const outputDir = resolve(cwd, opts.outputDir);\n const now = opts.now ?? (() => new Date());\n const scope = opts.scope ?? 'agent';\n\n return async (result: ExecutionResult
|
|
1
|
+
{"version":3,"sources":["../src/commit-hook.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport type { AgentIdentity, BoardTask, ExecutionResult } from './types.js';\n\nexport interface CommitHookOptions {\n outputDir: string;\n workingDir?: string;\n authorName?: string;\n authorEmail?: string;\n scope?: string;\n spawn?: typeof spawnSync;\n now?: () => Date;\n}\n\nexport interface CommitHookResult {\n filePath: string;\n commitHash?: string;\n staged: string[];\n message: string;\n}\n\nconst SAFE_HANDLE = /^[a-z0-9_-]{1,64}$/i;\n\nexport function makeCommitHook(opts: CommitHookOptions) {\n if (!opts.outputDir || opts.outputDir.trim().length === 0) {\n throw new Error('CommitHookOptions.outputDir is required');\n }\n const spawn = opts.spawn ?? spawnSync;\n const cwd = opts.workingDir ?? process.cwd();\n const outputDir = resolve(cwd, opts.outputDir);\n const now = opts.now ?? (() => new Date());\n const scope = opts.scope ?? 'agent';\n\n return async (\n result: ExecutionResult,\n task: BoardTask,\n identity: AgentIdentity\n ): Promise<CommitHookResult> => {\n if (!SAFE_HANDLE.test(identity.handle)) {\n throw new Error(`Refusing to commit: handle \"${identity.handle}\" must match ${SAFE_HANDLE}`);\n }\n const date = now().toISOString().slice(0, 10);\n const safeTaskId = task.id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80);\n const fileName = `${date}_${safeTaskId}_${identity.handle}.md`;\n const filePath = join(outputDir, fileName);\n\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, renderMemo(result, task, identity, date), 'utf8');\n\n const relPath = relativeTo(cwd, filePath);\n const addRes = spawn('git', ['add', relPath], { cwd, encoding: 'utf8' });\n if (addRes.status !== 0) {\n throw new Error(\n `git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`\n );\n }\n\n const message = renderCommitMessage({ scope, task, identity, result });\n const commitArgs = ['commit', '-m', message];\n if (opts.authorName && opts.authorEmail) {\n commitArgs.push('--author', `${opts.authorName} <${opts.authorEmail}>`);\n }\n const commitRes = spawn('git', commitArgs, { cwd, encoding: 'utf8' });\n if (commitRes.status !== 0) {\n throw new Error(\n `git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`\n );\n }\n\n const hashRes = spawn('git', ['rev-parse', 'HEAD'], { cwd, encoding: 'utf8' });\n const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : undefined;\n\n return { filePath, commitHash, staged: [relPath], message };\n };\n}\n\nfunction renderMemo(\n result: ExecutionResult,\n task: BoardTask,\n identity: AgentIdentity,\n date: string\n): string {\n return [\n '---',\n `title: \"${task.title.replace(/\"/g, \"'\")}\"`,\n `task_id: ${task.id}`,\n `agent: ${identity.handle}`,\n `surface: ${identity.surface}`,\n `provider: ${identity.llmProvider}`,\n `model: ${identity.llmModel}`,\n `wallet: ${identity.wallet}`,\n `date: ${date}`,\n `tokens: ${result.usage.totalTokens}`,\n `cost_usd: ${result.costUsd.toFixed(4)}`,\n `duration_ms: ${result.durationMs}`,\n `tags: [${(task.tags ?? []).map((t) => JSON.stringify(t)).join(', ')}]`,\n '---',\n '',\n `# ${task.title}`,\n '',\n '## Task description',\n '',\n task.description ?? '(no description)',\n '',\n '## Agent response',\n '',\n result.responseText.trim(),\n '',\n ].join('\\n');\n}\n\nconst SUBJECT_MAX = 72;\n\nfunction renderCommitMessage(opts: {\n scope: string;\n task: BoardTask;\n identity: AgentIdentity;\n result: ExecutionResult;\n}): string {\n const suffix = ` [agent:${opts.identity.handle}]`;\n const prefix = `${opts.scope}: `;\n const titleBudget = Math.max(8, SUBJECT_MAX - prefix.length - suffix.length);\n const subject = `${prefix}${truncate(opts.task.title, titleBudget)}${suffix}`;\n const body = [\n '',\n `task: ${opts.task.id}`,\n `agent: ${opts.identity.handle} (${opts.identity.llmProvider}/${opts.identity.llmModel})`,\n `wallet: ${opts.identity.wallet}`,\n `cost: $${opts.result.costUsd.toFixed(4)} / ${opts.result.usage.totalTokens} tok / ${opts.result.durationMs}ms`,\n ].join('\\n');\n return `${subject}\\n${body}\\n`;\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max ? s : `${s.slice(0, max - 1)}…`;\n}\n\nfunction relativeTo(base: string, target: string): string {\n const b = base.replace(/\\\\/g, '/');\n const t = target.replace(/\\\\/g, '/');\n if (t.startsWith(b + '/')) return t.slice(b.length + 1);\n if (t === b) return '.';\n return t;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cost-guard.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
var ANTHROPIC_PRICING_USD_PER_MTOK = {
|
|
5
|
+
"claude-opus-4-8": { input: 10, output: 50 },
|
|
6
|
+
// 3× cheaper than 4.7 on total cost; A-020 2026-06-08
|
|
5
7
|
"claude-opus-4-7": { input: 5, output: 25 },
|
|
6
8
|
"claude-opus-4-6": { input: 5, output: 25 },
|
|
7
9
|
"claude-sonnet-4-6": { input: 3, output: 15 },
|
package/dist/cost-guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cost-guard.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { TokenUsage } from '@holoscript/llm-provider';\nimport type { CostState, ModelPricer } from './types.js';\n\nexport const ANTHROPIC_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> = {\n 'claude-opus-4-7': { input: 5, output: 25 },\n 'claude-opus-4-6': { input: 5, output: 25 },\n 'claude-sonnet-4-6': { input: 3, output: 15 },\n 'claude-haiku-4-5-20251001': { input: 1, output: 5 },\n 'claude-haiku-4-5': { input: 1, output: 5 },\n};\n\nexport function defaultAnthropicPricer(model: string, usage: TokenUsage): number {\n const price = ANTHROPIC_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No pricing configured for model \"${model}\" — add to ANTHROPIC_PRICING_USD_PER_MTOK or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n/**\n * Pricer for local-llm providers (vLLM-on-GPU). The compute cost is the\n * Vast.ai (or other GPU) hourly rental, NOT per-token. From the agent's\n * perspective each LLM call has $0 marginal cost — the budget guard for\n * local-llm should track tick count or wall-clock time, not tokens.\n *\n * Returns 0 unconditionally. Token counts are still recorded in CostState\n * so usage analytics work, but cost-guard never trips on token spend.\n */\nexport function defaultLocalLlmPricer(_model: string, _usage: TokenUsage): number {\n return 0;\n}\n\n// xAI / Grok pricing — populated by /research task task_1778109552044_qed8.\n// Empty until verified pricing lands. defaultXAIPricer throws on missing\n// model with a helpful pointer (matches defaultAnthropicPricer behavior).\n// Never paste training-era pricing here — F.014 / W.GOLD.341.\nexport const XAI_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> = {};\n\nexport function defaultXAIPricer(model: string, usage: TokenUsage): number {\n const price = XAI_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No xAI pricing configured for model \"${model}\" — populate XAI_PRICING_USD_PER_MTOK ` +\n `(see /research task_1778109552044_qed8 in docs/LLM_CAPABILITIES.md) or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n// OpenRouter pricing is per-model and varies by upstream — populated lazily.\n// Empty until verified pricing lands.\nexport const OPENROUTER_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> =\n {};\n\nexport function defaultOpenRouterPricer(model: string, usage: TokenUsage): number {\n const price = OPENROUTER_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No OpenRouter pricing configured for model \"${model}\" — populate OPENROUTER_PRICING_USD_PER_MTOK ` +\n `or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n/**\n * Provider-aware default pricer dispatch. Picks the right pricer by\n * provider so the holoscript-agent runtime works for both Anthropic\n * (per-token billing) and local-llm (compute already paid via GPU\n * rental) without a custom pricer at every call site.\n *\n * Refs: 2026-04-26 mw02 boot loop — local-llm workers tick-erroring with\n * \"No pricing configured for model 'Qwen/Qwen2.5-0.5B-Instruct'\" because\n * defaultAnthropicPricer was wired in for ALL providers regardless of\n * which LLM the agent uses.\n *\n * Known gap (separate task): non-Anthropic non-local providers (openai,\n * gemini) still fall through to defaultAnthropicPricer here. xai +\n * openrouter were added 2026-05-06 with explicit dispatch + empty\n * pricing dicts (Lane A — see docs/LLM_CAPABILITIES.md).\n */\nexport function defaultPricerForProvider(\n provider: 'anthropic' | 'local-llm' | 'openai' | 'xai' | 'openrouter' | string\n): ModelPricer {\n if (provider === 'local-llm' || provider === 'mock') return defaultLocalLlmPricer;\n if (provider === 'xai') return defaultXAIPricer;\n if (provider === 'openrouter') return defaultOpenRouterPricer;\n return defaultAnthropicPricer;\n}\n\nexport class CostGuard {\n private state: CostState;\n private readonly statePath: string;\n private readonly dailyBudgetUsd: number;\n private readonly pricer: ModelPricer;\n\n constructor(opts: { statePath: string; dailyBudgetUsd: number; pricer?: ModelPricer }) {\n this.statePath = opts.statePath;\n this.dailyBudgetUsd = opts.dailyBudgetUsd;\n this.pricer = opts.pricer ?? defaultAnthropicPricer;\n this.state = this.loadOrInit();\n }\n\n recordUsage(\n model: string,\n usage: TokenUsage\n ): { costUsd: number; spentUsd: number; remainingUsd: number } {\n this.rolloverIfNewDay();\n const costUsd = this.pricer(model, usage);\n this.state.spentUsd += costUsd;\n this.state.promptTokens += usage.promptTokens;\n this.state.completionTokens += usage.completionTokens;\n this.state.callCount += 1;\n this.persist();\n return {\n costUsd,\n spentUsd: this.state.spentUsd,\n remainingUsd: Math.max(0, this.dailyBudgetUsd - this.state.spentUsd),\n };\n }\n\n isOverBudget(): boolean {\n if (this.dailyBudgetUsd === 0) return false;\n this.rolloverIfNewDay();\n return this.state.spentUsd >= this.dailyBudgetUsd;\n }\n\n getRemainingUsd(): number {\n if (this.dailyBudgetUsd === 0) return Number.POSITIVE_INFINITY;\n this.rolloverIfNewDay();\n return Math.max(0, this.dailyBudgetUsd - this.state.spentUsd);\n }\n\n getState(): Readonly<CostState> {\n this.rolloverIfNewDay();\n return { ...this.state };\n }\n\n private rolloverIfNewDay(): void {\n const today = todayUtc();\n if (this.state.date !== today) {\n this.state = { date: today, spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };\n this.persist();\n }\n }\n\n private loadOrInit(): CostState {\n if (existsSync(this.statePath)) {\n const raw = readFileSync(this.statePath, 'utf8');\n const parsed = JSON.parse(raw) as CostState;\n if (parsed.date === todayUtc()) return parsed;\n }\n return { date: todayUtc(), spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };\n }\n\n private persist(): void {\n mkdirSync(dirname(this.statePath), { recursive: true });\n writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), 'utf8');\n }\n}\n\nfunction todayUtc(): string {\n return new Date().toISOString().slice(0, 10);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cost-guard.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { TokenUsage } from '@holoscript/llm-provider';\nimport type { CostState, ModelPricer } from './types.js';\n\nexport const ANTHROPIC_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> = {\n 'claude-opus-4-8': { input: 10, output: 50 }, // 3× cheaper than 4.7 on total cost; A-020 2026-06-08\n 'claude-opus-4-7': { input: 5, output: 25 },\n 'claude-opus-4-6': { input: 5, output: 25 },\n 'claude-sonnet-4-6': { input: 3, output: 15 },\n 'claude-haiku-4-5-20251001': { input: 1, output: 5 },\n 'claude-haiku-4-5': { input: 1, output: 5 },\n};\n\nexport function defaultAnthropicPricer(model: string, usage: TokenUsage): number {\n const price = ANTHROPIC_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No pricing configured for model \"${model}\" — add to ANTHROPIC_PRICING_USD_PER_MTOK or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n/**\n * Pricer for local-llm providers (vLLM-on-GPU). The compute cost is the\n * Vast.ai (or other GPU) hourly rental, NOT per-token. From the agent's\n * perspective each LLM call has $0 marginal cost — the budget guard for\n * local-llm should track tick count or wall-clock time, not tokens.\n *\n * Returns 0 unconditionally. Token counts are still recorded in CostState\n * so usage analytics work, but cost-guard never trips on token spend.\n */\nexport function defaultLocalLlmPricer(_model: string, _usage: TokenUsage): number {\n return 0;\n}\n\n// xAI / Grok pricing — populated by /research task task_1778109552044_qed8.\n// Empty until verified pricing lands. defaultXAIPricer throws on missing\n// model with a helpful pointer (matches defaultAnthropicPricer behavior).\n// Never paste training-era pricing here — F.014 / W.GOLD.341.\nexport const XAI_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> = {};\n\nexport function defaultXAIPricer(model: string, usage: TokenUsage): number {\n const price = XAI_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No xAI pricing configured for model \"${model}\" — populate XAI_PRICING_USD_PER_MTOK ` +\n `(see /research task_1778109552044_qed8 in docs/LLM_CAPABILITIES.md) or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n// OpenRouter pricing is per-model and varies by upstream — populated lazily.\n// Empty until verified pricing lands.\nexport const OPENROUTER_PRICING_USD_PER_MTOK: Record<string, { input: number; output: number }> =\n {};\n\nexport function defaultOpenRouterPricer(model: string, usage: TokenUsage): number {\n const price = OPENROUTER_PRICING_USD_PER_MTOK[model];\n if (!price) {\n throw new Error(\n `No OpenRouter pricing configured for model \"${model}\" — populate OPENROUTER_PRICING_USD_PER_MTOK ` +\n `or pass a custom pricer`\n );\n }\n return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1_000_000;\n}\n\n/**\n * Provider-aware default pricer dispatch. Picks the right pricer by\n * provider so the holoscript-agent runtime works for both Anthropic\n * (per-token billing) and local-llm (compute already paid via GPU\n * rental) without a custom pricer at every call site.\n *\n * Refs: 2026-04-26 mw02 boot loop — local-llm workers tick-erroring with\n * \"No pricing configured for model 'Qwen/Qwen2.5-0.5B-Instruct'\" because\n * defaultAnthropicPricer was wired in for ALL providers regardless of\n * which LLM the agent uses.\n *\n * Known gap (separate task): non-Anthropic non-local providers (openai,\n * gemini) still fall through to defaultAnthropicPricer here. xai +\n * openrouter were added 2026-05-06 with explicit dispatch + empty\n * pricing dicts (Lane A — see docs/LLM_CAPABILITIES.md).\n */\nexport function defaultPricerForProvider(\n provider: 'anthropic' | 'local-llm' | 'openai' | 'xai' | 'openrouter' | string\n): ModelPricer {\n if (provider === 'local-llm' || provider === 'mock') return defaultLocalLlmPricer;\n if (provider === 'xai') return defaultXAIPricer;\n if (provider === 'openrouter') return defaultOpenRouterPricer;\n return defaultAnthropicPricer;\n}\n\nexport class CostGuard {\n private state: CostState;\n private readonly statePath: string;\n private readonly dailyBudgetUsd: number;\n private readonly pricer: ModelPricer;\n\n constructor(opts: { statePath: string; dailyBudgetUsd: number; pricer?: ModelPricer }) {\n this.statePath = opts.statePath;\n this.dailyBudgetUsd = opts.dailyBudgetUsd;\n this.pricer = opts.pricer ?? defaultAnthropicPricer;\n this.state = this.loadOrInit();\n }\n\n recordUsage(\n model: string,\n usage: TokenUsage\n ): { costUsd: number; spentUsd: number; remainingUsd: number } {\n this.rolloverIfNewDay();\n const costUsd = this.pricer(model, usage);\n this.state.spentUsd += costUsd;\n this.state.promptTokens += usage.promptTokens;\n this.state.completionTokens += usage.completionTokens;\n this.state.callCount += 1;\n this.persist();\n return {\n costUsd,\n spentUsd: this.state.spentUsd,\n remainingUsd: Math.max(0, this.dailyBudgetUsd - this.state.spentUsd),\n };\n }\n\n isOverBudget(): boolean {\n if (this.dailyBudgetUsd === 0) return false;\n this.rolloverIfNewDay();\n return this.state.spentUsd >= this.dailyBudgetUsd;\n }\n\n getRemainingUsd(): number {\n if (this.dailyBudgetUsd === 0) return Number.POSITIVE_INFINITY;\n this.rolloverIfNewDay();\n return Math.max(0, this.dailyBudgetUsd - this.state.spentUsd);\n }\n\n getState(): Readonly<CostState> {\n this.rolloverIfNewDay();\n return { ...this.state };\n }\n\n private rolloverIfNewDay(): void {\n const today = todayUtc();\n if (this.state.date !== today) {\n this.state = { date: today, spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };\n this.persist();\n }\n }\n\n private loadOrInit(): CostState {\n if (existsSync(this.statePath)) {\n const raw = readFileSync(this.statePath, 'utf8');\n const parsed = JSON.parse(raw) as CostState;\n if (parsed.date === todayUtc()) return parsed;\n }\n return { date: todayUtc(), spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };\n }\n\n private persist(): void {\n mkdirSync(dirname(this.statePath), { recursive: true });\n writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), 'utf8');\n }\n}\n\nfunction todayUtc(): string {\n return new Date().toISOString().slice(0, 10);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -27,11 +27,15 @@ interface CaelAuditRecord {
|
|
|
27
27
|
trust_epoch?: 'post-w107';
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Wraps a request body in a signed envelope for strict-mode endpoints (e.g. /team/:id/join). */
|
|
31
|
+
type RequestSigner = (body: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
30
32
|
interface HolomeshClientOptions {
|
|
31
33
|
apiBase: string;
|
|
32
34
|
bearer: string;
|
|
33
35
|
teamId: string;
|
|
34
36
|
fetchImpl?: typeof fetch;
|
|
37
|
+
/** EIP-191 signing function. When present, used on strict endpoints like joinTeam(). */
|
|
38
|
+
signer?: RequestSigner;
|
|
35
39
|
}
|
|
36
40
|
interface TeamMessage {
|
|
37
41
|
id: string;
|
|
@@ -46,7 +50,10 @@ declare class HolomeshClient {
|
|
|
46
50
|
private readonly bearer;
|
|
47
51
|
private readonly teamId;
|
|
48
52
|
private readonly fetchImpl;
|
|
53
|
+
private readonly signer?;
|
|
49
54
|
constructor(opts: HolomeshClientOptions);
|
|
55
|
+
/** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
|
|
56
|
+
private signBody;
|
|
50
57
|
heartbeat(payload: {
|
|
51
58
|
agentName: string;
|
|
52
59
|
surface: string;
|
|
@@ -109,4 +116,4 @@ declare class HolomeshClient {
|
|
|
109
116
|
declare function deriveSurface(seatName: string | undefined): string;
|
|
110
117
|
declare function pickClaimableTask(tasks: BoardTask[], brainCapabilityTags: string[]): BoardTask | undefined;
|
|
111
118
|
|
|
112
|
-
export { HolomeshClient, type HolomeshClientOptions, type TeamMessage, deriveSurface, pickClaimableTask };
|
|
119
|
+
export { HolomeshClient, type HolomeshClientOptions, type RequestSigner, type TeamMessage, deriveSurface, pickClaimableTask };
|
package/dist/holomesh-client.js
CHANGED
|
@@ -5,9 +5,14 @@ var HolomeshClient = class {
|
|
|
5
5
|
this.bearer = opts.bearer;
|
|
6
6
|
this.teamId = opts.teamId;
|
|
7
7
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
8
|
+
this.signer = opts.signer;
|
|
9
|
+
}
|
|
10
|
+
/** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */
|
|
11
|
+
async signBody(body) {
|
|
12
|
+
return this.signer ? await this.signer(body) : body;
|
|
8
13
|
}
|
|
9
14
|
async heartbeat(payload) {
|
|
10
|
-
await this.req("POST", `/team/${this.teamId}/presence`, payload);
|
|
15
|
+
await this.req("POST", `/team/${this.teamId}/presence`, await this.signBody(payload));
|
|
11
16
|
}
|
|
12
17
|
async getOpenTasks() {
|
|
13
18
|
const data = await this.req(
|
|
@@ -17,28 +22,33 @@ var HolomeshClient = class {
|
|
|
17
22
|
return data.tasks ?? data.open ?? [];
|
|
18
23
|
}
|
|
19
24
|
async claim(taskId) {
|
|
20
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, { action: "claim" });
|
|
25
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "claim" }));
|
|
21
26
|
}
|
|
22
27
|
async joinTeam() {
|
|
23
28
|
return this.req(
|
|
24
29
|
"POST",
|
|
25
30
|
`/team/${this.teamId}/join`,
|
|
26
|
-
{}
|
|
31
|
+
await this.signBody({})
|
|
27
32
|
);
|
|
28
33
|
}
|
|
29
34
|
async sendMessageOnTask(taskId, body) {
|
|
30
|
-
await this.req("POST", `/team/${this.teamId}/message`, {
|
|
35
|
+
await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({
|
|
31
36
|
to: "team",
|
|
32
37
|
subject: `task:${taskId}`,
|
|
33
38
|
content: body
|
|
34
|
-
});
|
|
39
|
+
}));
|
|
35
40
|
}
|
|
36
41
|
async markDone(taskId, summary, commitHash) {
|
|
37
|
-
await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
42
|
+
await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({
|
|
38
43
|
action: "done",
|
|
39
44
|
summary,
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
// verification_evidence required by server before task can be closed.
|
|
46
|
+
verification_evidence: summary,
|
|
47
|
+
// Exclude commitHash when undefined — JSON.stringify drops undefined but
|
|
48
|
+
// canonicalizeSigning preserves it as the literal string "undefined",
|
|
49
|
+
// causing a signature-mismatch vs what the server sees after JSON.parse.
|
|
50
|
+
...commitHash !== void 0 ? { commitHash } : {}
|
|
51
|
+
}));
|
|
42
52
|
}
|
|
43
53
|
// POST CAEL audit records for this agent. Server validator at
|
|
44
54
|
// packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
|
|
@@ -72,39 +82,28 @@ var HolomeshClient = class {
|
|
|
72
82
|
}
|
|
73
83
|
/** Post a message to the team feed. */
|
|
74
84
|
async sendTeamMessage(content, messageType = "text") {
|
|
75
|
-
await this.req("POST", `/team/${this.teamId}/message`, {
|
|
76
|
-
content,
|
|
77
|
-
type: messageType
|
|
78
|
-
});
|
|
85
|
+
await this.req("POST", `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));
|
|
79
86
|
}
|
|
80
87
|
// ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
|
|
81
88
|
/** Switch team mode. Requires owner or founder role. */
|
|
82
89
|
async setTeamMode(mode, reason) {
|
|
83
|
-
return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
|
|
90
|
+
return this.req("POST", `/team/${this.teamId}/mode`, await this.signBody({ mode, reason }));
|
|
84
91
|
}
|
|
85
92
|
/** Update room preferences. Requires config:write permission. */
|
|
86
93
|
async patchRoomPrefs(prefs) {
|
|
87
|
-
return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
|
|
94
|
+
return this.req("PATCH", `/team/${this.teamId}/room`, await this.signBody(prefs));
|
|
88
95
|
}
|
|
89
96
|
/** Update a board task. */
|
|
90
97
|
async updateTask(taskId, updates) {
|
|
91
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
92
|
-
action: "update",
|
|
93
|
-
...updates
|
|
94
|
-
});
|
|
98
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "update", ...updates }));
|
|
95
99
|
}
|
|
96
100
|
/** Delete a board task. */
|
|
97
101
|
async deleteTask(taskId) {
|
|
98
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
99
|
-
action: "delete"
|
|
100
|
-
});
|
|
102
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delete" }));
|
|
101
103
|
}
|
|
102
104
|
/** Delegate a board task to another agent. */
|
|
103
105
|
async delegateTask(taskId, toAgentId) {
|
|
104
|
-
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
|
|
105
|
-
action: "delegate",
|
|
106
|
-
toAgentId
|
|
107
|
-
});
|
|
106
|
+
return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: "delegate", toAgentId }));
|
|
108
107
|
}
|
|
109
108
|
async req(method, path, body) {
|
|
110
109
|
const url = `${this.apiBase}${path}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/holomesh-client.ts"],"sourcesContent":["import type { BoardTask } from './types.js';\nimport type { CaelAuditRecord } from './cael-builder.js';\n\nexport interface HolomeshClientOptions {\n apiBase: string;\n bearer: string;\n teamId: string;\n fetchImpl?: typeof fetch;\n}\n\nexport interface TeamMessage {\n id: string;\n fromAgentId: string;\n fromAgentName: string;\n content: string;\n messageType: string;\n createdAt: string;\n}\n\nexport class HolomeshClient {\n private readonly apiBase: string;\n private readonly bearer: string;\n private readonly teamId: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: HolomeshClientOptions) {\n this.apiBase = opts.apiBase.replace(/\\/$/, '');\n this.bearer = opts.bearer;\n this.teamId = opts.teamId;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n }\n\n async heartbeat(payload: { agentName: string; surface: string }): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/presence`, payload);\n }\n\n async getOpenTasks(): Promise<BoardTask[]> {\n const data = await this.req<{ tasks?: BoardTask[]; open?: BoardTask[] }>(\n 'GET',\n `/team/${this.teamId}/board`\n );\n return data.tasks ?? data.open ?? [];\n }\n\n async claim(taskId: string): Promise<BoardTask> {\n return this.req<BoardTask>('PATCH', `/team/${this.teamId}/board/${taskId}`, { action: 'claim' });\n }\n\n async joinTeam(): Promise<{ success: boolean; role?: string; members?: number }> {\n return this.req<{ success: boolean; role?: string; members?: number }>(\n 'POST',\n `/team/${this.teamId}/join`,\n {}\n );\n }\n\n async sendMessageOnTask(taskId: string, body: string): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, {\n to: 'team',\n subject: `task:${taskId}`,\n content: body,\n });\n }\n\n async markDone(taskId: string, summary: string, commitHash?: string): Promise<void> {\n await this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, {\n action: 'done',\n summary,\n commitHash,\n });\n }\n\n // POST CAEL audit records for this agent. Server validator at\n // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires\n // bearer == handle owner OR founder; the per-surface x402 bearer is the\n // handle owner so this resolves correctly. Records that fail shape\n // validation (layer_hashes != 7 elements, missing tick_iso/operation/\n // fnv1a_chain) are silently dropped server-side, not rejected as a batch.\n async postAuditRecords(handle: string, records: CaelAuditRecord[]): Promise<{ appended: number; rejected: number }> {\n return this.req<{ appended: number; rejected: number }>(\n 'POST',\n `/agent/${encodeURIComponent(handle)}/audit`,\n { records }\n );\n }\n\n async whoAmI(): Promise<{ agentId: string; surface: string; wallet?: string }> {\n // GET /api/holomesh/me returns { agentId, name, wallet, isFounder, teamId, teams, permissions }\n // (see packages/mcp-server/src/holomesh/routes/core-routes.ts §/me handler).\n // It does NOT return a `surface` field — derive it from the seat name on the\n // client side. Seat naming convention (set by the provisioning admin path):\n // claudecode-claude-x402 → claude-code\n // cursor-claude-x402 → claude-cursor\n // gemini-antigravity → gemini-antigravity\n // copilot-vscode → copilot-vscode\n // Founder → unknown (shared key, no surface attribution)\n const raw = await this.req<{\n agentId: string;\n name?: string;\n wallet?: string;\n }>('GET', '/me');\n return {\n agentId: raw.agentId,\n surface: deriveSurface(raw.name),\n wallet: raw.wallet,\n };\n }\n\n // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────\n\n /** Read recent team messages. */\n async getTeamMessages(limit = 20): Promise<TeamMessage[]> {\n const data = await this.req<{ messages?: TeamMessage[]; success?: boolean }>(\n 'GET',\n `/team/${this.teamId}/messages?limit=${limit}`\n );\n return data.messages ?? [];\n }\n\n /** Post a message to the team feed. */\n async sendTeamMessage(content: string, messageType = 'text'): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, {\n content,\n type: messageType,\n });\n }\n\n // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────\n\n /** Switch team mode. Requires owner or founder role. */\n async setTeamMode(mode: string, reason?: string): Promise<{ mode: string; unchanged?: boolean }> {\n return this.req('POST', `/team/${this.teamId}/mode`, { mode, reason });\n }\n\n /** Update room preferences. Requires config:write permission. */\n async patchRoomPrefs(prefs: { communicationStyle?: string; objective?: string }): Promise<{\n communicationStyle: string;\n objective: string;\n }> {\n return this.req('PATCH', `/team/${this.teamId}/room`, prefs);\n }\n\n /** Update a board task. */\n async updateTask(\n taskId: string,\n updates: {\n title?: string;\n description?: string;\n priority?: number;\n tags?: string[];\n }\n ): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, {\n action: 'update',\n ...updates,\n });\n }\n\n /** Delete a board task. */\n async deleteTask(taskId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, {\n action: 'delete',\n });\n }\n\n /** Delegate a board task to another agent. */\n async delegateTask(taskId: string, toAgentId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, {\n action: 'delegate',\n toAgentId,\n });\n }\n\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.apiBase}${path}`;\n // HoloMesh REST auth: server (packages/mcp-server/src/holomesh/auth-utils.ts\n // resolveRequestingAgent) accepts EITHER `Authorization: Bearer <token>`\n // (HTTP-standard, used here) OR `x-mcp-api-key: <token>` (orchestrator\n // convention). Both resolve through the same key-registry / agent-store /\n // env-fallback chain. Bearer is preferred for new code (W.087 vertex B,\n // task_1777073616424_klls).\n const res = await this.fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.bearer}`,\n 'content-type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!res.ok) {\n const txt = await res.text().catch(() => '');\n throw new Error(`HoloMesh ${method} ${path} ${res.status}: ${txt.slice(0, 300)}`);\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n\n/**\n * Derive a surface tag from a seat name returned by /me. Mirrors the surface\n * detection in scripts/probe-surface-bearers.mjs and hooks/lib/holomesh-env.mjs\n * so a single agent's surface attribution is consistent across read and write\n * paths. Returns 'unknown' when the seat name doesn't encode a surface\n * (e.g. shared-key resolution to \"Founder\").\n */\nexport function deriveSurface(seatName: string | undefined): string {\n if (!seatName) return 'unknown';\n const n = seatName.toLowerCase();\n if (n.startsWith('claudecode')) return 'claude-code';\n if (n.startsWith('cursor')) return 'claude-cursor';\n if (n.startsWith('claudedesktop')) return 'claude-desktop';\n if (n.startsWith('vscode-claude') || n.startsWith('claude-vscode')) return 'claude-vscode';\n if (n.startsWith('gemini')) return 'gemini-antigravity';\n if (n.startsWith('copilot')) return 'copilot-vscode';\n return 'unknown';\n}\n\nexport function pickClaimableTask(\n tasks: BoardTask[],\n brainCapabilityTags: string[]\n): BoardTask | undefined {\n const wanted = new Set(brainCapabilityTags.map((t) => t.toLowerCase()));\n const open = tasks.filter((t) => t.status === 'open' && !t.claimedBy);\n const scored = open\n .map((t) => ({ task: t, score: scoreTask(t, wanted) }))\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score || priority(a.task) - priority(b.task));\n return scored[0]?.task;\n}\n\nfunction scoreTask(task: BoardTask, wanted: Set<string>): number {\n const tags = (task.tags ?? []).map((t) => t.toLowerCase());\n const text = `${task.title} ${task.description ?? ''}`.toLowerCase();\n let score = 0;\n for (const tag of tags) if (wanted.has(tag)) score += 2;\n for (const w of wanted) if (text.includes(w)) score += 1;\n return score;\n}\n\nfunction priority(t: BoardTask): number {\n if (typeof t.priority === 'number') return t.priority;\n const map: Record<string, number> = { critical: 1, high: 2, medium: 4, low: 6 };\n return map[String(t.priority).toLowerCase()] ?? 5;\n}\n"],"mappings":";AAmBO,IAAM,iBAAN,MAAqB;AAAA,EAM1B,YAAY,MAA6B;AACvC,SAAK,UAAU,KAAK,QAAQ,QAAQ,OAAO,EAAE;AAC7C,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,KAAK;AACnB,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,MAAM,UAAU,SAAgE;AAC9E,UAAM,KAAK,IAAI,QAAQ,SAAS,KAAK,MAAM,aAAa,OAAO;AAAA,EACjE;AAAA,EAEA,MAAM,eAAqC;AACzC,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA,SAAS,KAAK,MAAM;AAAA,IACtB;AACA,WAAO,KAAK,SAAS,KAAK,QAAQ,CAAC;AAAA,EACrC;AAAA,EAEA,MAAM,MAAM,QAAoC;AAC9C,WAAO,KAAK,IAAe,SAAS,SAAS,KAAK,MAAM,UAAU,MAAM,IAAI,EAAE,QAAQ,QAAQ,CAAC;AAAA,EACjG;AAAA,EAEA,MAAM,WAA2E;AAC/E,WAAO,KAAK;AAAA,MACV;AAAA,MACA,SAAS,KAAK,MAAM;AAAA,MACpB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,QAAgB,MAA6B;AACnE,UAAM,KAAK,IAAI,QAAQ,SAAS,KAAK,MAAM,YAAY;AAAA,MACrD,IAAI;AAAA,MACJ,SAAS,QAAQ,MAAM;AAAA,MACvB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,QAAgB,SAAiB,YAAoC;AAClF,UAAM,KAAK,IAAI,SAAS,SAAS,KAAK,MAAM,UAAU,MAAM,IAAI;AAAA,MAC9D,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,QAAgB,SAA6E;AAClH,WAAO,KAAK;AAAA,MACV;AAAA,MACA,UAAU,mBAAmB,MAAM,CAAC;AAAA,MACpC,EAAE,QAAQ;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,SAAyE;AAU7E,UAAM,MAAM,MAAM,KAAK,IAIpB,OAAO,KAAK;AACf,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,SAAS,cAAc,IAAI,IAAI;AAAA,MAC/B,QAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,QAAQ,IAA4B;AACxD,UAAM,OAAO,MAAM,KAAK;AAAA,MACtB;AAAA,MACA,SAAS,KAAK,MAAM,mBAAmB,KAAK;AAAA,IAC9C;AACA,WAAO,KAAK,YAAY,CAAC;AAAA,EAC3B;AAAA;AAAA,EAGA,MAAM,gBAAgB,SAAiB,cAAc,QAAuB;AAC1E,UAAM,KAAK,IAAI,QAAQ,SAAS,KAAK,MAAM,YAAY;AAAA,MACrD;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,MAAc,QAAiE;AAC/F,WAAO,KAAK,IAAI,QAAQ,SAAS,KAAK,MAAM,SAAS,EAAE,MAAM,OAAO,CAAC;AAAA,EACvE;AAAA;AAAA,EAGA,MAAM,eAAe,OAGlB;AACD,WAAO,KAAK,IAAI,SAAS,SAAS,KAAK,MAAM,SAAS,KAAK;AAAA,EAC7D;AAAA;AAAA,EAGA,MAAM,WACJ,QACA,SAMkB;AAClB,WAAO,KAAK,IAAI,SAAS,SAAS,KAAK,MAAM,UAAU,MAAM,IAAI;AAAA,MAC/D,QAAQ;AAAA,MACR,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,WAAW,QAAkC;AACjD,WAAO,KAAK,IAAI,SAAS,SAAS,KAAK,MAAM,UAAU,MAAM,IAAI;AAAA,MAC/D,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,aAAa,QAAgB,WAAqC;AACtE,WAAO,KAAK,IAAI,SAAS,SAAS,KAAK,MAAM,UAAU,MAAM,IAAI;AAAA,MAC/D,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,IAAO,QAAgB,MAAc,MAA4B;AAC7E,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAOlC,UAAM,MAAM,MAAM,KAAK,UAAU,KAAK;AAAA,MACpC;AAAA,MACA,SAAS;AAAA,QACP,eAAe,UAAU,KAAK,MAAM;AAAA,QACpC,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,IACtC,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC3C,YAAM,IAAI,MAAM,YAAY,MAAM,IAAI,IAAI,IAAI,IAAI,MAAM,KAAK,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,IAClF;AACA,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AACF;AASO,SAAS,cAAc,UAAsC;AAClE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,IAAI,SAAS,YAAY;AAC/B,MAAI,EAAE,WAAW,YAAY,EAAG,QAAO;AACvC,MAAI,EAAE,WAAW,QAAQ,EAAG,QAAO;AACnC,MAAI,EAAE,WAAW,eAAe,EAAG,QAAO;AAC1C,MAAI,EAAE,WAAW,eAAe,KAAK,EAAE,WAAW,eAAe,EAAG,QAAO;AAC3E,MAAI,EAAE,WAAW,QAAQ,EAAG,QAAO;AACnC,MAAI,EAAE,WAAW,SAAS,EAAG,QAAO;AACpC,SAAO;AACT;AAEO,SAAS,kBACd,OACA,qBACuB;AACvB,QAAM,SAAS,IAAI,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AACtE,QAAM,OAAO,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,CAAC,EAAE,SAAS;AACpE,QAAM,SAAS,KACZ,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,UAAU,GAAG,MAAM,EAAE,EAAE,EACrD,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,SAAS,EAAE,IAAI,IAAI,SAAS,EAAE,IAAI,CAAC;AAC1E,SAAO,OAAO,CAAC,GAAG;AACpB;AAEA,SAAS,UAAU,MAAiB,QAA6B;AAC/D,QAAM,QAAQ,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AACzD,QAAM,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,eAAe,EAAE,GAAG,YAAY;AACnE,MAAI,QAAQ;AACZ,aAAW,OAAO,KAAM,KAAI,OAAO,IAAI,GAAG,EAAG,UAAS;AACtD,aAAW,KAAK,OAAQ,KAAI,KAAK,SAAS,CAAC,EAAG,UAAS;AACvD,SAAO;AACT;AAEA,SAAS,SAAS,GAAsB;AACtC,MAAI,OAAO,EAAE,aAAa,SAAU,QAAO,EAAE;AAC7C,QAAM,MAA8B,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,EAAE;AAC9E,SAAO,IAAI,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,KAAK;AAClD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/holomesh-client.ts"],"sourcesContent":["import type { BoardTask } from './types.js';\nimport type { CaelAuditRecord } from './cael-builder.js';\n\n/** Wraps a request body in a signed envelope for strict-mode endpoints (e.g. /team/:id/join). */\nexport type RequestSigner = (\n body: Record<string, unknown>\n) => Promise<Record<string, unknown>>;\n\nexport interface HolomeshClientOptions {\n apiBase: string;\n bearer: string;\n teamId: string;\n fetchImpl?: typeof fetch;\n /** EIP-191 signing function. When present, used on strict endpoints like joinTeam(). */\n signer?: RequestSigner;\n}\n\nexport interface TeamMessage {\n id: string;\n fromAgentId: string;\n fromAgentName: string;\n content: string;\n messageType: string;\n createdAt: string;\n}\n\nexport class HolomeshClient {\n private readonly apiBase: string;\n private readonly bearer: string;\n private readonly teamId: string;\n private readonly fetchImpl: typeof fetch;\n private readonly signer?: RequestSigner;\n\n constructor(opts: HolomeshClientOptions) {\n this.apiBase = opts.apiBase.replace(/\\/$/, '');\n this.bearer = opts.bearer;\n this.teamId = opts.teamId;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n this.signer = opts.signer;\n }\n\n /** Wrap body in a signed envelope when a signer is available (strict-mode endpoints). */\n private async signBody(body: Record<string, unknown>): Promise<Record<string, unknown>> {\n return this.signer ? await this.signer(body) : body;\n }\n\n async heartbeat(payload: { agentName: string; surface: string }): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/presence`, await this.signBody(payload as Record<string, unknown>));\n }\n\n async getOpenTasks(): Promise<BoardTask[]> {\n const data = await this.req<{ tasks?: BoardTask[]; open?: BoardTask[] }>(\n 'GET',\n `/team/${this.teamId}/board`\n );\n return data.tasks ?? data.open ?? [];\n }\n\n async claim(taskId: string): Promise<BoardTask> {\n return this.req<BoardTask>('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'claim' }));\n }\n\n async joinTeam(): Promise<{ success: boolean; role?: string; members?: number }> {\n return this.req<{ success: boolean; role?: string; members?: number }>(\n 'POST',\n `/team/${this.teamId}/join`,\n await this.signBody({})\n );\n }\n\n async sendMessageOnTask(taskId: string, body: string): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({\n to: 'team',\n subject: `task:${taskId}`,\n content: body,\n }));\n }\n\n async markDone(taskId: string, summary: string, commitHash?: string): Promise<void> {\n await this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({\n action: 'done',\n summary,\n // verification_evidence required by server before task can be closed.\n verification_evidence: summary,\n // Exclude commitHash when undefined — JSON.stringify drops undefined but\n // canonicalizeSigning preserves it as the literal string \"undefined\",\n // causing a signature-mismatch vs what the server sees after JSON.parse.\n ...(commitHash !== undefined ? { commitHash } : {}),\n }));\n }\n\n // POST CAEL audit records for this agent. Server validator at\n // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires\n // bearer == handle owner OR founder; the per-surface x402 bearer is the\n // handle owner so this resolves correctly. Records that fail shape\n // validation (layer_hashes != 7 elements, missing tick_iso/operation/\n // fnv1a_chain) are silently dropped server-side, not rejected as a batch.\n async postAuditRecords(\n handle: string,\n records: CaelAuditRecord[]\n ): Promise<{ appended: number; rejected: number }> {\n // Audit endpoint uses bearer-only auth — no signed envelope wrapper.\n return this.req<{ appended: number; rejected: number }>(\n 'POST',\n `/agent/${encodeURIComponent(handle)}/audit`,\n { records } as unknown as Record<string, unknown>\n );\n }\n\n async whoAmI(): Promise<{ agentId: string; surface: string; wallet?: string }> {\n // GET /api/holomesh/me returns { agentId, name, wallet, isFounder, teamId, teams, permissions }\n // (see packages/mcp-server/src/holomesh/routes/core-routes.ts §/me handler).\n // It does NOT return a `surface` field — derive it from the seat name on the\n // client side. Seat naming convention (set by the provisioning admin path):\n // claudecode-claude-x402 → claude-code\n // cursor-claude-x402 → claude-cursor\n // gemini-antigravity → gemini-antigravity\n // copilot-vscode → copilot-vscode\n // Founder → unknown (shared key, no surface attribution)\n const raw = await this.req<{\n agentId: string;\n name?: string;\n wallet?: string;\n }>('GET', '/me');\n return {\n agentId: raw.agentId,\n surface: deriveSurface(raw.name),\n wallet: raw.wallet,\n };\n }\n\n // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────\n\n /** Read recent team messages. */\n async getTeamMessages(limit = 20): Promise<TeamMessage[]> {\n const data = await this.req<{ messages?: TeamMessage[]; success?: boolean }>(\n 'GET',\n `/team/${this.teamId}/messages?limit=${limit}`\n );\n return data.messages ?? [];\n }\n\n /** Post a message to the team feed. */\n async sendTeamMessage(content: string, messageType = 'text'): Promise<void> {\n await this.req('POST', `/team/${this.teamId}/message`, await this.signBody({ content, type: messageType }));\n }\n\n // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────\n\n /** Switch team mode. Requires owner or founder role. */\n async setTeamMode(mode: string, reason?: string): Promise<{ mode: string; unchanged?: boolean }> {\n return this.req('POST', `/team/${this.teamId}/mode`, await this.signBody({ mode, reason } as Record<string, unknown>));\n }\n\n /** Update room preferences. Requires config:write permission. */\n async patchRoomPrefs(prefs: { communicationStyle?: string; objective?: string }): Promise<{\n communicationStyle: string;\n objective: string;\n }> {\n return this.req('PATCH', `/team/${this.teamId}/room`, await this.signBody(prefs as Record<string, unknown>));\n }\n\n /** Update a board task. */\n async updateTask(\n taskId: string,\n updates: {\n title?: string;\n description?: string;\n priority?: number;\n tags?: string[];\n }\n ): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'update', ...updates } as Record<string, unknown>));\n }\n\n /** Delete a board task. */\n async deleteTask(taskId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delete' }));\n }\n\n /** Delegate a board task to another agent. */\n async delegateTask(taskId: string, toAgentId: string): Promise<unknown> {\n return this.req('PATCH', `/team/${this.teamId}/board/${taskId}`, await this.signBody({ action: 'delegate', toAgentId }));\n }\n\n private async req<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.apiBase}${path}`;\n // HoloMesh REST auth: server (packages/mcp-server/src/holomesh/auth-utils.ts\n // resolveRequestingAgent) accepts EITHER `Authorization: Bearer <token>`\n // (HTTP-standard, used here) OR `x-mcp-api-key: <token>` (orchestrator\n // convention). Both resolve through the same key-registry / agent-store /\n // env-fallback chain. Bearer is preferred for new code (W.087 vertex B,\n // task_1777073616424_klls).\n const res = await this.fetchImpl(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.bearer}`,\n 'content-type': 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!res.ok) {\n const txt = await res.text().catch(() => '');\n throw new Error(`HoloMesh ${method} ${path} ${res.status}: ${txt.slice(0, 300)}`);\n }\n if (res.status === 204) return undefined as T;\n return (await res.json()) as T;\n }\n}\n\n/**\n * Derive a surface tag from a seat name returned by /me. Mirrors the surface\n * detection in scripts/probe-surface-bearers.mjs and hooks/lib/holomesh-env.mjs\n * so a single agent's surface attribution is consistent across read and write\n * paths. Returns 'unknown' when the seat name doesn't encode a surface\n * (e.g. shared-key resolution to \"Founder\").\n */\nexport function deriveSurface(seatName: string | undefined): string {\n if (!seatName) return 'unknown';\n const n = seatName.toLowerCase();\n if (n.startsWith('claudecode')) return 'claude-code';\n if (n.startsWith('cursor')) return 'claude-cursor';\n if (n.startsWith('claudedesktop')) return 'claude-desktop';\n if (n.startsWith('vscode-claude') || n.startsWith('claude-vscode')) return 'claude-vscode';\n if (n.startsWith('gemini')) return 'gemini-antigravity';\n if (n.startsWith('copilot')) return 'copilot-vscode';\n return 'unknown';\n}\n\nexport function pickClaimableTask(\n tasks: BoardTask[],\n brainCapabilityTags: string[]\n): BoardTask | undefined {\n const wanted = new Set(brainCapabilityTags.map((t) => t.toLowerCase()));\n const open = tasks.filter((t) => t.status === 'open' && !t.claimedBy);\n const scored = open\n .map((t) => ({ task: t, score: scoreTask(t, wanted) }))\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score || priority(a.task) - priority(b.task));\n return scored[0]?.task;\n}\n\nfunction scoreTask(task: BoardTask, wanted: Set<string>): number {\n const tags = (task.tags ?? []).map((t) => t.toLowerCase());\n const text = `${task.title} ${task.description ?? ''}`.toLowerCase();\n let score = 0;\n for (const tag of tags) if (wanted.has(tag)) score += 2;\n for (const w of wanted) if (text.includes(w)) score += 1;\n return score;\n}\n\nfunction priority(t: BoardTask): number {\n if (typeof t.priority === 'number') return t.priority;\n const map: Record<string, number> = { critical: 1, high: 2, medium: 4, low: 6 };\n return map[String(t.priority).toLowerCase()] ?? 5;\n}\n"],"mappings":"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","names":[]}
|