@holoscript/holoscript-agent 2.0.0

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.
@@ -0,0 +1,103 @@
1
+ // src/commit-hook.ts
2
+ import { mkdirSync, writeFileSync } from "fs";
3
+ import { dirname, join, resolve } from "path";
4
+ import { spawnSync } from "child_process";
5
+ var SAFE_HANDLE = /^[a-z0-9_-]{1,64}$/i;
6
+ function makeCommitHook(opts) {
7
+ if (!opts.outputDir || opts.outputDir.trim().length === 0) {
8
+ throw new Error("CommitHookOptions.outputDir is required");
9
+ }
10
+ const spawn = opts.spawn ?? spawnSync;
11
+ const cwd = opts.workingDir ?? process.cwd();
12
+ const outputDir = resolve(cwd, opts.outputDir);
13
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
14
+ const scope = opts.scope ?? "agent";
15
+ return async (result, task, identity) => {
16
+ if (!SAFE_HANDLE.test(identity.handle)) {
17
+ throw new Error(`Refusing to commit: handle "${identity.handle}" must match ${SAFE_HANDLE}`);
18
+ }
19
+ const date = now().toISOString().slice(0, 10);
20
+ const safeTaskId = task.id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
21
+ const fileName = `${date}_${safeTaskId}_${identity.handle}.md`;
22
+ const filePath = join(outputDir, fileName);
23
+ mkdirSync(dirname(filePath), { recursive: true });
24
+ writeFileSync(filePath, renderMemo(result, task, identity, date), "utf8");
25
+ const relPath = relativeTo(cwd, filePath);
26
+ const addRes = spawn("git", ["add", relPath], { cwd, encoding: "utf8" });
27
+ if (addRes.status !== 0) {
28
+ throw new Error(`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`);
29
+ }
30
+ const message = renderCommitMessage({ scope, task, identity, result });
31
+ const commitArgs = ["commit", "-m", message];
32
+ if (opts.authorName && opts.authorEmail) {
33
+ commitArgs.push("--author", `${opts.authorName} <${opts.authorEmail}>`);
34
+ }
35
+ const commitRes = spawn("git", commitArgs, { cwd, encoding: "utf8" });
36
+ if (commitRes.status !== 0) {
37
+ throw new Error(`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`);
38
+ }
39
+ const hashRes = spawn("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
40
+ const commitHash = hashRes.status === 0 ? hashRes.stdout.trim() : void 0;
41
+ return { filePath, commitHash, staged: [relPath], message };
42
+ };
43
+ }
44
+ function renderMemo(result, task, identity, date) {
45
+ return [
46
+ "---",
47
+ `title: "${task.title.replace(/"/g, "'")}"`,
48
+ `task_id: ${task.id}`,
49
+ `agent: ${identity.handle}`,
50
+ `surface: ${identity.surface}`,
51
+ `provider: ${identity.llmProvider}`,
52
+ `model: ${identity.llmModel}`,
53
+ `wallet: ${identity.wallet}`,
54
+ `date: ${date}`,
55
+ `tokens: ${result.usage.totalTokens}`,
56
+ `cost_usd: ${result.costUsd.toFixed(4)}`,
57
+ `duration_ms: ${result.durationMs}`,
58
+ `tags: [${(task.tags ?? []).map((t) => JSON.stringify(t)).join(", ")}]`,
59
+ "---",
60
+ "",
61
+ `# ${task.title}`,
62
+ "",
63
+ "## Task description",
64
+ "",
65
+ task.description ?? "(no description)",
66
+ "",
67
+ "## Agent response",
68
+ "",
69
+ result.responseText.trim(),
70
+ ""
71
+ ].join("\n");
72
+ }
73
+ var SUBJECT_MAX = 72;
74
+ function renderCommitMessage(opts) {
75
+ const suffix = ` [agent:${opts.identity.handle}]`;
76
+ const prefix = `${opts.scope}: `;
77
+ const titleBudget = Math.max(8, SUBJECT_MAX - prefix.length - suffix.length);
78
+ const subject = `${prefix}${truncate(opts.task.title, titleBudget)}${suffix}`;
79
+ const body = [
80
+ "",
81
+ `task: ${opts.task.id}`,
82
+ `agent: ${opts.identity.handle} (${opts.identity.llmProvider}/${opts.identity.llmModel})`,
83
+ `wallet: ${opts.identity.wallet}`,
84
+ `cost: $${opts.result.costUsd.toFixed(4)} / ${opts.result.usage.totalTokens} tok / ${opts.result.durationMs}ms`
85
+ ].join("\n");
86
+ return `${subject}
87
+ ${body}
88
+ `;
89
+ }
90
+ function truncate(s, max) {
91
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
92
+ }
93
+ function relativeTo(base, target) {
94
+ const b = base.replace(/\\/g, "/");
95
+ const t = target.replace(/\\/g, "/");
96
+ if (t.startsWith(b + "/")) return t.slice(b.length + 1);
97
+ if (t === b) return ".";
98
+ return t;
99
+ }
100
+ export {
101
+ makeCommitHook
102
+ };
103
+ //# sourceMappingURL=commit-hook.js.map
@@ -0,0 +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, task: BoardTask, identity: AgentIdentity): 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(`git add failed: ${addRes.stderr || addRes.stdout || `exit ${addRes.status}`}`);\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(`git commit failed: ${commitRes.stderr || commitRes.stdout || `exit ${commitRes.status}`}`);\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(result: ExecutionResult, task: BoardTask, identity: AgentIdentity, date: string): 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":[]}
@@ -0,0 +1,54 @@
1
+ import { TokenUsage } from '@holoscript/llm-provider';
2
+ import { ModelPricer, CostState } from './types.js';
3
+
4
+ declare const ANTHROPIC_PRICING_USD_PER_MTOK: Record<string, {
5
+ input: number;
6
+ output: number;
7
+ }>;
8
+ declare function defaultAnthropicPricer(model: string, usage: TokenUsage): number;
9
+ /**
10
+ * Pricer for local-llm providers (vLLM-on-GPU). The compute cost is the
11
+ * Vast.ai (or other GPU) hourly rental, NOT per-token. From the agent's
12
+ * perspective each LLM call has $0 marginal cost — the budget guard for
13
+ * local-llm should track tick count or wall-clock time, not tokens.
14
+ *
15
+ * Returns 0 unconditionally. Token counts are still recorded in CostState
16
+ * so usage analytics work, but cost-guard never trips on token spend.
17
+ */
18
+ declare function defaultLocalLlmPricer(_model: string, _usage: TokenUsage): number;
19
+ /**
20
+ * Provider-aware default pricer dispatch. Picks the right pricer by
21
+ * provider so the holoscript-agent runtime works for both Anthropic
22
+ * (per-token billing) and local-llm (compute already paid via GPU
23
+ * rental) without a custom pricer at every call site.
24
+ *
25
+ * Refs: 2026-04-26 mw02 boot loop — local-llm workers tick-erroring with
26
+ * "No pricing configured for model 'Qwen/Qwen2.5-0.5B-Instruct'" because
27
+ * defaultAnthropicPricer was wired in for ALL providers regardless of
28
+ * which LLM the agent uses.
29
+ */
30
+ declare function defaultPricerForProvider(provider: 'anthropic' | 'local-llm' | 'openai' | string): ModelPricer;
31
+ declare class CostGuard {
32
+ private state;
33
+ private readonly statePath;
34
+ private readonly dailyBudgetUsd;
35
+ private readonly pricer;
36
+ constructor(opts: {
37
+ statePath: string;
38
+ dailyBudgetUsd: number;
39
+ pricer?: ModelPricer;
40
+ });
41
+ recordUsage(model: string, usage: TokenUsage): {
42
+ costUsd: number;
43
+ spentUsd: number;
44
+ remainingUsd: number;
45
+ };
46
+ isOverBudget(): boolean;
47
+ getRemainingUsd(): number;
48
+ getState(): Readonly<CostState>;
49
+ private rolloverIfNewDay;
50
+ private loadOrInit;
51
+ private persist;
52
+ }
53
+
54
+ export { ANTHROPIC_PRICING_USD_PER_MTOK, CostGuard, defaultAnthropicPricer, defaultLocalLlmPricer, defaultPricerForProvider };
@@ -0,0 +1,92 @@
1
+ // src/cost-guard.ts
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
3
+ import { dirname } from "path";
4
+ var ANTHROPIC_PRICING_USD_PER_MTOK = {
5
+ "claude-opus-4-7": { input: 15, output: 75 },
6
+ "claude-opus-4-6": { input: 15, output: 75 },
7
+ "claude-sonnet-4-6": { input: 3, output: 15 },
8
+ "claude-haiku-4-5-20251001": { input: 1, output: 5 },
9
+ "claude-haiku-4-5": { input: 1, output: 5 }
10
+ };
11
+ function defaultAnthropicPricer(model, usage) {
12
+ const price = ANTHROPIC_PRICING_USD_PER_MTOK[model];
13
+ if (!price) {
14
+ throw new Error(
15
+ `No pricing configured for model "${model}" \u2014 add to ANTHROPIC_PRICING_USD_PER_MTOK or pass a custom pricer`
16
+ );
17
+ }
18
+ return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1e6;
19
+ }
20
+ function defaultLocalLlmPricer(_model, _usage) {
21
+ return 0;
22
+ }
23
+ function defaultPricerForProvider(provider) {
24
+ if (provider === "local-llm" || provider === "mock") return defaultLocalLlmPricer;
25
+ return defaultAnthropicPricer;
26
+ }
27
+ var CostGuard = class {
28
+ constructor(opts) {
29
+ this.statePath = opts.statePath;
30
+ this.dailyBudgetUsd = opts.dailyBudgetUsd;
31
+ this.pricer = opts.pricer ?? defaultAnthropicPricer;
32
+ this.state = this.loadOrInit();
33
+ }
34
+ recordUsage(model, usage) {
35
+ this.rolloverIfNewDay();
36
+ const costUsd = this.pricer(model, usage);
37
+ this.state.spentUsd += costUsd;
38
+ this.state.promptTokens += usage.promptTokens;
39
+ this.state.completionTokens += usage.completionTokens;
40
+ this.state.callCount += 1;
41
+ this.persist();
42
+ return {
43
+ costUsd,
44
+ spentUsd: this.state.spentUsd,
45
+ remainingUsd: Math.max(0, this.dailyBudgetUsd - this.state.spentUsd)
46
+ };
47
+ }
48
+ isOverBudget() {
49
+ if (this.dailyBudgetUsd === 0) return false;
50
+ this.rolloverIfNewDay();
51
+ return this.state.spentUsd >= this.dailyBudgetUsd;
52
+ }
53
+ getRemainingUsd() {
54
+ if (this.dailyBudgetUsd === 0) return Number.POSITIVE_INFINITY;
55
+ this.rolloverIfNewDay();
56
+ return Math.max(0, this.dailyBudgetUsd - this.state.spentUsd);
57
+ }
58
+ getState() {
59
+ this.rolloverIfNewDay();
60
+ return { ...this.state };
61
+ }
62
+ rolloverIfNewDay() {
63
+ const today = todayUtc();
64
+ if (this.state.date !== today) {
65
+ this.state = { date: today, spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };
66
+ this.persist();
67
+ }
68
+ }
69
+ loadOrInit() {
70
+ if (existsSync(this.statePath)) {
71
+ const raw = readFileSync(this.statePath, "utf8");
72
+ const parsed = JSON.parse(raw);
73
+ if (parsed.date === todayUtc()) return parsed;
74
+ }
75
+ return { date: todayUtc(), spentUsd: 0, promptTokens: 0, completionTokens: 0, callCount: 0 };
76
+ }
77
+ persist() {
78
+ mkdirSync(dirname(this.statePath), { recursive: true });
79
+ writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), "utf8");
80
+ }
81
+ };
82
+ function todayUtc() {
83
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
84
+ }
85
+ export {
86
+ ANTHROPIC_PRICING_USD_PER_MTOK,
87
+ CostGuard,
88
+ defaultAnthropicPricer,
89
+ defaultLocalLlmPricer,
90
+ defaultPricerForProvider
91
+ };
92
+ //# sourceMappingURL=cost-guard.js.map
@@ -0,0 +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: 15, output: 75 },\n 'claude-opus-4-6': { input: 15, output: 75 },\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/**\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 */\nexport function defaultPricerForProvider(\n provider: 'anthropic' | 'local-llm' | 'openai' | string\n): ModelPricer {\n if (provider === 'local-llm' || provider === 'mock') return defaultLocalLlmPricer;\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(model: string, usage: TokenUsage): { 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":[]}
@@ -0,0 +1,63 @@
1
+ import { BoardTask } from './types.js';
2
+ import '@holoscript/llm-provider';
3
+
4
+ interface CaelAuditRecord {
5
+ tick_iso: string;
6
+ layer_hashes: string[];
7
+ operation: string;
8
+ prev_hash: string | null;
9
+ fnv1a_chain: string;
10
+ version_vector_fingerprint: string;
11
+ brain_class?: string;
12
+ trial?: number;
13
+ attack_class?: string;
14
+ defense_state?: string;
15
+ }
16
+
17
+ interface HolomeshClientOptions {
18
+ apiBase: string;
19
+ bearer: string;
20
+ teamId: string;
21
+ fetchImpl?: typeof fetch;
22
+ }
23
+ declare class HolomeshClient {
24
+ private readonly apiBase;
25
+ private readonly bearer;
26
+ private readonly teamId;
27
+ private readonly fetchImpl;
28
+ constructor(opts: HolomeshClientOptions);
29
+ heartbeat(payload: {
30
+ agentName: string;
31
+ surface: string;
32
+ }): Promise<void>;
33
+ getOpenTasks(): Promise<BoardTask[]>;
34
+ claim(taskId: string): Promise<BoardTask>;
35
+ joinTeam(): Promise<{
36
+ success: boolean;
37
+ role?: string;
38
+ members?: number;
39
+ }>;
40
+ sendMessageOnTask(taskId: string, body: string): Promise<void>;
41
+ markDone(taskId: string, summary: string, commitHash?: string): Promise<void>;
42
+ postAuditRecords(handle: string, records: CaelAuditRecord[]): Promise<{
43
+ appended: number;
44
+ rejected: number;
45
+ }>;
46
+ whoAmI(): Promise<{
47
+ agentId: string;
48
+ surface: string;
49
+ wallet?: string;
50
+ }>;
51
+ private req;
52
+ }
53
+ /**
54
+ * Derive a surface tag from a seat name returned by /me. Mirrors the surface
55
+ * detection in scripts/probe-surface-bearers.mjs and hooks/lib/holomesh-env.mjs
56
+ * so a single agent's surface attribution is consistent across read and write
57
+ * paths. Returns 'unknown' when the seat name doesn't encode a surface
58
+ * (e.g. shared-key resolution to "Founder").
59
+ */
60
+ declare function deriveSurface(seatName: string | undefined): string;
61
+ declare function pickClaimableTask(tasks: BoardTask[], brainCapabilityTags: string[]): BoardTask | undefined;
62
+
63
+ export { HolomeshClient, type HolomeshClientOptions, deriveSurface, pickClaimableTask };
@@ -0,0 +1,117 @@
1
+ // src/holomesh-client.ts
2
+ var HolomeshClient = class {
3
+ constructor(opts) {
4
+ this.apiBase = opts.apiBase.replace(/\/$/, "");
5
+ this.bearer = opts.bearer;
6
+ this.teamId = opts.teamId;
7
+ this.fetchImpl = opts.fetchImpl ?? fetch;
8
+ }
9
+ async heartbeat(payload) {
10
+ await this.req("POST", `/team/${this.teamId}/presence`, payload);
11
+ }
12
+ async getOpenTasks() {
13
+ const data = await this.req(
14
+ "GET",
15
+ `/team/${this.teamId}/board`
16
+ );
17
+ return data.tasks ?? data.open ?? [];
18
+ }
19
+ async claim(taskId) {
20
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, { action: "claim" });
21
+ }
22
+ async joinTeam() {
23
+ return this.req(
24
+ "POST",
25
+ `/team/${this.teamId}/join`,
26
+ {}
27
+ );
28
+ }
29
+ async sendMessageOnTask(taskId, body) {
30
+ await this.req("POST", `/team/${this.teamId}/message`, {
31
+ to: "team",
32
+ subject: `task:${taskId}`,
33
+ content: body
34
+ });
35
+ }
36
+ async markDone(taskId, summary, commitHash) {
37
+ await this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
38
+ action: "done",
39
+ summary,
40
+ commitHash
41
+ });
42
+ }
43
+ // POST CAEL audit records for this agent. Server validator at
44
+ // packages/mcp-server/src/holomesh/routes/core-routes.ts:472-533 requires
45
+ // bearer == handle owner OR founder; the per-surface x402 bearer is the
46
+ // handle owner so this resolves correctly. Records that fail shape
47
+ // validation (layer_hashes != 7 elements, missing tick_iso/operation/
48
+ // fnv1a_chain) are silently dropped server-side, not rejected as a batch.
49
+ async postAuditRecords(handle, records) {
50
+ return this.req(
51
+ "POST",
52
+ `/agent/${encodeURIComponent(handle)}/audit`,
53
+ { records }
54
+ );
55
+ }
56
+ async whoAmI() {
57
+ const raw = await this.req("GET", "/me");
58
+ return {
59
+ agentId: raw.agentId,
60
+ surface: deriveSurface(raw.name),
61
+ wallet: raw.wallet
62
+ };
63
+ }
64
+ async req(method, path, body) {
65
+ const url = `${this.apiBase}${path}`;
66
+ const res = await this.fetchImpl(url, {
67
+ method,
68
+ headers: {
69
+ Authorization: `Bearer ${this.bearer}`,
70
+ "content-type": "application/json"
71
+ },
72
+ body: body ? JSON.stringify(body) : void 0
73
+ });
74
+ if (!res.ok) {
75
+ const txt = await res.text().catch(() => "");
76
+ throw new Error(`HoloMesh ${method} ${path} ${res.status}: ${txt.slice(0, 300)}`);
77
+ }
78
+ if (res.status === 204) return void 0;
79
+ return await res.json();
80
+ }
81
+ };
82
+ function deriveSurface(seatName) {
83
+ if (!seatName) return "unknown";
84
+ const n = seatName.toLowerCase();
85
+ if (n.startsWith("claudecode")) return "claude-code";
86
+ if (n.startsWith("cursor")) return "claude-cursor";
87
+ if (n.startsWith("claudedesktop")) return "claude-desktop";
88
+ if (n.startsWith("vscode-claude") || n.startsWith("claude-vscode")) return "claude-vscode";
89
+ if (n.startsWith("gemini")) return "gemini-antigravity";
90
+ if (n.startsWith("copilot")) return "copilot-vscode";
91
+ return "unknown";
92
+ }
93
+ function pickClaimableTask(tasks, brainCapabilityTags) {
94
+ const wanted = new Set(brainCapabilityTags.map((t) => t.toLowerCase()));
95
+ const open = tasks.filter((t) => t.status === "open" && !t.claimedBy);
96
+ const scored = open.map((t) => ({ task: t, score: scoreTask(t, wanted) })).filter((s) => s.score > 0).sort((a, b) => b.score - a.score || priority(a.task) - priority(b.task));
97
+ return scored[0]?.task;
98
+ }
99
+ function scoreTask(task, wanted) {
100
+ const tags = (task.tags ?? []).map((t) => t.toLowerCase());
101
+ const text = `${task.title} ${task.description ?? ""}`.toLowerCase();
102
+ let score = 0;
103
+ for (const tag of tags) if (wanted.has(tag)) score += 2;
104
+ for (const w of wanted) if (text.includes(w)) score += 1;
105
+ return score;
106
+ }
107
+ function priority(t) {
108
+ if (typeof t.priority === "number") return t.priority;
109
+ const map = { critical: 1, high: 2, medium: 4, low: 6 };
110
+ return map[String(t.priority).toLowerCase()] ?? 5;
111
+ }
112
+ export {
113
+ HolomeshClient,
114
+ deriveSurface,
115
+ pickClaimableTask
116
+ };
117
+ //# sourceMappingURL=holomesh-client.js.map
@@ -0,0 +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 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 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":[]}
@@ -0,0 +1,7 @@
1
+ import { AgentIdentity } from './types.js';
2
+ import '@holoscript/llm-provider';
3
+
4
+ declare function loadIdentity(env?: NodeJS.ProcessEnv): AgentIdentity;
5
+ declare function identityForLog(id: AgentIdentity): Record<string, string | number>;
6
+
7
+ export { identityForLog, loadIdentity };
@@ -0,0 +1,64 @@
1
+ // src/identity.ts
2
+ var VALID_PROVIDERS = /* @__PURE__ */ new Set([
3
+ "anthropic",
4
+ "openai",
5
+ "gemini",
6
+ "mock",
7
+ "bitnet",
8
+ "local-llm"
9
+ ]);
10
+ function loadIdentity(env = process.env) {
11
+ const handle = required(env, "HOLOSCRIPT_AGENT_HANDLE");
12
+ const provider = required(env, "HOLOSCRIPT_AGENT_PROVIDER");
13
+ if (!VALID_PROVIDERS.has(provider)) {
14
+ throw new Error(
15
+ `HOLOSCRIPT_AGENT_PROVIDER=${provider} not in [${[...VALID_PROVIDERS].join(", ")}]`
16
+ );
17
+ }
18
+ const x402Bearer = required(env, "HOLOSCRIPT_AGENT_X402_BEARER");
19
+ const wallet = required(env, "HOLOSCRIPT_AGENT_WALLET");
20
+ if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {
21
+ throw new Error(`HOLOSCRIPT_AGENT_WALLET is not a 0x-prefixed 40-hex address: ${wallet}`);
22
+ }
23
+ const budgetRaw = env.HOLOSCRIPT_AGENT_BUDGET_USD_DAY ?? "5";
24
+ const budget = Number(budgetRaw);
25
+ if (!Number.isFinite(budget) || budget < 0) {
26
+ throw new Error(`HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`);
27
+ }
28
+ return {
29
+ handle,
30
+ surface: env.HOLOSCRIPT_AGENT_SURFACE ?? handle,
31
+ wallet,
32
+ x402Bearer,
33
+ llmProvider: provider,
34
+ llmModel: required(env, "HOLOSCRIPT_AGENT_MODEL"),
35
+ brainPath: required(env, "HOLOSCRIPT_AGENT_BRAIN"),
36
+ budgetUsdPerDay: budget,
37
+ teamId: required(env, "HOLOMESH_TEAM_ID"),
38
+ meshApiBase: env.HOLOMESH_API_BASE ?? "https://mcp.holoscript.net/api/holomesh"
39
+ };
40
+ }
41
+ function required(env, key) {
42
+ const v = env[key];
43
+ if (!v || v.trim().length === 0) {
44
+ throw new Error(`Missing required env var: ${key}`);
45
+ }
46
+ return v.trim();
47
+ }
48
+ function identityForLog(id) {
49
+ return {
50
+ handle: id.handle,
51
+ surface: id.surface,
52
+ wallet: `${id.wallet.slice(0, 6)}\u2026${id.wallet.slice(-4)}`,
53
+ bearer: `${id.x402Bearer.slice(0, 6)}\u2026`,
54
+ provider: id.llmProvider,
55
+ model: id.llmModel,
56
+ brain: id.brainPath,
57
+ budgetUsdPerDay: id.budgetUsdPerDay
58
+ };
59
+ }
60
+ export {
61
+ identityForLog,
62
+ loadIdentity
63
+ };
64
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +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 '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(`HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`);\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;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,MAAM,qEAAqE,SAAS,EAAE;AAAA,EAClG;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":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node