@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.
@@ -65,6 +65,51 @@ var HolomeshClient = class {
65
65
  wallet: raw.wallet
66
66
  };
67
67
  }
68
+ // ── Team Message Surface (E4 delegated-authority protocol) ───────────────────
69
+ /** Read recent team messages. */
70
+ async getTeamMessages(limit = 20) {
71
+ const data = await this.req(
72
+ "GET",
73
+ `/team/${this.teamId}/messages?limit=${limit}`
74
+ );
75
+ return data.messages ?? [];
76
+ }
77
+ /** Post a message to the team feed. */
78
+ async sendTeamMessage(content, messageType = "text") {
79
+ await this.req("POST", `/team/${this.teamId}/message`, {
80
+ content,
81
+ type: messageType
82
+ });
83
+ }
84
+ // ── Owner-op API wrappers (E4) ─────────────────────────────────────────────
85
+ /** Switch team mode. Requires owner or founder role. */
86
+ async setTeamMode(mode, reason) {
87
+ return this.req("POST", `/team/${this.teamId}/mode`, { mode, reason });
88
+ }
89
+ /** Update room preferences. Requires config:write permission. */
90
+ async patchRoomPrefs(prefs) {
91
+ return this.req("PATCH", `/team/${this.teamId}/room`, prefs);
92
+ }
93
+ /** Update a board task. */
94
+ async updateTask(taskId, updates) {
95
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
96
+ action: "update",
97
+ ...updates
98
+ });
99
+ }
100
+ /** Delete a board task. */
101
+ async deleteTask(taskId) {
102
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
103
+ action: "delete"
104
+ });
105
+ }
106
+ /** Delegate a board task to another agent. */
107
+ async delegateTask(taskId, toAgentId) {
108
+ return this.req("PATCH", `/team/${this.teamId}/board/${taskId}`, {
109
+ action: "delegate",
110
+ toAgentId
111
+ });
112
+ }
68
113
  async req(method, path, body) {
69
114
  const url = `${this.apiBase}${path}`;
70
115
  const res = await this.fetchImpl(url, {
@@ -148,7 +193,8 @@ function buildCaelRecord(input) {
148
193
  prev_hash: prevChain,
149
194
  fnv1a_chain,
150
195
  version_vector_fingerprint: `agent@${runtimeVersion}|brain@${brainClassOf(brain)}|provider@${identity.llmProvider}|model@${identity.llmModel}`,
151
- brain_class: brainClassOf(brain)
196
+ brain_class: brainClassOf(brain),
197
+ trust_epoch: "post-w107"
152
198
  };
153
199
  }
154
200
 
@@ -168,11 +214,7 @@ var ALLOWED_WRITE_ROOTS = [
168
214
  "/root/agent-output"
169
215
  // Single write sink — keeps deliverables in one place
170
216
  ];
171
- var BASH_WHITELIST = [
172
- "lake build",
173
- "lake env",
174
- "lake clean",
175
- "lean ",
217
+ var BASH_READ_ONLY_PREFIXES = [
176
218
  "ls ",
177
219
  "ls\n",
178
220
  "ls$",
@@ -187,12 +229,24 @@ var BASH_WHITELIST = [
187
229
  "git log",
188
230
  "git diff",
189
231
  "git show",
232
+ "pwd",
233
+ "echo ",
234
+ "lake env"
235
+ ];
236
+ var BASH_PRODUCTIVE_PREFIXES = [
237
+ "lake build",
238
+ "lake clean",
239
+ "lean ",
190
240
  "pnpm --filter",
191
241
  "pnpm vitest",
192
- "vitest run",
193
- "pwd",
194
- "echo "
242
+ "vitest run"
195
243
  ];
244
+ var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
245
+ function isProductiveBashCommand(cmd) {
246
+ const trimmed = String(cmd ?? "").trim();
247
+ if (!trimmed) return false;
248
+ return BASH_PRODUCTIVE_PREFIXES.some((prefix) => trimmed.startsWith(prefix.trim()));
249
+ }
196
250
  var MESH_TOOLS = [
197
251
  {
198
252
  name: "read_file",
@@ -315,6 +369,13 @@ ${result.stderr || result.stdout}`);
315
369
  }
316
370
  }
317
371
  function runBash(cmd, cwd) {
372
+ if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
373
+ return Promise.resolve({
374
+ code: 0,
375
+ stdout: `[mock-bash under vitest] cmd="${cmd}" cwd="${cwd}"`,
376
+ stderr: ""
377
+ });
378
+ }
318
379
  return new Promise((resolveProm) => {
319
380
  const child = spawn("bash", ["-c", cmd], { cwd, env: process.env });
320
381
  let stdout = "";
@@ -383,6 +444,35 @@ var AgentRunner = class {
383
444
  const { identity, brain, mesh, costGuard, provider, logger } = this.opts;
384
445
  const log = logger ?? (() => void 0);
385
446
  await this.heartbeatWithAutoRejoin();
447
+ if (this.opts.messageHandler) {
448
+ try {
449
+ const receipts = await this.opts.messageHandler.processMessages();
450
+ if (receipts.length > 0) {
451
+ log({
452
+ ev: "messages-processed",
453
+ count: receipts.length,
454
+ statuses: receipts.map((r) => r.status)
455
+ });
456
+ if (brain.capabilityTags.length === 0 || brain.capabilityTags.every((t) => t.startsWith("delegated"))) {
457
+ return {
458
+ action: "messages-processed",
459
+ spentUsd: costGuard.getState().spentUsd,
460
+ remainingUsd: costGuard.getRemainingUsd(),
461
+ receipts: receipts.map((r) => ({
462
+ status: r.status,
463
+ action: r.action,
464
+ reason: r.reason
465
+ }))
466
+ };
467
+ }
468
+ }
469
+ } catch (err) {
470
+ log({
471
+ ev: "message-handler-error",
472
+ message: err instanceof Error ? err.message : String(err)
473
+ });
474
+ }
475
+ }
386
476
  if (costGuard.isOverBudget()) {
387
477
  const state = costGuard.getState();
388
478
  log({ ev: "over-budget", spentUsd: state.spentUsd, budget: identity.budgetUsdPerDay });
@@ -416,6 +506,8 @@ var AgentRunner = class {
416
506
  const MAX_TOOL_ITERS = 30;
417
507
  let lastResponse;
418
508
  const toolsCalled = /* @__PURE__ */ new Set();
509
+ let productiveCallCount = 0;
510
+ let lastCommitHash;
419
511
  while (true) {
420
512
  iters++;
421
513
  if (iters > MAX_TOOL_ITERS) {
@@ -440,12 +532,31 @@ var AgentRunner = class {
440
532
  };
441
533
  if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
442
534
  log({ ev: "tool-call", taskId: target.id, iter: iters, tools: resp.toolUses.map((t) => t.name) });
443
- for (const u of resp.toolUses) toolsCalled.add(u.name);
535
+ for (const u of resp.toolUses) {
536
+ toolsCalled.add(u.name);
537
+ if (u.name === "write_file") {
538
+ const content = String(u.input?.content ?? "");
539
+ if (content.length > 0) productiveCallCount++;
540
+ } else if (u.name === "bash") {
541
+ const cmd = String(u.input?.cmd ?? "");
542
+ if (isProductiveBashCommand(cmd)) productiveCallCount++;
543
+ }
544
+ }
444
545
  messages.push({
445
546
  role: "assistant",
446
547
  content: resp.assistantBlocks ?? []
447
548
  });
448
549
  const toolResults = await Promise.all(resp.toolUses.map((u) => runTool(u)));
550
+ for (let ti = 0; ti < resp.toolUses.length; ti++) {
551
+ const tu = resp.toolUses[ti];
552
+ if (tu.name === "bash") {
553
+ const tr = toolResults[ti];
554
+ if (tr && !tr.is_error) {
555
+ const shaMatch = tr.content.match(/\b([0-9a-f]{7,40})\b/);
556
+ if (shaMatch) lastCommitHash = shaMatch[1];
557
+ }
558
+ }
559
+ }
449
560
  messages.push({
450
561
  role: "user",
451
562
  content: toolResults
@@ -456,22 +567,21 @@ var AgentRunner = class {
456
567
  break;
457
568
  }
458
569
  const durationMs = Date.now() - start;
459
- const SIDE_EFFECTING_TOOLS = /* @__PURE__ */ new Set(["write_file", "bash"]);
460
- const sideEffectingCalled = [...toolsCalled].some((t) => SIDE_EFFECTING_TOOLS.has(t));
461
- if (!sideEffectingCalled) {
570
+ if (productiveCallCount === 0) {
462
571
  log({
463
572
  ev: "no-artifact",
464
573
  taskId: target.id,
465
574
  tool_iters: iters,
466
575
  toolsCalled: [...toolsCalled],
467
- 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."
576
+ productiveCallCount,
577
+ 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."
468
578
  });
469
579
  return {
470
580
  action: "no-artifact",
471
581
  taskId: target.id,
472
582
  spentUsd: costGuard.getState().spentUsd,
473
583
  remainingUsd: costGuard.getRemainingUsd(),
474
- message: `no side-effecting tool called (toolsCalled=[${[...toolsCalled].join(",")}], iters=${iters})`
584
+ message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
475
585
  };
476
586
  }
477
587
  const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
@@ -531,6 +641,12 @@ var AgentRunner = class {
531
641
  ${response.content}`
532
642
  );
533
643
  }
644
+ try {
645
+ await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
646
+ log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
647
+ } catch (err) {
648
+ log({ ev: "mark-done-error", taskId: target.id, message: err instanceof Error ? err.message : String(err) });
649
+ }
534
650
  return {
535
651
  action: "executed",
536
652
  taskId: target.id,
@@ -637,8 +753,8 @@ function jitter(base) {
637
753
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
638
754
  import { dirname as dirname2 } from "path";
639
755
  var ANTHROPIC_PRICING_USD_PER_MTOK = {
640
- "claude-opus-4-7": { input: 15, output: 75 },
641
- "claude-opus-4-6": { input: 15, output: 75 },
756
+ "claude-opus-4-7": { input: 5, output: 25 },
757
+ "claude-opus-4-6": { input: 5, output: 25 },
642
758
  "claude-sonnet-4-6": { input: 3, output: 15 },
643
759
  "claude-haiku-4-5-20251001": { input: 1, output: 5 },
644
760
  "claude-haiku-4-5": { input: 1, output: 5 }
@@ -715,15 +831,29 @@ function todayUtc() {
715
831
  import { readFile as readFile2 } from "fs/promises";
716
832
  async function loadBrain(brainPath, scopeTier = "warm") {
717
833
  const systemPrompt = await readFile2(brainPath, "utf8");
718
- const { domain, capabilityTags } = extractIdentity(systemPrompt);
719
- return { brainPath, systemPrompt, capabilityTags, domain, scopeTier };
834
+ const { domain, capabilityTags, requires, prefers, avoids } = extractIdentity(systemPrompt);
835
+ return {
836
+ brainPath,
837
+ systemPrompt,
838
+ capabilityTags,
839
+ domain,
840
+ scopeTier,
841
+ requires,
842
+ prefers,
843
+ avoids
844
+ };
720
845
  }
721
846
  function extractIdentity(brain) {
722
847
  const identityBlock = sliceNamedBlock(brain, "identity");
723
- if (!identityBlock) return { domain: "unknown", capabilityTags: [] };
848
+ if (!identityBlock) {
849
+ return { domain: "unknown", capabilityTags: [], requires: [], prefers: [], avoids: [] };
850
+ }
724
851
  const domain = scalarField(identityBlock, "domain") ?? "unknown";
725
852
  const capabilityTags = listField(identityBlock, "capability_tags") ?? [];
726
- return { domain, capabilityTags };
853
+ const requires = listField(identityBlock, "requires") ?? [];
854
+ const prefers = listField(identityBlock, "prefers") ?? [];
855
+ const avoids = listField(identityBlock, "avoids") ?? [];
856
+ return { domain, capabilityTags, requires, prefers, avoids };
727
857
  }
728
858
  function sliceNamedBlock(src, name) {
729
859
  const re = new RegExp(`\\b${name}\\s*:?\\s*\\{`, "g");
@@ -994,6 +1124,181 @@ function applyFilter(events, filter) {
994
1124
  return result;
995
1125
  }
996
1126
 
1127
+ // src/capability-router.ts
1128
+ import {
1129
+ ANTHROPIC_CAPABILITIES,
1130
+ OPENAI_CAPABILITIES,
1131
+ GEMINI_CAPABILITIES,
1132
+ XAI_CAPABILITIES,
1133
+ OPENROUTER_CAPABILITIES,
1134
+ LOCAL_LLM_CAPABILITIES,
1135
+ BITNET_CAPABILITIES,
1136
+ MOCK_CAPABILITIES
1137
+ } from "@holoscript/llm-provider";
1138
+ var NoEligibleProviderError = class extends Error {
1139
+ constructor(requires, avoids, considered, excludedByAvoids) {
1140
+ super(
1141
+ `No provider satisfies brain requires=[${requires.join(", ")}] avoids=[${avoids.join(", ")}]. Considered: [${considered.join(", ")}]. Excluded by avoids: [${excludedByAvoids.join(", ")}].`
1142
+ );
1143
+ this.requires = requires;
1144
+ this.avoids = avoids;
1145
+ this.considered = considered;
1146
+ this.excludedByAvoids = excludedByAvoids;
1147
+ this.name = "NoEligibleProviderError";
1148
+ }
1149
+ };
1150
+ function satisfies(capabilities, key) {
1151
+ const value = capabilities[key];
1152
+ if (typeof value === "boolean") return value;
1153
+ if (typeof value === "number") return value > 0;
1154
+ return false;
1155
+ }
1156
+ function countMatches(capabilities, keys) {
1157
+ let count = 0;
1158
+ for (const key of keys) {
1159
+ if (satisfies(capabilities, key)) count++;
1160
+ }
1161
+ return count;
1162
+ }
1163
+ function unsatisfiedKeys(capabilities, keys) {
1164
+ return keys.filter((key) => !satisfies(capabilities, key));
1165
+ }
1166
+ function pickProvider(opts) {
1167
+ const { brain, envOverride, candidates } = opts;
1168
+ const tieBreaker = opts.tieBreakerOrder ?? candidates.map((c) => c.name);
1169
+ if (candidates.length === 0) {
1170
+ throw new Error("pickProvider: no candidates supplied");
1171
+ }
1172
+ const excludedByAvoids = [];
1173
+ const notAvoided = [];
1174
+ for (const candidate of candidates) {
1175
+ const matchesAvoid = brain.avoids.some((a) => satisfies(candidate.capabilities, a));
1176
+ if (matchesAvoid) {
1177
+ excludedByAvoids.push(candidate.name);
1178
+ } else {
1179
+ notAvoided.push(candidate);
1180
+ }
1181
+ }
1182
+ if (brain.requires.length === 0) {
1183
+ if (envOverride !== void 0) {
1184
+ const envCandidate = candidates.find((c) => c.name === envOverride);
1185
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
1186
+ return {
1187
+ picked: envOverride,
1188
+ reason: "env-override-no-requirements",
1189
+ unsatisfiedRequires: [],
1190
+ matchedPrefers,
1191
+ excludedByAvoids,
1192
+ alternatives: candidates.filter((c) => c.name !== envOverride).map((c) => c.name)
1193
+ };
1194
+ }
1195
+ const ordered = orderCandidates(notAvoided, tieBreaker);
1196
+ if (ordered.length === 0) {
1197
+ return {
1198
+ picked: candidates[0].name,
1199
+ reason: "open-routing-default",
1200
+ unsatisfiedRequires: [],
1201
+ matchedPrefers: brain.prefers.filter(
1202
+ (p) => satisfies(candidates[0].capabilities, p)
1203
+ ),
1204
+ excludedByAvoids,
1205
+ alternatives: candidates.slice(1).map((c) => c.name)
1206
+ };
1207
+ }
1208
+ return {
1209
+ picked: ordered[0].name,
1210
+ reason: "open-routing-default",
1211
+ unsatisfiedRequires: [],
1212
+ matchedPrefers: brain.prefers.filter((p) => satisfies(ordered[0].capabilities, p)),
1213
+ excludedByAvoids,
1214
+ alternatives: ordered.slice(1).map((c) => c.name)
1215
+ };
1216
+ }
1217
+ const eligible = notAvoided.filter((c) => unsatisfiedKeys(c.capabilities, brain.requires).length === 0);
1218
+ if (eligible.length === 0) {
1219
+ if (envOverride !== void 0) {
1220
+ const envCandidate = candidates.find((c) => c.name === envOverride);
1221
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
1222
+ const matchedPrefers = envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [];
1223
+ return {
1224
+ picked: envOverride,
1225
+ reason: "env-override-mismatch",
1226
+ unsatisfiedRequires: unsatisfied,
1227
+ matchedPrefers,
1228
+ excludedByAvoids,
1229
+ alternatives: []
1230
+ };
1231
+ }
1232
+ throw new NoEligibleProviderError(
1233
+ brain.requires,
1234
+ brain.avoids,
1235
+ candidates.map((c) => c.name),
1236
+ excludedByAvoids
1237
+ );
1238
+ }
1239
+ const ranked = [...eligible].sort((a, b) => {
1240
+ const aMatches = countMatches(a.capabilities, brain.prefers);
1241
+ const bMatches = countMatches(b.capabilities, brain.prefers);
1242
+ if (aMatches !== bMatches) return bMatches - aMatches;
1243
+ const aIdx = tieBreaker.indexOf(a.name);
1244
+ const bIdx = tieBreaker.indexOf(b.name);
1245
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
1246
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
1247
+ return aRank - bRank;
1248
+ });
1249
+ if (envOverride !== void 0) {
1250
+ const envEligible = ranked.find((c) => c.name === envOverride);
1251
+ if (envEligible) {
1252
+ return {
1253
+ picked: envOverride,
1254
+ reason: "env-override-satisfies",
1255
+ unsatisfiedRequires: [],
1256
+ matchedPrefers: brain.prefers.filter((p) => satisfies(envEligible.capabilities, p)),
1257
+ excludedByAvoids,
1258
+ alternatives: ranked.filter((c) => c.name !== envOverride).map((c) => c.name)
1259
+ };
1260
+ }
1261
+ const envCandidate = candidates.find((c) => c.name === envOverride);
1262
+ const unsatisfied = envCandidate ? unsatisfiedKeys(envCandidate.capabilities, brain.requires) : brain.requires.slice();
1263
+ return {
1264
+ picked: envOverride,
1265
+ reason: "env-override-mismatch",
1266
+ unsatisfiedRequires: unsatisfied,
1267
+ matchedPrefers: envCandidate ? brain.prefers.filter((p) => satisfies(envCandidate.capabilities, p)) : [],
1268
+ excludedByAvoids,
1269
+ alternatives: ranked.map((c) => c.name)
1270
+ };
1271
+ }
1272
+ const top = ranked[0];
1273
+ return {
1274
+ picked: top.name,
1275
+ reason: "capability-best-fit",
1276
+ unsatisfiedRequires: [],
1277
+ matchedPrefers: brain.prefers.filter((p) => satisfies(top.capabilities, p)),
1278
+ excludedByAvoids,
1279
+ alternatives: ranked.slice(1).map((c) => c.name)
1280
+ };
1281
+ }
1282
+ var BUILT_IN_CANDIDATES = [
1283
+ { name: "anthropic", capabilities: ANTHROPIC_CAPABILITIES },
1284
+ { name: "openai", capabilities: OPENAI_CAPABILITIES },
1285
+ { name: "gemini", capabilities: GEMINI_CAPABILITIES },
1286
+ { name: "xai", capabilities: XAI_CAPABILITIES },
1287
+ { name: "openrouter", capabilities: OPENROUTER_CAPABILITIES },
1288
+ { name: "local-llm", capabilities: LOCAL_LLM_CAPABILITIES },
1289
+ { name: "bitnet", capabilities: BITNET_CAPABILITIES },
1290
+ { name: "mock", capabilities: MOCK_CAPABILITIES }
1291
+ ];
1292
+ function orderCandidates(candidates, tieBreaker) {
1293
+ return [...candidates].sort((a, b) => {
1294
+ const aIdx = tieBreaker.indexOf(a.name);
1295
+ const bIdx = tieBreaker.indexOf(b.name);
1296
+ const aRank = aIdx === -1 ? Number.MAX_SAFE_INTEGER : aIdx;
1297
+ const bRank = bIdx === -1 ? Number.MAX_SAFE_INTEGER : bIdx;
1298
+ return aRank - bRank;
1299
+ });
1300
+ }
1301
+
997
1302
  // src/supervisor.ts
998
1303
  var Supervisor = class {
999
1304
  constructor(opts) {
@@ -1036,13 +1341,29 @@ var Supervisor = class {
1036
1341
  return { ...managed.status };
1037
1342
  }
1038
1343
  async bootAgent(spec) {
1039
- const identity = this.identityFromSpec(spec);
1040
1344
  const brain = await loadBrain(spec.brainPath, spec.scopeTier ?? "warm");
1041
- const provider = await this.opts.providerFactory(spec, identity);
1345
+ const decision = pickProvider({
1346
+ brain,
1347
+ envOverride: spec.provider,
1348
+ candidates: BUILT_IN_CANDIDATES
1349
+ });
1350
+ const effectiveSpec = decision.picked === spec.provider ? spec : { ...spec, provider: decision.picked };
1351
+ const identity = this.identityFromSpec(effectiveSpec);
1352
+ if (decision.reason === "env-override-mismatch" && this.opts.logger) {
1353
+ this.opts.logger({
1354
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1355
+ ev: "capability-router-mismatch",
1356
+ handle: spec.handle,
1357
+ envOverride: spec.provider,
1358
+ unsatisfiedRequires: decision.unsatisfiedRequires,
1359
+ excludedByAvoids: decision.excludedByAvoids
1360
+ });
1361
+ }
1362
+ const provider = await this.opts.providerFactory(effectiveSpec, identity);
1042
1363
  const stateDir = this.opts.stateDir ?? join2(homedir(), ".holoscript-agent", "cost-state");
1043
- const isFree = spec.provider === "mock" || spec.provider === "local-llm" || spec.provider === "bitnet";
1364
+ const isFree = effectiveSpec.provider === "mock" || effectiveSpec.provider === "local-llm" || effectiveSpec.provider === "bitnet";
1044
1365
  const costGuard = new CostGuard({
1045
- statePath: join2(stateDir, `${spec.handle}.json`),
1366
+ statePath: join2(stateDir, `${effectiveSpec.handle}.json`),
1046
1367
  dailyBudgetUsd: identity.budgetUsdPerDay,
1047
1368
  pricer: isFree ? () => 0 : void 0
1048
1369
  });
@@ -1052,7 +1373,7 @@ var Supervisor = class {
1052
1373
  teamId: identity.teamId,
1053
1374
  fetchImpl: this.opts.fetchImpl
1054
1375
  });
1055
- const onTaskExecuted = spec.enableCommitHook ? this.buildCommitHook(spec, identity, mesh) : void 0;
1376
+ const onTaskExecuted = effectiveSpec.enableCommitHook ? this.buildCommitHook(effectiveSpec, identity, mesh) : void 0;
1056
1377
  const runner = new AgentRunner({
1057
1378
  identity,
1058
1379
  brain,
@@ -1061,16 +1382,16 @@ var Supervisor = class {
1061
1382
  mesh,
1062
1383
  onTaskExecuted,
1063
1384
  auditLog: this.auditLog,
1064
- logger: (ev) => this.log({ agent: spec.handle, ...ev })
1385
+ logger: (ev) => this.log({ agent: effectiveSpec.handle, ...ev })
1065
1386
  });
1066
1387
  const status = {
1067
- handle: spec.handle,
1388
+ handle: effectiveSpec.handle,
1068
1389
  state: "starting",
1069
1390
  spentUsd: 0,
1070
1391
  remainingUsd: identity.budgetUsdPerDay,
1071
1392
  restarts: 0
1072
1393
  };
1073
- return { spec, identity, brain, runner, costGuard, status };
1394
+ return { spec: effectiveSpec, identity, brain, runner, costGuard, status };
1074
1395
  }
1075
1396
  buildCommitHook(spec, identity, mesh) {
1076
1397
  const writer = makeCommitHook({