@holoscript/holoscript-agent 2.0.0 → 2.0.1

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/dist/index.js CHANGED
@@ -8,7 +8,9 @@ import {
8
8
  createOpenAIProvider,
9
9
  createGeminiProvider,
10
10
  createMockProvider,
11
- createLocalLLMProvider
11
+ createLocalLLMProvider,
12
+ createXAIProvider,
13
+ createOpenRouterProvider
12
14
  } from "@holoscript/llm-provider";
13
15
 
14
16
  // src/identity.ts
@@ -16,6 +18,8 @@ var VALID_PROVIDERS = /* @__PURE__ */ new Set([
16
18
  "anthropic",
17
19
  "openai",
18
20
  "gemini",
21
+ "xai",
22
+ "openrouter",
19
23
  "mock",
20
24
  "bitnet",
21
25
  "local-llm"
@@ -36,7 +40,9 @@ function loadIdentity(env = process.env) {
36
40
  const budgetRaw = env.HOLOSCRIPT_AGENT_BUDGET_USD_DAY ?? "5";
37
41
  const budget = Number(budgetRaw);
38
42
  if (!Number.isFinite(budget) || budget < 0) {
39
- throw new Error(`HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`);
43
+ throw new Error(
44
+ `HOLOSCRIPT_AGENT_BUDGET_USD_DAY must be >= 0 (0 = unlimited), got ${budgetRaw}`
45
+ );
40
46
  }
41
47
  return {
42
48
  handle,
@@ -75,15 +81,29 @@ function identityForLog(id) {
75
81
  import { readFile } from "fs/promises";
76
82
  async function loadBrain(brainPath, scopeTier = "warm") {
77
83
  const systemPrompt = await readFile(brainPath, "utf8");
78
- const { domain, capabilityTags } = extractIdentity(systemPrompt);
79
- return { brainPath, systemPrompt, capabilityTags, domain, scopeTier };
84
+ const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(systemPrompt);
85
+ return {
86
+ brainPath,
87
+ systemPrompt,
88
+ capabilityTags,
89
+ domain,
90
+ scopeTier,
91
+ requires,
92
+ prefers,
93
+ avoids
94
+ };
80
95
  }
81
96
  function extractIdentity(brain) {
82
97
  const identityBlock = sliceNamedBlock(brain, "identity");
83
- if (!identityBlock) return { domain: "unknown", capabilityTags: [] };
98
+ if (!identityBlock) {
99
+ return { domain: "unknown", capabilityTags: [], requires: [], prefers: [], avoids: [] };
100
+ }
84
101
  const domain = scalarField(identityBlock, "domain") ?? "unknown";
85
102
  const capabilityTags = listField(identityBlock, "capability_tags") ?? [];
86
- return { domain, capabilityTags };
103
+ const requires = listField(identityBlock, "requires") ?? [];
104
+ const prefers = listField(identityBlock, "prefers") ?? [];
105
+ const avoids = listField(identityBlock, "avoids") ?? [];
106
+ return { domain, capabilityTags, requires, prefers, avoids };
87
107
  }
88
108
  function sliceNamedBlock(src, name) {
89
109
  const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
@@ -138,8 +158,8 @@ function listField(block, key) {
138
158
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
139
159
  import { dirname } from "path";
140
160
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
141
- "claude-opus-4-7": { input: 15, output: 75 },
142
- "claude-opus-4-6": { input: 15, output: 75 },
161
+ "claude-opus-4-7": { input: 5, output: 25 },
162
+ "claude-opus-4-6": { input: 5, output: 25 },
143
163
  "claude-sonnet-4-6": { input: 3, output: 15 },
144
164
  "claude-haiku-4-5-20251001": { input: 1, output: 5 },
145
165
  "claude-haiku-4-5": { input: 1, output: 5 }
@@ -156,8 +176,30 @@ function defaultAnthropicPricer(model, usage) {
156
176
  function defaultLocalLlmPricer(_model, _usage) {
157
177
  return 0;
158
178
  }
179
+ var XAI_PRICING_USD_PER_MTOK = {};
180
+ function defaultXAIPricer(model, usage) {
181
+ const price = XAI_PRICING_USD_PER_MTOK[model];
182
+ if (!price) {
183
+ throw new Error(
184
+ `No xAI pricing configured for model "${model}" \u2014 populate XAI_PRICING_USD_PER_MTOK (see /research task_1778109552044_qed8 in docs/LLM_CAPABILITIES.md) or pass a custom pricer`
185
+ );
186
+ }
187
+ return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1e6;
188
+ }
189
+ var OPENROUTER_PRICING_USD_PER_MTOK = {};
190
+ function defaultOpenRouterPricer(model, usage) {
191
+ const price = OPENROUTER_PRICING_USD_PER_MTOK[model];
192
+ if (!price) {
193
+ throw new Error(
194
+ `No OpenRouter pricing configured for model "${model}" \u2014 populate OPENROUTER_PRICING_USD_PER_MTOK or pass a custom pricer`
195
+ );
196
+ }
197
+ return (usage.promptTokens * price.input + usage.completionTokens * price.output) / 1e6;
198
+ }
159
199
  function defaultPricerForProvider(provider) {
160
200
  if (provider === "local-llm" || provider === "mock") return defaultLocalLlmPricer;
201
+ if (provider === "xai") return defaultXAIPricer;
202
+ if (provider === "openrouter") return defaultOpenRouterPricer;
161
203
  return defaultAnthropicPricer;
162
204
  }
163
205
  var CostGuard = class {
@@ -219,6 +261,181 @@ function todayUtc() {
219
261
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
220
262
  }
221
263
 
264
+ // src/capability-router.ts
265
+ import {
266
+ ANTHROPIC_CAPABILITIES,
267
+ OPENAI_CAPABILITIES,
268
+ GEMINI_CAPABILITIES,
269
+ XAI_CAPABILITIES,
270
+ OPENROUTER_CAPABILITIES,
271
+ LOCAL_LLM_CAPABILITIES,
272
+ BITNET_CAPABILITIES,
273
+ MOCK_CAPABILITIES
274
+ } from "@holoscript/llm-provider";
275
+ var NoEligibleProviderError = class extends Error {
276
+ constructor(requires, avoids, considered, excludedByAvoids) {
277
+ super(
278
+ `No provider satisfies brain requires=[${requires.join(", ")}] avoids=[${avoids.join(", ")}]. Considered: [${considered.join(", ")}]. Excluded by avoids: [${excludedByAvoids.join(", ")}].`
279
+ );
280
+ this.requires = requires;
281
+ this.avoids = avoids;
282
+ this.considered = considered;
283
+ this.excludedByAvoids = excludedByAvoids;
284
+ this.name = "NoEligibleProviderError";
285
+ }
286
+ };
287
+ function satisfies(capabilities, key) {
288
+ const value = capabilities[key];
289
+ if (typeof value === "boolean") return value;
290
+ if (typeof value === "number") return value > 0;
291
+ return false;
292
+ }
293
+ function countMatches(capabilities, keys) {
294
+ let count = 0;
295
+ for (const key of keys) {
296
+ if (satisfies(capabilities, key)) count++;
297
+ }
298
+ return count;
299
+ }
300
+ function unsatisfiedKeys(capabilities, keys) {
301
+ return keys.filter((key) => !satisfies(capabilities, key));
302
+ }
303
+ function pickProvider(opts) {
304
+ const { brain, envOverride, candidates } = opts;
305
+ const tieBreaker = opts.tieBreakerOrder ?? candidates.map((c) => c.name);
306
+ if (candidates.length === 0) {
307
+ throw new Error("pickProvider: no candidates supplied");
308
+ }
309
+ const excludedByAvoids = [];
310
+ const notAvoided = [];
311
+ for (const candidate of candidates) {
312
+ const matchesAvoid = brain.avoids.some((a) => satisfies(candidate.capabilities, a));
313
+ if (matchesAvoid) {
314
+ excludedByAvoids.push(candidate.name);
315
+ } else {
316
+ notAvoided.push(candidate);
317
+ }
318
+ }
319
+ if (brain.requires.length === 0) {
320
+ if (envOverride !== void 0) {
321
+ const envCandidate = candidates.find((c) => c.name === envOverride);
322
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
323
+ return {
324
+ picked: envOverride,
325
+ reason: "env-override-no-requirements",
326
+ unsatisfiedRequires: [],
327
+ matchedPrefers,
328
+ excludedByAvoids,
329
+ alternatives: candidates.filter((c) => c.name !== envOverride).map((c) => c.name)
330
+ };
331
+ }
332
+ const ordered = orderCandidates(notAvoided, tieBreaker);
333
+ if (ordered.length === 0) {
334
+ return {
335
+ picked: candidates[0].name,
336
+ reason: "open-routing-default",
337
+ unsatisfiedRequires: [],
338
+ matchedPrefers: brain.prefers.filter(
339
+ (p) => satisfies(candidates[0].capabilities, p)
340
+ ),
341
+ excludedByAvoids,
342
+ alternatives: candidates.slice(1).map((c) => c.name)
343
+ };
344
+ }
345
+ return {
346
+ picked: ordered[0].name,
347
+ reason: "open-routing-default",
348
+ unsatisfiedRequires: [],
349
+ matchedPrefers: brain.prefers.filter((p) => satisfies(ordered[0].capabilities, p)),
350
+ excludedByAvoids,
351
+ alternatives: ordered.slice(1).map((c) => c.name)
352
+ };
353
+ }
354
+ const eligible = notAvoided.filter((c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0);
355
+ if (eligible.length === 0) {
356
+ if (envOverride !== void 0) {
357
+ const envCandidate = candidates.find((c) => c.name === envOverride);
358
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
359
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
360
+ return {
361
+ picked: envOverride,
362
+ reason: "env-override-mismatch",
363
+ unsatisfiedRequires: unsatisfied,
364
+ matchedPrefers,
365
+ excludedByAvoids,
366
+ alternatives: []
367
+ };
368
+ }
369
+ throw new NoEligibleProviderError(
370
+ brain.requires,
371
+ brain.avoids,
372
+ candidates.map((c) => c.name),
373
+ excludedByAvoids
374
+ );
375
+ }
376
+ const ranked = [...eligible].sort((a, b) => {
377
+ const aMatches = countMatches(a.capabilities, brain.prefers);
378
+ const bMatches = countMatches(b.capabilities, brain.prefers);
379
+ if (aMatches !== bMatches) return bMatches - aMatches;
380
+ const aIdx = tieBreaker.indexOf(a.name);
381
+ const bIdx = tieBreaker.indexOf(b.name);
382
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
383
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
384
+ return aRank - bRank;
385
+ });
386
+ if (envOverride !== void 0) {
387
+ const envEligible = ranked.find((c) => c.name === envOverride);
388
+ if (envEligible) {
389
+ return {
390
+ picked: envOverride,
391
+ reason: "env-override-satisfies",
392
+ unsatisfiedRequires: [],
393
+ matchedPrefers: brain.prefers.filter((p) => satisfies(envEligible.capabilities, p)),
394
+ excludedByAvoids,
395
+ alternatives: ranked.filter((c) => c.name !== envOverride).map((c) => c.name)
396
+ };
397
+ }
398
+ const envCandidate = candidates.find((c) => c.name === envOverride);
399
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
400
+ return {
401
+ picked: envOverride,
402
+ reason: "env-override-mismatch",
403
+ unsatisfiedRequires: unsatisfied,
404
+ matchedPrefers: envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [],
405
+ excludedByAvoids,
406
+ alternatives: ranked.map((c) => c.name)
407
+ };
408
+ }
409
+ const top = ranked[0];
410
+ return {
411
+ picked: top.name,
412
+ reason: "capability-best-fit",
413
+ unsatisfiedRequires: [],
414
+ matchedPrefers: brain.prefers.filter((p) => satisfies(top.capabilities, p)),
415
+ excludedByAvoids,
416
+ alternatives: ranked.slice(1).map((c) => c.name)
417
+ };
418
+ }
419
+ var BUILT_IN_CANDIDATES = [
420
+ { name: "anthropic", capabilities: ANTHROPIC_CAPABILITIES },
421
+ { name: "openai", capabilities: OPENAI_CAPABILITIES },
422
+ { name: "gemini", capabilities: GEMINI_CAPABILITIES },
423
+ { name: "xai", capabilities: XAI_CAPABILITIES },
424
+ { name: "openrouter", capabilities: OPENROUTER_CAPABILITIES },
425
+ { name: "local-llm", capabilities: LOCAL_LLM_CAPABILITIES },
426
+ { name: "bitnet", capabilities: BITNET_CAPABILITIES },
427
+ { name: "mock", capabilities: MOCK_CAPABILITIES }
428
+ ];
429
+ function orderCandidates(candidates, tieBreaker) {
430
+ return [...candidates].sort((a, b) => {
431
+ const aIdx = tieBreaker.indexOf(a.name);
432
+ const bIdx = tieBreaker.indexOf(b.name);
433
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
434
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
435
+ return aRank - bRank;
436
+ });
437
+ }
438
+
222
439
  // src/holomesh-client.ts
223
440
  var HolomeshClient = class {
224
441
  constructor(opts) {
@@ -282,6 +499,51 @@ var HolomeshClient = class {
282
499
  wallet: raw.wallet
283
500
  };
284
501
  }
502
+ // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────
503
+ /** Read recent team messages. */
504
+ async getTeamMessages(limit = 20) {
505
+ const data = await this.req(
506
+ "GET",
507
+ `/team/${this.teamId}/messages?limit=${limit}`
508
+ );
509
+ return data.messages ?? [];
510
+ }
511
+ /** Post a message to the team feed. */
512
+ async sendTeamMessage(content, messageType = "text") {
513
+ await this.req("POST", `/team/${this.teamId}/message`, {
514
+ content,
515
+ type: messageType
516
+ });
517
+ }
518
+ // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
519
+ /** Switch team mode. Requires owner or founder role. */
520
+ async setTeamMode(mode, reason) {
521
+ return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
522
+ }
523
+ /** Update room preferences. Requires config:write permission. */
524
+ async patchRoomPrefs(prefs) {
525
+ return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
526
+ }
527
+ /** Update a board task. */
528
+ async updateTask(taskId, updates) {
529
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
530
+ action: "update",
531
+ ...updates
532
+ });
533
+ }
534
+ /** Delete a board task. */
535
+ async deleteTask(taskId) {
536
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
537
+ action: "delete"
538
+ });
539
+ }
540
+ /** Delegate a board task to another agent. */
541
+ async delegateTask(taskId, toAgentId) {
542
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
543
+ action: "delegate",
544
+ toAgentId
545
+ });
546
+ }
285
547
  async req(method, path, body) {
286
548
  const url = `${this.apiBase}${path}`;
287
549
  const res = await this.fetchImpl(url, {
@@ -365,7 +627,8 @@ function buildCaelRecord(input) {
365
627
  prev_hash: prevChain,
366
628
  fnv1a_chain,
367
629
  version_vector_fingerprint: `agent@${runtimeVersion}|brain@${brainClassOf(brain)}|provider@${identity.llmProvider}|model@${identity.llmModel}`,
368
- brain_class: brainClassOf(brain)
630
+ brain_class: brainClassOf(brain),
631
+ trust_epoch: "post-w107"
369
632
  };
370
633
  }
371
634
 
@@ -385,11 +648,7 @@ var ALLOWED_WRITE_ROOTS = [
385
648
  "/root/agent-output"
386
649
  // Single write sink — keeps deliverables in one place
387
650
  ];
388
- var BASH_WHITELIST = [
389
- "lake build",
390
- "lake env",
391
- "lake clean",
392
- "lean ",
651
+ var BASH_READ_ONLY_PREFIXES = [
393
652
  "ls ",
394
653
  "ls\n",
395
654
  "ls$",
@@ -404,12 +663,24 @@ var BASH_WHITELIST = [
404
663
  "git log",
405
664
  "git diff",
406
665
  "git show",
666
+ "pwd",
667
+ "echo ",
668
+ "lake env"
669
+ ];
670
+ var BASH_PRODUCTIVE_PREFIXES = [
671
+ "lake build",
672
+ "lake clean",
673
+ "lean ",
407
674
  "pnpm --filter",
408
675
  "pnpm vitest",
409
- "vitest run",
410
- "pwd",
411
- "echo "
676
+ "vitest run"
412
677
  ];
678
+ var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
679
+ function isProductiveBashCommand(cmd) {
680
+ const trimmed = String(cmd ?? "").trim();
681
+ if (!trimmed) return false;
682
+ return BASH_PRODUCTIVE_PREFIXES.some((prefix) => trimmed.startsWith(prefix.trim()));
683
+ }
413
684
  var MESH_TOOLS = [
414
685
  {
415
686
  name: "read_file",
@@ -532,6 +803,13 @@ ${result.stderr || result.stdout}`);
532
803
  }
533
804
  }
534
805
  function runBash(cmd, cwd) {
806
+ if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
807
+ return Promise.resolve({
808
+ code: 0,
809
+ stdout: `[mock-bash under vitest] cmd="${cmd}" cwd="${cwd}"`,
810
+ stderr: ""
811
+ });
812
+ }
535
813
  return new Promise((resolveProm) => {
536
814
  const child = spawn("bash", ["-c", cmd], { cwd, env: process.env });
537
815
  let stdout = "";
@@ -600,6 +878,35 @@ var AgentRunner = class {
600
878
  const { identity, brain, mesh, costGuard, provider, logger } = this.opts;
601
879
  const log = logger ?? (() => void 0);
602
880
  await this.heartbeatWithAutoRejoin();
881
+ if (this.opts.messageHandler) {
882
+ try {
883
+ const receipts = await this.opts.messageHandler.processMessages();
884
+ if (receipts.length > 0) {
885
+ log({
886
+ ev: "messages-processed",
887
+ count: receipts.length,
888
+ statuses: receipts.map((r) => r.status)
889
+ });
890
+ if (brain.capabilityTags.length === 0 || brain.capabilityTags.every((t) => t.startsWith("delegated"))) {
891
+ return {
892
+ action: "messages-processed",
893
+ spentUsd: costGuard.getState().spentUsd,
894
+ remainingUsd: costGuard.getRemainingUsd(),
895
+ receipts: receipts.map((r) => ({
896
+ status: r.status,
897
+ action: r.action,
898
+ reason: r.reason
899
+ }))
900
+ };
901
+ }
902
+ }
903
+ } catch (err) {
904
+ log({
905
+ ev: "message-handler-error",
906
+ message: err instanceof Error ? err.message : String(err)
907
+ });
908
+ }
909
+ }
603
910
  if (costGuard.isOverBudget()) {
604
911
  const state = costGuard.getState();
605
912
  log({ ev: "over-budget", spentUsd: state.spentUsd, budget: identity.budgetUsdPerDay });
@@ -633,6 +940,8 @@ var AgentRunner = class {
633
940
  const MAX_TOOL_ITERS = 30;
634
941
  let lastResponse;
635
942
  const toolsCalled = /* @__PURE__ */ new Set();
943
+ let productiveCallCount = 0;
944
+ let lastCommitHash;
636
945
  while (true) {
637
946
  iters++;
638
947
  if (iters > MAX_TOOL_ITERS) {
@@ -657,12 +966,31 @@ var AgentRunner = class {
657
966
  };
658
967
  if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
659
968
  log({ ev: "tool-call", taskId: target.id, iter: iters, tools: resp.toolUses.map((t) => t.name) });
660
- for (const u of resp.toolUses) toolsCalled.add(u.name);
969
+ for (const u of resp.toolUses) {
970
+ toolsCalled.add(u.name);
971
+ if (u.name === "write_file") {
972
+ const content = String(u.input?.content ?? "");
973
+ if (content.length > 0) productiveCallCount++;
974
+ } else if (u.name === "bash") {
975
+ const cmd = String(u.input?.cmd ?? "");
976
+ if (isProductiveBashCommand(cmd)) productiveCallCount++;
977
+ }
978
+ }
661
979
  messages.push({
662
980
  role: "assistant",
663
981
  content: resp.assistantBlocks ?? []
664
982
  });
665
983
  const toolResults = await Promise.all(resp.toolUses.map((u) => runTool(u)));
984
+ for (let ti = 0; ti < resp.toolUses.length; ti++) {
985
+ const tu = resp.toolUses[ti];
986
+ if (tu.name === "bash") {
987
+ const tr = toolResults[ti];
988
+ if (tr && !tr.is_error) {
989
+ const shaMatch = tr.content.match(/\b([0-9a-f]{7,40})\b/);
990
+ if (shaMatch) lastCommitHash = shaMatch[1];
991
+ }
992
+ }
993
+ }
666
994
  messages.push({
667
995
  role: "user",
668
996
  content: toolResults
@@ -673,22 +1001,21 @@ var AgentRunner = class {
673
1001
  break;
674
1002
  }
675
1003
  const durationMs = Date.now() - start;
676
- const SIDE_EFFECTING_TOOLS = /* @__PURE__ */ new Set(["write_file", "bash"]);
677
- const sideEffectingCalled = [...toolsCalled].some((t) => SIDE_EFFECTING_TOOLS.has(t));
678
- if (!sideEffectingCalled) {
1004
+ if (productiveCallCount === 0) {
679
1005
  log({
680
1006
  ev: "no-artifact",
681
1007
  taskId: target.id,
682
1008
  tool_iters: iters,
683
1009
  toolsCalled: [...toolsCalled],
684
- message: "task execution called no side-effecting tool (write_file/bash) \u2014 refusing to mark executed. Likely a pure-text or read-only-inspection response. Task remains open for a grounded attempt."
1010
+ productiveCallCount,
1011
+ message: "task execution did not produce a real artifact \u2014 refusing to mark executed. Required: write_file with non-empty content OR bash with a productive prefix (lake build / pnpm --filter / vitest run / lean / pnpm vitest). Pure-text, read-only inspection, and trivial-bash-bypass (`echo`, `cat`, etc.) do not satisfy the gate."
685
1012
  });
686
1013
  return {
687
1014
  action: "no-artifact",
688
1015
  taskId: target.id,
689
1016
  spentUsd: costGuard.getState().spentUsd,
690
1017
  remainingUsd: costGuard.getRemainingUsd(),
691
- message: `no side-effecting tool called (toolsCalled=[${[...toolsCalled].join(",")}], iters=${iters})`
1018
+ message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
692
1019
  };
693
1020
  }
694
1021
  const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
@@ -748,6 +1075,12 @@ var AgentRunner = class {
748
1075
  ${response.content}`
749
1076
  );
750
1077
  }
1078
+ try {
1079
+ await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
1080
+ log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
1081
+ } catch (err) {
1082
+ log({ ev: "mark-done-error", taskId: target.id, message: err instanceof Error ? err.message : String(err) });
1083
+ }
751
1084
  return {
752
1085
  action: "executed",
753
1086
  taskId: target.id,
@@ -1295,13 +1628,29 @@ var Supervisor = class {
1295
1628
  return { ...managed.status };
1296
1629
  }
1297
1630
  async bootAgent(spec) {
1298
- const identity = this.identityFromSpec(spec);
1299
1631
  const brain = await loadBrain(spec.brainPath, spec.scopeTier ?? "warm");
1300
- const provider = await this.opts.providerFactory(spec, identity);
1632
+ const decision = pickProvider({
1633
+ brain,
1634
+ envOverride: spec.provider,
1635
+ candidates: BUILT_IN_CANDIDATES
1636
+ });
1637
+ const effectiveSpec = decision.picked === spec.provider ? spec : { ...spec, provider: decision.picked };
1638
+ const identity = this.identityFromSpec(effectiveSpec);
1639
+ if (decision.reason === "env-override-mismatch" && this.opts.logger) {
1640
+ this.opts.logger({
1641
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1642
+ ev: "capability-router-mismatch",
1643
+ handle: spec.handle,
1644
+ envOverride: spec.provider,
1645
+ unsatisfiedRequires: decision.unsatisfiedRequires,
1646
+ excludedByAvoids: decision.excludedByAvoids
1647
+ });
1648
+ }
1649
+ const provider = await this.opts.providerFactory(effectiveSpec, identity);
1301
1650
  const stateDir = this.opts.stateDir ?? join2(homedir(), ".holoscript-agent", "cost-state");
1302
- const isFree = spec.provider === "mock" || spec.provider === "local-llm" || spec.provider === "bitnet";
1651
+ const isFree = effectiveSpec.provider === "mock" || effectiveSpec.provider === "local-llm" || effectiveSpec.provider === "bitnet";
1303
1652
  const costGuard = new CostGuard({
1304
- statePath: join2(stateDir, `${spec.handle}.json`),
1653
+ statePath: join2(stateDir, `${effectiveSpec.handle}.json`),
1305
1654
  dailyBudgetUsd: identity.budgetUsdPerDay,
1306
1655
  pricer: isFree ? () => 0 : void 0
1307
1656
  });
@@ -1311,7 +1660,7 @@ var Supervisor = class {
1311
1660
  teamId: identity.teamId,
1312
1661
  fetchImpl: this.opts.fetchImpl
1313
1662
  });
1314
- const onTaskExecuted = spec.enableCommitHook ? this.buildCommitHook(spec, identity, mesh) : void 0;
1663
+ const onTaskExecuted = effectiveSpec.enableCommitHook ? this.buildCommitHook(effectiveSpec, identity, mesh) : void 0;
1315
1664
  const runner = new AgentRunner({
1316
1665
  identity,
1317
1666
  brain,
@@ -1320,16 +1669,16 @@ var Supervisor = class {
1320
1669
  mesh,
1321
1670
  onTaskExecuted,
1322
1671
  auditLog: this.auditLog,
1323
- logger: (ev) => this.log({ agent: spec.handle, ...ev })
1672
+ logger: (ev) => this.log({ agent: effectiveSpec.handle, ...ev })
1324
1673
  });
1325
1674
  const status = {
1326
- handle: spec.handle,
1675
+ handle: effectiveSpec.handle,
1327
1676
  state: "starting",
1328
1677
  spentUsd: 0,
1329
1678
  remainingUsd: identity.budgetUsdPerDay,
1330
1679
  restarts: 0
1331
1680
  };
1332
- return { spec, identity, brain, runner, costGuard, status };
1681
+ return { spec: effectiveSpec, identity, brain, runner, costGuard, status };
1333
1682
  }
1334
1683
  buildCommitHook(spec, identity, mesh) {
1335
1684
  const writer = makeCommitHook({
@@ -1427,6 +1776,8 @@ var VALID_PROVIDERS2 = /* @__PURE__ */ new Set([
1427
1776
  "anthropic",
1428
1777
  "openai",
1429
1778
  "gemini",
1779
+ "xai",
1780
+ "openrouter",
1430
1781
  "mock",
1431
1782
  "bitnet",
1432
1783
  "local-llm"
@@ -1440,16 +1791,21 @@ function parseSupervisorConfig(raw) {
1440
1791
  const data = JSON.parse(raw);
1441
1792
  if (!isObject(data)) throw new Error("Supervisor config must be a JSON object");
1442
1793
  if (!Array.isArray(data.agents)) throw new Error("Supervisor config.agents must be an array");
1443
- if (data.agents.length === 0) throw new Error("Supervisor config.agents must have at least one entry");
1794
+ if (data.agents.length === 0)
1795
+ throw new Error("Supervisor config.agents must have at least one entry");
1444
1796
  const seenHandles = /* @__PURE__ */ new Set();
1445
- const agents = data.agents.map((entry, idx) => validateAgent(entry, idx, seenHandles));
1797
+ const agents = data.agents.map(
1798
+ (entry, idx) => validateAgent(entry, idx, seenHandles)
1799
+ );
1446
1800
  const globalBudgetUsdPerDay = optionalNumber(data, "globalBudgetUsdPerDay");
1447
1801
  const defaultTickIntervalMs = optionalNumber(data, "defaultTickIntervalMs");
1448
1802
  if (globalBudgetUsdPerDay != null && globalBudgetUsdPerDay <= 0) {
1449
1803
  throw new Error(`globalBudgetUsdPerDay must be positive, got ${globalBudgetUsdPerDay}`);
1450
1804
  }
1451
1805
  if (defaultTickIntervalMs != null && defaultTickIntervalMs < 5e3) {
1452
- throw new Error(`defaultTickIntervalMs must be >= 5000ms (mesh-friendly), got ${defaultTickIntervalMs}`);
1806
+ throw new Error(
1807
+ `defaultTickIntervalMs must be >= 5000ms (mesh-friendly), got ${defaultTickIntervalMs}`
1808
+ );
1453
1809
  }
1454
1810
  return { agents, globalBudgetUsdPerDay, defaultTickIntervalMs };
1455
1811
  }
@@ -1472,8 +1828,8 @@ function validateAgent(entry, idx, seen) {
1472
1828
  throw new Error(`agents[${idx}].scopeTier "${scopeTier}" must be cold | warm | hot`);
1473
1829
  }
1474
1830
  const budgetUsdPerDay = optionalNumber(entry, "budgetUsdPerDay");
1475
- if (budgetUsdPerDay != null && budgetUsdPerDay <= 0) {
1476
- throw new Error(`agents[${idx}].budgetUsdPerDay must be positive, got ${budgetUsdPerDay}`);
1831
+ if (budgetUsdPerDay != null && budgetUsdPerDay < 0) {
1832
+ throw new Error(`agents[${idx}].budgetUsdPerDay must be >= 0 (0 = unlimited), got ${budgetUsdPerDay}`);
1477
1833
  }
1478
1834
  const tickIntervalMs = optionalNumber(entry, "tickIntervalMs");
1479
1835
  if (tickIntervalMs != null && tickIntervalMs < 5e3) {
@@ -1763,11 +2119,28 @@ async function main() {
1763
2119
  async function cmdRun(opts) {
1764
2120
  const identity = loadIdentity();
1765
2121
  const brain = await loadBrain(identity.brainPath, scopeTierFromEnv());
1766
- const provider = await buildProvider(identity);
2122
+ const decision = pickProvider({
2123
+ brain,
2124
+ envOverride: identity.llmProvider,
2125
+ candidates: BUILT_IN_CANDIDATES
2126
+ });
2127
+ const effectiveIdentity = decision.picked === identity.llmProvider ? identity : { ...identity, llmProvider: decision.picked };
2128
+ if (decision.reason === "env-override-mismatch") {
2129
+ console.log(
2130
+ JSON.stringify({
2131
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2132
+ ev: "capability-router-mismatch",
2133
+ envOverride: identity.llmProvider,
2134
+ unsatisfiedRequires: decision.unsatisfiedRequires,
2135
+ excludedByAvoids: decision.excludedByAvoids
2136
+ })
2137
+ );
2138
+ }
2139
+ const provider = await buildProvider(effectiveIdentity);
1767
2140
  const costGuard = new CostGuard({
1768
2141
  statePath: stateFilePath(identity),
1769
2142
  dailyBudgetUsd: identity.budgetUsdPerDay,
1770
- pricer: defaultPricerForProvider(identity.llmProvider)
2143
+ pricer: defaultPricerForProvider(effectiveIdentity.llmProvider)
1771
2144
  });
1772
2145
  const mesh = new HolomeshClient({
1773
2146
  apiBase: identity.meshApiBase,
@@ -1786,7 +2159,14 @@ async function cmdRun(opts) {
1786
2159
  onTaskExecuted: commitHook,
1787
2160
  auditLog
1788
2161
  });
1789
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "boot", identity: identityForLog(identity), brain: { domain: brain.domain, tags: brain.capabilityTags, tier: brain.scopeTier } }));
2162
+ console.log(
2163
+ JSON.stringify({
2164
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2165
+ ev: "boot",
2166
+ identity: identityForLog(identity),
2167
+ brain: { domain: brain.domain, tags: brain.capabilityTags, tier: brain.scopeTier }
2168
+ })
2169
+ );
1790
2170
  if (opts.once) {
1791
2171
  const result = await runner.tick();
1792
2172
  console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "tick-result", ...result }));
@@ -1811,6 +2191,10 @@ function supervisorProviderFactory() {
1811
2191
  return createOpenAIProvider({ defaultModel: spec.model });
1812
2192
  case "gemini":
1813
2193
  return createGeminiProvider({ defaultModel: spec.model });
2194
+ case "xai":
2195
+ return createXAIProvider({ defaultModel: spec.model });
2196
+ case "openrouter":
2197
+ return createOpenRouterProvider({ defaultModel: spec.model });
1814
2198
  case "local-llm":
1815
2199
  return createLocalLLMProvider({
1816
2200
  baseURL: process.env.HOLOSCRIPT_AGENT_LOCAL_LLM_BASE_URL,
@@ -1819,7 +2203,9 @@ function supervisorProviderFactory() {
1819
2203
  case "mock":
1820
2204
  return createMockProvider();
1821
2205
  default:
1822
- throw new Error(`Provider "${spec.provider}" not yet wired in supervisor \u2014 Phase 2.5 deliverable.`);
2206
+ throw new Error(
2207
+ `Provider "${spec.provider}" not yet wired in supervisor \u2014 add a case here.`
2208
+ );
1823
2209
  }
1824
2210
  };
1825
2211
  }
@@ -1846,11 +2232,15 @@ async function cmdSupervise(rest) {
1846
2232
  process.on("SIGINT", onSig);
1847
2233
  process.on("SIGTERM", onSig);
1848
2234
  await sup.start();
1849
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervise-running", config: cfgPath }));
2235
+ console.log(
2236
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervise-running", config: cfgPath })
2237
+ );
1850
2238
  const reportEvery = Number(process.env.HOLOSCRIPT_AGENT_STATUS_REPORT_MS ?? "300000");
1851
2239
  if (reportEvery > 0) {
1852
2240
  setInterval(() => {
1853
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervisor-status", ...sup.status() }));
2241
+ console.log(
2242
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "supervisor-status", ...sup.status() })
2243
+ );
1854
2244
  }, reportEvery);
1855
2245
  }
1856
2246
  }
@@ -1872,7 +2262,9 @@ async function cmdAudit(rest) {
1872
2262
  const events = log.query(filter);
1873
2263
  for (const e of events) console.log(JSON.stringify(e));
1874
2264
  } else {
1875
- throw new Error("Usage: holoscript-agent audit [rollup|query|tail] [--agent=<h>] [--provider=<p>] [--task=<id>] [--kind=<k>] [--limit=<n>] [--log=<path>]");
2265
+ throw new Error(
2266
+ "Usage: holoscript-agent audit [rollup|query|tail] [--agent=<h>] [--provider=<p>] [--task=<id>] [--kind=<k>] [--limit=<n>] [--log=<path>]"
2267
+ );
1876
2268
  }
1877
2269
  }
1878
2270
  async function cmdProvision(rest) {
@@ -1884,7 +2276,9 @@ async function cmdProvision(rest) {
1884
2276
  const force = rest.includes("--force");
1885
2277
  const founderBearer = process.env.HOLOMESH_API_KEY;
1886
2278
  if (!founderBearer) {
1887
- throw new Error("HOLOMESH_API_KEY env var required for provisioning (founder-tier bearer for /register endpoints)");
2279
+ throw new Error(
2280
+ "HOLOMESH_API_KEY env var required for provisioning (founder-tier bearer for /register endpoints)"
2281
+ );
1888
2282
  }
1889
2283
  const result = await provisionAgent(
1890
2284
  {
@@ -1896,7 +2290,9 @@ async function cmdProvision(rest) {
1896
2290
  },
1897
2291
  { execute, force }
1898
2292
  );
1899
- console.log(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "provision-result", ...result }, null, 2));
2293
+ console.log(
2294
+ JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "provision-result", ...result }, null, 2)
2295
+ );
1900
2296
  if (result.status === "executed" || result.status === "reused") {
1901
2297
  console.log("\n# Add these lines to your .env to use this seat:");
1902
2298
  for (const line of result.envVarLines) console.log(line);
@@ -1908,19 +2304,27 @@ async function cmdStatus(rest) {
1908
2304
  throw new Error("Usage: holoscript-agent status --config=<path-to-agents.json>");
1909
2305
  }
1910
2306
  const config = loadSupervisorConfig(cfgPath);
1911
- console.log(JSON.stringify({
1912
- config: cfgPath,
1913
- agentCount: config.agents.length,
1914
- enabled: config.agents.filter((a) => a.enabled !== false).map((a) => a.handle),
1915
- disabled: config.agents.filter((a) => a.enabled === false).map((a) => a.handle),
1916
- globalBudgetUsdPerDay: config.globalBudgetUsdPerDay ?? null,
1917
- defaultTickIntervalMs: config.defaultTickIntervalMs ?? null
1918
- }, null, 2));
2307
+ console.log(
2308
+ JSON.stringify(
2309
+ {
2310
+ config: cfgPath,
2311
+ agentCount: config.agents.length,
2312
+ enabled: config.agents.filter((a) => a.enabled !== false).map((a) => a.handle),
2313
+ disabled: config.agents.filter((a) => a.enabled === false).map((a) => a.handle),
2314
+ globalBudgetUsdPerDay: config.globalBudgetUsdPerDay ?? null,
2315
+ defaultTickIntervalMs: config.defaultTickIntervalMs ?? null
2316
+ },
2317
+ null,
2318
+ 2
2319
+ )
2320
+ );
1919
2321
  }
1920
2322
  async function cmdAblate(rest) {
1921
2323
  const specPath = rest.find((a) => a.startsWith("--spec="))?.split("=")[1];
1922
2324
  if (!specPath) {
1923
- throw new Error("Usage: holoscript-agent ablate --spec=<path-to-ablation.json> [--out-md=<path>] [--out-json=<path>]");
2325
+ throw new Error(
2326
+ "Usage: holoscript-agent ablate --spec=<path-to-ablation.json> [--out-md=<path>] [--out-json=<path>]"
2327
+ );
1924
2328
  }
1925
2329
  const outMd = rest.find((a) => a.startsWith("--out-md="))?.split("=")[1];
1926
2330
  const outJson = rest.find((a) => a.startsWith("--out-json="))?.split("=")[1];
@@ -1949,7 +2353,12 @@ async function cmdAblate(rest) {
1949
2353
  },
1950
2354
  pricer: p.pricePerCallUsd != null ? () => p.pricePerCallUsd : p.pricePerMtokInput != null && p.pricePerMtokOutput != null ? (u) => (u.promptTokens * p.pricePerMtokInput + u.completionTokens * p.pricePerMtokOutput) / 1e6 : void 0
1951
2355
  }));
1952
- const startMsg = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "ablation-start", task: spec.task.taskId, cells: providers.length });
2356
+ const startMsg = JSON.stringify({
2357
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2358
+ ev: "ablation-start",
2359
+ task: spec.task.taskId,
2360
+ cells: providers.length
2361
+ });
1953
2362
  console.log(startMsg);
1954
2363
  const matrix = await runAblation({
1955
2364
  task: spec.task,
@@ -1967,17 +2376,19 @@ async function cmdAblate(rest) {
1967
2376
  if (!outMd && !outJson) {
1968
2377
  console.log(renderAblationMarkdown(matrix));
1969
2378
  }
1970
- console.log(JSON.stringify({
1971
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1972
- ev: "ablation-done",
1973
- task: matrix.taskId,
1974
- cells: matrix.cells.length,
1975
- errors: matrix.cells.filter((c) => c.errorMessage).length,
1976
- totalCostUsd: matrix.totalCostUsd,
1977
- promptHash: matrix.promptHash,
1978
- outMd: outMd ?? null,
1979
- outJson: outJson ?? null
1980
- }));
2379
+ console.log(
2380
+ JSON.stringify({
2381
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2382
+ ev: "ablation-done",
2383
+ task: matrix.taskId,
2384
+ cells: matrix.cells.length,
2385
+ errors: matrix.cells.filter((c) => c.errorMessage).length,
2386
+ totalCostUsd: matrix.totalCostUsd,
2387
+ promptHash: matrix.promptHash,
2388
+ outMd: outMd ?? null,
2389
+ outJson: outJson ?? null
2390
+ })
2391
+ );
1981
2392
  }
1982
2393
  async function cmdWhoami() {
1983
2394
  const identity = loadIdentity();
@@ -1998,6 +2409,10 @@ async function buildProvider(identity) {
1998
2409
  return createOpenAIProvider({ defaultModel: identity.llmModel });
1999
2410
  case "gemini":
2000
2411
  return createGeminiProvider({ defaultModel: identity.llmModel });
2412
+ case "xai":
2413
+ return createXAIProvider({ defaultModel: identity.llmModel });
2414
+ case "openrouter":
2415
+ return createOpenRouterProvider({ defaultModel: identity.llmModel });
2001
2416
  case "mock":
2002
2417
  return createMockProvider();
2003
2418
  case "local-llm":
@@ -2006,9 +2421,7 @@ async function buildProvider(identity) {
2006
2421
  model: identity.llmModel
2007
2422
  });
2008
2423
  default:
2009
- throw new Error(
2010
- `Provider "${p}" not yet wired in CLI \u2014 Phase 2 deliverable. Use anthropic | openai | gemini | local-llm | mock for now.`
2011
- );
2424
+ throw new Error(`Provider "${p}" not yet wired in CLI \u2014 add a case in buildProvider.`);
2012
2425
  }
2013
2426
  }
2014
2427
  function buildCommitHook(identity, mesh) {
@@ -2095,7 +2508,13 @@ OPTIONAL ENV
2095
2508
  `);
2096
2509
  }
2097
2510
  main().catch((err) => {
2098
- console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ev: "fatal", message: err instanceof Error ? err.message : String(err) }));
2511
+ console.error(
2512
+ JSON.stringify({
2513
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2514
+ ev: "fatal",
2515
+ message: err instanceof Error ? err.message : String(err)
2516
+ })
2517
+ );
2099
2518
  process.exit(1);
2100
2519
  });
2101
2520
  //# sourceMappingURL=index.js.map