@wrongstack/core 0.285.0 → 0.286.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.
Files changed (145) hide show
  1. package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
  2. package/dist/boot.d.ts.map +1 -1
  3. package/dist/coordination/agent-monitor.d.ts +29 -0
  4. package/dist/coordination/agent-monitor.d.ts.map +1 -1
  5. package/dist/coordination/autonomous-brain.d.ts +0 -2
  6. package/dist/coordination/autonomous-brain.d.ts.map +1 -1
  7. package/dist/coordination/autonomous-coordinator.d.ts +4 -0
  8. package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
  9. package/dist/coordination/brain-ledger.d.ts +142 -0
  10. package/dist/coordination/brain-ledger.d.ts.map +1 -0
  11. package/dist/coordination/brain-monitor.d.ts +31 -2
  12. package/dist/coordination/brain-monitor.d.ts.map +1 -1
  13. package/dist/coordination/brain.d.ts +43 -0
  14. package/dist/coordination/brain.d.ts.map +1 -1
  15. package/dist/coordination/delegate-tool.d.ts.map +1 -1
  16. package/dist/coordination/director-construction.d.ts.map +1 -1
  17. package/dist/coordination/director.d.ts +1 -1
  18. package/dist/coordination/director.d.ts.map +1 -1
  19. package/dist/coordination/fleet.d.ts +4 -4
  20. package/dist/coordination/fleet.d.ts.map +1 -1
  21. package/dist/coordination/global-mailbox.d.ts +63 -2
  22. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  23. package/dist/coordination/index.d.ts +2 -1
  24. package/dist/coordination/index.d.ts.map +1 -1
  25. package/dist/coordination/index.js +1566 -462
  26. package/dist/coordination/index.js.map +4 -4
  27. package/dist/coordination/knowledge-graph.d.ts +13 -1
  28. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  29. package/dist/coordination/mailbox-actions.d.ts +12 -13
  30. package/dist/coordination/mailbox-actions.d.ts.map +1 -1
  31. package/dist/coordination/mailbox-constants.d.ts +64 -0
  32. package/dist/coordination/mailbox-constants.d.ts.map +1 -0
  33. package/dist/coordination/mailbox-events.d.ts +32 -0
  34. package/dist/coordination/mailbox-events.d.ts.map +1 -0
  35. package/dist/coordination/mailbox-message-codec.d.ts +8 -0
  36. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
  37. package/dist/coordination/mailbox-types.d.ts +88 -4
  38. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  39. package/dist/coordination/mailbox.d.ts +21 -2
  40. package/dist/coordination/mailbox.d.ts.map +1 -1
  41. package/dist/coordination/package-author-tracker.d.ts.map +1 -1
  42. package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
  43. package/dist/defaults/index.js +846 -523
  44. package/dist/defaults/index.js.map +4 -4
  45. package/dist/execution/autonomous-runner.d.ts +7 -1
  46. package/dist/execution/autonomous-runner.d.ts.map +1 -1
  47. package/dist/execution/autonomy-brain.d.ts +72 -11
  48. package/dist/execution/autonomy-brain.d.ts.map +1 -1
  49. package/dist/execution/brain-chain.d.ts +53 -0
  50. package/dist/execution/brain-chain.d.ts.map +1 -0
  51. package/dist/execution/brain-evaluation.d.ts +66 -0
  52. package/dist/execution/brain-evaluation.d.ts.map +1 -0
  53. package/dist/execution/brain-runtime.d.ts +171 -0
  54. package/dist/execution/brain-runtime.d.ts.map +1 -0
  55. package/dist/execution/council-brain.d.ts +79 -0
  56. package/dist/execution/council-brain.d.ts.map +1 -0
  57. package/dist/execution/eternal-autonomy.d.ts +12 -0
  58. package/dist/execution/eternal-autonomy.d.ts.map +1 -1
  59. package/dist/execution/index.d.ts +5 -1
  60. package/dist/execution/index.d.ts.map +1 -1
  61. package/dist/execution/index.js +1350 -377
  62. package/dist/execution/index.js.map +4 -4
  63. package/dist/execution/parallel-eternal-engine.d.ts +12 -0
  64. package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
  65. package/dist/execution/tool-executor.d.ts.map +1 -1
  66. package/dist/hq/auth-store.d.ts +21 -3
  67. package/dist/hq/auth-store.d.ts.map +1 -1
  68. package/dist/hq/factory.d.ts +3 -0
  69. package/dist/hq/factory.d.ts.map +1 -1
  70. package/dist/hq/index.js +628 -72
  71. package/dist/hq/index.js.map +4 -4
  72. package/dist/hq/protocol.d.ts +54 -2
  73. package/dist/hq/protocol.d.ts.map +1 -1
  74. package/dist/hq/publisher.d.ts +4 -0
  75. package/dist/hq/publisher.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -6
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3482 -1495
  79. package/dist/index.js.map +4 -4
  80. package/dist/kernel/events.d.ts +22 -4
  81. package/dist/kernel/events.d.ts.map +1 -1
  82. package/dist/kernel/index.js.map +2 -2
  83. package/dist/mailbox-attach.d.ts.map +1 -1
  84. package/dist/observability/index.js +16 -15
  85. package/dist/observability/index.js.map +2 -2
  86. package/dist/security/index.d.ts +1 -0
  87. package/dist/security/index.d.ts.map +1 -1
  88. package/dist/security/index.js +253 -21
  89. package/dist/security/index.js.map +4 -4
  90. package/dist/security/permission-policy-schema.d.ts +77 -0
  91. package/dist/security/permission-policy-schema.d.ts.map +1 -0
  92. package/dist/security/permission-policy.d.ts +5 -0
  93. package/dist/security/permission-policy.d.ts.map +1 -1
  94. package/dist/security/secret-vault.d.ts +9 -0
  95. package/dist/security/secret-vault.d.ts.map +1 -1
  96. package/dist/session-registry.d.ts.map +1 -1
  97. package/dist/storage/config-loader.d.ts +10 -0
  98. package/dist/storage/config-loader.d.ts.map +1 -1
  99. package/dist/storage/goal-store.d.ts +1 -1
  100. package/dist/storage/goal-store.d.ts.map +1 -1
  101. package/dist/storage/index.js +147 -162
  102. package/dist/storage/index.js.map +2 -2
  103. package/dist/storage/plan-store.d.ts +1 -1
  104. package/dist/storage/plan-store.d.ts.map +1 -1
  105. package/dist/storage/queue-store.d.ts +4 -0
  106. package/dist/storage/queue-store.d.ts.map +1 -1
  107. package/dist/storage/session-store.d.ts +9 -0
  108. package/dist/storage/session-store.d.ts.map +1 -1
  109. package/dist/storage/task-store.d.ts +1 -1
  110. package/dist/storage/task-store.d.ts.map +1 -1
  111. package/dist/storage/todos-checkpoint.d.ts +2 -2
  112. package/dist/storage/todos-checkpoint.d.ts.map +1 -1
  113. package/dist/types/config.d.ts +209 -1
  114. package/dist/types/config.d.ts.map +1 -1
  115. package/dist/types/index.js +11 -0
  116. package/dist/types/index.js.map +2 -2
  117. package/dist/types/multi-agent.d.ts +8 -0
  118. package/dist/types/multi-agent.d.ts.map +1 -1
  119. package/dist/types/observability.d.ts +5 -0
  120. package/dist/types/observability.d.ts.map +1 -1
  121. package/dist/types/tool.d.ts +7 -0
  122. package/dist/types/tool.d.ts.map +1 -1
  123. package/dist/utils/glob-expand.d.ts.map +1 -1
  124. package/dist/utils/incoming-images.d.ts +39 -0
  125. package/dist/utils/incoming-images.d.ts.map +1 -0
  126. package/dist/utils/index.d.ts +1 -0
  127. package/dist/utils/index.d.ts.map +1 -1
  128. package/dist/utils/index.js +81 -3
  129. package/dist/utils/index.js.map +3 -3
  130. package/dist/worktree/worktree-manager.d.ts +13 -4
  131. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  132. package/instructions/agents/browser.md +25 -16
  133. package/instructions/coordination/subagent-baseline.md +16 -2
  134. package/instructions/leader-after-task.md +3 -1
  135. package/instructions/sections/tool/common-patterns.md +7 -1
  136. package/instructions/system.md +47 -0
  137. package/package.json +2 -2
  138. package/skills/docker-deploy/SKILL.md +1 -1
  139. package/skills/wrongstack-mailbox/SKILL.md +51 -12
  140. package/dist/plugins/git-plugin.d.ts +0 -15
  141. package/dist/plugins/git-plugin.d.ts.map +0 -1
  142. package/dist/plugins/observability-plugin.d.ts +0 -19
  143. package/dist/plugins/observability-plugin.d.ts.map +0 -1
  144. package/dist/plugins/plan-plugin.d.ts +0 -16
  145. package/dist/plugins/plan-plugin.d.ts.map +0 -1
@@ -2069,19 +2069,27 @@ function adaptThresholdsForSignals(thresholds, signals) {
2069
2069
 
2070
2070
  // src/execution/autonomous-runner.ts
2071
2071
  var DoneConditionChecker = class {
2072
- constructor(condition) {
2072
+ constructor(condition, opts) {
2073
2073
  this.condition = condition;
2074
+ this.logger = opts?.logger;
2074
2075
  if (condition.type === "output_match" && condition.pattern) {
2075
2076
  const result = compileUserRegex(condition.pattern, "");
2076
2077
  this.compiledRegex = result.ok ? result.regex : null;
2077
2078
  if (!result.ok) {
2078
- console.warn(JSON.stringify({
2079
- level: "warn",
2080
- event: "autonomous.done_condition_invalid_regex",
2081
- pattern: condition.pattern,
2082
- reason: result.reason,
2083
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2084
- }));
2079
+ if (this.logger) {
2080
+ this.logger.warn(
2081
+ "Done condition has invalid regex \u2014 will never match",
2082
+ { event: "autonomous.done_condition_invalid_regex", pattern: condition.pattern, reason: result.reason }
2083
+ );
2084
+ } else {
2085
+ console.warn(JSON.stringify({
2086
+ level: "warn",
2087
+ event: "autonomous.done_condition_invalid_regex",
2088
+ pattern: condition.pattern,
2089
+ reason: result.reason,
2090
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2091
+ }));
2092
+ }
2085
2093
  }
2086
2094
  } else {
2087
2095
  this.compiledRegex = null;
@@ -2089,6 +2097,7 @@ var DoneConditionChecker = class {
2089
2097
  }
2090
2098
  condition;
2091
2099
  compiledRegex;
2100
+ logger;
2092
2101
  check(state) {
2093
2102
  switch (this.condition.type) {
2094
2103
  case "iterations":
@@ -2139,7 +2148,7 @@ var DoneConditionChecker = class {
2139
2148
  var AutonomousRunner = class {
2140
2149
  constructor(opts) {
2141
2150
  this.opts = opts;
2142
- this.doneChecker = new DoneConditionChecker(opts.doneCondition);
2151
+ this.doneChecker = new DoneConditionChecker(opts.doneCondition, { logger: opts.logger });
2143
2152
  }
2144
2153
  opts;
2145
2154
  iterations = 0;
@@ -2300,12 +2309,25 @@ function createTieredBrainArbiter(opts) {
2300
2309
  return {
2301
2310
  async decide(request) {
2302
2311
  const policyDecision = await opts.policy.decide(request);
2303
- if (policyDecision.type !== "ask_human") return policyDecision;
2312
+ const provisionalContinue = policyDecision.type === "answer" && policyDecision.optionId === void 0 && request.fallback === "continue";
2313
+ if (policyDecision.type !== "ask_human" && !provisionalContinue) return policyDecision;
2304
2314
  const ceiling = opts.getMaxAutoRisk?.() ?? "medium";
2305
- if (!opts.autonomous || ceiling === "off") return policyDecision;
2315
+ if (ceiling === "off") return policyDecision;
2306
2316
  const ceilingLevel = ceiling === "all" ? 3 : RISK_LEVELS[ceiling] ?? 1;
2307
2317
  const requestLevel = RISK_LEVELS[request.risk] ?? 2;
2308
2318
  if (requestLevel > ceilingLevel) return policyDecision;
2319
+ if (opts.council) {
2320
+ const floor = opts.getCouncilMinRisk?.() ?? "high";
2321
+ const floorLevel = RISK_LEVELS[floor] ?? 2;
2322
+ if (requestLevel >= floorLevel) {
2323
+ try {
2324
+ const councilDecision = await opts.council.decide(request);
2325
+ if (councilDecision.type !== "ask_human") return councilDecision;
2326
+ } catch {
2327
+ }
2328
+ }
2329
+ }
2330
+ if (!opts.autonomous) return policyDecision;
2309
2331
  try {
2310
2332
  const llmDecision = await opts.autonomous.decide(request);
2311
2333
  if (llmDecision.type === "answer") return llmDecision;
@@ -2319,6 +2341,12 @@ function createAutonomyBrain(opts) {
2319
2341
  const maxRisk = opts.maxAutoRisk ?? "high";
2320
2342
  const maxRiskLevel = RISK_LEVELS[maxRisk] ?? 2;
2321
2343
  const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
2344
+ const targets = opts.targets && opts.targets.length > 0 ? opts.targets : opts.provider && opts.model ? [{ provider: opts.provider, model: opts.model }] : [];
2345
+ if (targets.length === 0) {
2346
+ throw new Error("createAutonomyBrain: provide `targets` or `provider` + `model`.");
2347
+ }
2348
+ const strategy = opts.strategy ?? "fallback";
2349
+ let rrCursor = 0;
2322
2350
  return {
2323
2351
  async decide(request) {
2324
2352
  const requestLevel = RISK_LEVELS[request.risk] ?? 2;
@@ -2337,7 +2365,10 @@ function createAutonomyBrain(opts) {
2337
2365
  opts.onDecision?.(formatDecisionSummary(heuristic, request), heuristic, request);
2338
2366
  return heuristic;
2339
2367
  }
2340
- const llmDecision = await llmDecide(request, opts.provider, opts.model, timeoutMs);
2368
+ const start = strategy === "round-robin" ? rrCursor++ % targets.length : 0;
2369
+ const ordered = [...targets.slice(start), ...targets.slice(0, start)];
2370
+ const digest = opts.getDecisionDigest?.(request);
2371
+ const llmDecision = await llmDecide(request, ordered, timeoutMs, digest);
2341
2372
  opts.onDecision?.(formatDecisionSummary(llmDecision, request), llmDecision, request);
2342
2373
  return llmDecision;
2343
2374
  }
@@ -2355,6 +2386,7 @@ function formatDecisionSummary(decision, request) {
2355
2386
  return `\u{1F9E0} Brain: ASKED HUMAN \u2014 "${question}"`;
2356
2387
  }
2357
2388
  function quickDecide(request) {
2389
+ if (request.options?.length) return null;
2358
2390
  const q = request.question.toLowerCase();
2359
2391
  const ctx = request.context?.toLowerCase() ?? "";
2360
2392
  if (q.includes("deadlock") && ctx.includes("failed")) {
@@ -2364,7 +2396,7 @@ function quickDecide(request) {
2364
2396
  rationale: "Heuristic: deadlocked tasks blocked by failed dependencies \u2014 skipping unblocks remaining work."
2365
2397
  };
2366
2398
  }
2367
- if ((q.includes("failed") || q.includes("retry")) && (ctx.includes("3") || ctx.includes("exhausted"))) {
2399
+ if ((q.includes("failed") || q.includes("retry")) && (/\bexhausted\b/.test(ctx) || /\b(?:[3-9]|\d{2,})\s+(?:consecutive\s+)?(?:times|attempts|retries|failures)\b/.test(ctx) || /\b(?:attempt|retr(?:y|ies)|failure)s?\W{0,3}(?:[3-9]|\d{2,})\b/.test(ctx))) {
2368
2400
  return {
2369
2401
  type: "answer",
2370
2402
  text: "Mark as failed and move on. Note the failure for the final report.",
@@ -2374,7 +2406,7 @@ function quickDecide(request) {
2374
2406
  if (q.includes("goal complete") || q.includes("mission complete")) {
2375
2407
  return null;
2376
2408
  }
2377
- if (q.includes("continue") || q.includes("proceed")) {
2409
+ if (request.fallback === "continue" && /\b(?:continue|proceed)\b/.test(q) && !/\b(?:stop|abort|halt|cancel|pause|rollback)\b/.test(q) && !/\bor\b/.test(q)) {
2378
2410
  return {
2379
2411
  type: "answer",
2380
2412
  text: "Continue execution. Do not stop.",
@@ -2383,94 +2415,1160 @@ function quickDecide(request) {
2383
2415
  }
2384
2416
  return null;
2385
2417
  }
2386
- async function llmDecide(request, provider, model, timeoutMs) {
2418
+ function buildBrainUserMessage(request) {
2387
2419
  const optionsText = request.options?.length ? "\nOptions:\n" + request.options.map(
2388
2420
  (o) => ` [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}${o.recommended ? " \u2605 recommended" : ""}`
2389
2421
  ).join("\n") : "";
2390
- const systemPrompt = readBundledInstructionText("llm/autonomy-brain.md");
2391
- const userMessage = [
2422
+ return [
2392
2423
  `Question: ${request.question}`,
2393
2424
  request.context ? `
2394
2425
  Context:
2395
2426
  ${request.context}` : "",
2396
2427
  optionsText
2397
2428
  ].filter(Boolean).join("\n");
2398
- try {
2399
- const signal = AbortSignal.timeout(timeoutMs);
2400
- const response = await provider.complete(
2401
- {
2402
- model,
2403
- system: [{ type: "text", text: systemPrompt }],
2404
- messages: [{ role: "user", content: userMessage || "Decide." }],
2405
- maxTokens: 200
2406
- },
2407
- { signal }
2429
+ }
2430
+ function withDecisionDigest(user, digest) {
2431
+ if (!digest?.trim()) return user;
2432
+ return `${user}
2433
+
2434
+ Outcome history of similar past decisions (learn from it):
2435
+ ${digest}`;
2436
+ }
2437
+ async function completeBrainLlm(target, input) {
2438
+ const signal = AbortSignal.timeout(input.timeoutMs);
2439
+ const response = await target.provider.complete(
2440
+ {
2441
+ model: target.model,
2442
+ system: [{ type: "text", text: input.system }],
2443
+ messages: [{ role: "user", content: input.user || "Decide." }],
2444
+ maxTokens: input.maxTokens ?? 200
2445
+ },
2446
+ { signal }
2447
+ );
2448
+ return extractText(response).trim();
2449
+ }
2450
+ async function llmDecide(request, targets, timeoutMs, digest) {
2451
+ const systemPrompt = readBundledInstructionText("llm/autonomy-brain.md");
2452
+ const userMessage = withDecisionDigest(buildBrainUserMessage(request), digest);
2453
+ let text = null;
2454
+ for (const target of targets) {
2455
+ try {
2456
+ text = await completeBrainLlm(target, {
2457
+ system: systemPrompt,
2458
+ user: userMessage,
2459
+ timeoutMs
2460
+ });
2461
+ break;
2462
+ } catch {
2463
+ }
2464
+ }
2465
+ if (text === null) {
2466
+ if (request.fallback === "continue") {
2467
+ return {
2468
+ type: "answer",
2469
+ text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
2470
+ };
2471
+ }
2472
+ return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
2473
+ }
2474
+ if (request.options?.length) {
2475
+ const parsed = parseOptionDecision(text, request.options);
2476
+ if (parsed) {
2477
+ return parsed;
2478
+ }
2479
+ return {
2480
+ type: "deny",
2481
+ reason: "Autonomy Brain returned no exact valid option id."
2482
+ };
2483
+ }
2484
+ return {
2485
+ type: "answer",
2486
+ text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
2487
+ rationale: text || void 0
2488
+ };
2489
+ }
2490
+ function parseOptionDecision(rawText, options) {
2491
+ const text = rawText.trim();
2492
+ const byId = new Map(options.map((option2) => [option2.id, option2]));
2493
+ const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
2494
+ const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
2495
+ const parsed = safeParse(jsonCandidate, 16384);
2496
+ if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
2497
+ const option2 = byId.get(parsed.value.optionId);
2498
+ if (!option2) return null;
2499
+ return {
2500
+ type: "answer",
2501
+ optionId: option2.id,
2502
+ text: option2.label,
2503
+ rationale: typeof parsed.value.rationale === "string" && parsed.value.rationale.trim() ? parsed.value.rationale.trim() : void 0
2504
+ };
2505
+ }
2506
+ const legacy = /^\s*\[([^\]\r\n]+)\](?:\s*(?:—|-|:)\s*)?([\s\S]*)$/.exec(text);
2507
+ if (!legacy) return null;
2508
+ const option = byId.get((legacy[1] ?? "").trim());
2509
+ if (!option) return null;
2510
+ const rationale = (legacy[2] ?? "").trim();
2511
+ return {
2512
+ type: "answer",
2513
+ optionId: option.id,
2514
+ text: option.label,
2515
+ rationale: rationale || void 0
2516
+ };
2517
+ }
2518
+ function extractText(result) {
2519
+ if (!result || typeof result !== "object") return "";
2520
+ const r = result;
2521
+ if (Array.isArray(r.content)) {
2522
+ return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
2523
+ }
2524
+ if (Array.isArray(r.choices)) {
2525
+ return r.choices[0]?.message?.content ?? "";
2526
+ }
2527
+ return typeof r.text === "string" ? r.text : "";
2528
+ }
2529
+
2530
+ // src/types/provider.ts
2531
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
2532
+ var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
2533
+ function classifyProviderError(status, body, message) {
2534
+ const type = body?.type;
2535
+ if (status === 0) return "network";
2536
+ if (status === 408) return "timeout";
2537
+ if (status === 599) return "stream_hang";
2538
+ if (type === "rate_limit_error" || status === 429) return "rate_limit";
2539
+ if (type === "overloaded_error" || status === 529) return "overloaded";
2540
+ if (status >= 500) return "server";
2541
+ if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
2542
+ return "auth";
2543
+ }
2544
+ const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
2545
+ if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
2546
+ if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
2547
+ return "context_overflow";
2548
+ }
2549
+ if (status >= 400) return "invalid_request";
2550
+ return "unknown";
2551
+ }
2552
+ function isRetryableKind(kind) {
2553
+ return RETRYABLE_BY_KIND[kind];
2554
+ }
2555
+ var RETRYABLE_BY_KIND = {
2556
+ rate_limit: true,
2557
+ overloaded: true,
2558
+ server: true,
2559
+ timeout: true,
2560
+ network: true,
2561
+ stream_hang: true,
2562
+ auth: false,
2563
+ context_overflow: false,
2564
+ content_filter: false,
2565
+ invalid_request: false,
2566
+ unknown: false
2567
+ };
2568
+ var ProviderError = class extends WrongStackError {
2569
+ status;
2570
+ retryable;
2571
+ providerId;
2572
+ /** Canonical failure classification — see {@link ProviderErrorKind}. */
2573
+ kind;
2574
+ body;
2575
+ constructor(message, status, retryable, providerId, opts = {}) {
2576
+ const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
2577
+ super({
2578
+ message,
2579
+ code: kindToCode(kind),
2580
+ subsystem: "provider",
2581
+ severity: status >= 500 ? "error" : "warning",
2582
+ recoverable: retryable,
2583
+ context: { providerId, status },
2584
+ cause: opts.cause
2585
+ });
2586
+ this.name = "ProviderError";
2587
+ this.status = status;
2588
+ this.retryable = retryable;
2589
+ this.providerId = providerId;
2590
+ this.kind = kind;
2591
+ this.body = opts.body;
2592
+ }
2593
+ /**
2594
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
2595
+ * status line and the agent's retry warning. Avoids dumping raw JSON
2596
+ * (which is what users see today when a 529 lands and the log message
2597
+ * includes the full `{"type":"error",...}` body).
2598
+ *
2599
+ * Examples:
2600
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
2601
+ * "openai rate limited (429): Retry after 12s"
2602
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
2603
+ * "groq HTTP 500 (server error)"
2604
+ */
2605
+ describe() {
2606
+ const kind = describeStatus(this.status, this.body?.type);
2607
+ const head = `${this.providerId} ${kind}`;
2608
+ const detail = this.body?.message?.trim();
2609
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
2610
+ if (detail && detail.length > 0) {
2611
+ return `${head}: ${truncate(detail, 240)}${reqId}`;
2612
+ }
2613
+ return `${head}${reqId}`;
2614
+ }
2615
+ };
2616
+ function describeStatus(status, type) {
2617
+ if (status === 0) return "network error";
2618
+ if (status === 599) return `stream hang (${status})`;
2619
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
2620
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
2621
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
2622
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
2623
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
2624
+ if (type === "content_filter") return `content filtered (${status})`;
2625
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
2626
+ if (status === 408) return `timeout (${status})`;
2627
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
2628
+ if (type) return `${type} (${status})`;
2629
+ return `HTTP ${status}`;
2630
+ }
2631
+ var KIND_TO_CODE = {
2632
+ network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2633
+ timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2634
+ rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
2635
+ auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
2636
+ overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
2637
+ context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
2638
+ server: ERROR_CODES.PROVIDER_SERVER_ERROR,
2639
+ stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
2640
+ content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2641
+ invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2642
+ unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
2643
+ };
2644
+ function kindToCode(kind) {
2645
+ return KIND_TO_CODE[kind];
2646
+ }
2647
+
2648
+ // src/core/fallback-model.ts
2649
+ function parseModelRef(ref) {
2650
+ const trimmed = ref.trim();
2651
+ const slash = trimmed.indexOf("/");
2652
+ if (slash !== -1) {
2653
+ return {
2654
+ provider: trimmed.slice(0, slash) || void 0,
2655
+ model: trimmed.slice(slash + 1).trim()
2656
+ };
2657
+ }
2658
+ const parts = trimmed.split(/\s+/);
2659
+ if (parts.length >= 2) {
2660
+ return { provider: parts[0], model: parts.slice(1).join(" ") };
2661
+ }
2662
+ return { model: trimmed };
2663
+ }
2664
+ var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
2665
+
2666
+ // src/execution/council-brain.ts
2667
+ var COUNCIL_REFUSE_OPTION_ID = "council_refuse";
2668
+ var BUILTIN_PERSONAS = {
2669
+ executor: "PERSONA: You hold the EXECUTOR seat. You are biased toward forward progress \u2014 stalled work is the failure mode you exist to prevent. Favor the option that keeps the system moving, unless doing so is clearly reckless.",
2670
+ skeptic: "PERSONA: You hold the SKEPTIC seat. Your job is to find the concrete reason the proposed action is wrong, unsafe, or based on a false premise. If you find one, vote against it or refuse. Do not oppose for its own sake \u2014 a sound proposal deserves your vote.",
2671
+ auditor: "PERSONA: You hold the AUDITOR seat. You judge through the cost/waste lens: budget already burned, expected cost of each option, and the odds of throwing resources at a dead end. Prefer the option with the best expected value per token spent."
2672
+ };
2673
+ function personaText(persona) {
2674
+ if (!persona) return BUILTIN_PERSONAS["executor"] ?? "";
2675
+ return BUILTIN_PERSONAS[persona] ?? `PERSONA: ${persona}`;
2676
+ }
2677
+ function voterLabel(voter, index) {
2678
+ return voter.label ?? `${voter.persona ?? "voter"}#${index + 1} (${voter.model})`;
2679
+ }
2680
+ function ballotOptions(request) {
2681
+ const options = request.options ?? [];
2682
+ if (options.some((o) => o.id === COUNCIL_REFUSE_OPTION_ID)) return options;
2683
+ return [
2684
+ ...options,
2685
+ {
2686
+ id: COUNCIL_REFUSE_OPTION_ID,
2687
+ label: "Refuse \u2014 none of these options should be taken",
2688
+ consequence: "The proposed action is not taken at all."
2689
+ }
2690
+ ];
2691
+ }
2692
+ function createCouncilBrainArbiter(opts) {
2693
+ if (opts.voters.length === 0) {
2694
+ throw new Error("createCouncilBrainArbiter: at least one voter is required.");
2695
+ }
2696
+ const quorumFraction = opts.quorumFraction ?? 0.5;
2697
+ const approvalFraction = opts.approvalFraction ?? 0.5;
2698
+ const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
2699
+ const baseSystem = readBundledInstructionText("llm/autonomy-brain.md");
2700
+ const abstain = (request, why) => ({
2701
+ type: "ask_human",
2702
+ prompt: `Council abstained (${why}): ${request.question}`,
2703
+ options: request.options,
2704
+ rationale: `Council could not reach a decision: ${why}.`
2705
+ });
2706
+ const finish = (request, decision) => {
2707
+ opts.onDecision?.(formatDecisionSummary(decision, request), decision, request);
2708
+ return decision;
2709
+ };
2710
+ async function collectVotes(request, ballot, digest) {
2711
+ const ballotRequest = { ...request, options: ballot };
2712
+ const user = withDecisionDigest(buildBrainUserMessage(ballotRequest), digest);
2713
+ const settled = await Promise.allSettled(
2714
+ opts.voters.map(
2715
+ (voter) => completeBrainLlm(voter, {
2716
+ system: `${baseSystem}
2717
+
2718
+ ${personaText(voter.persona)}`,
2719
+ user,
2720
+ timeoutMs
2721
+ })
2722
+ )
2723
+ );
2724
+ const votes = [];
2725
+ settled.forEach((result, i) => {
2726
+ const voter = opts.voters[i];
2727
+ if (!voter || result.status !== "fulfilled") return;
2728
+ const parsed = parseOptionDecision(result.value, ballot);
2729
+ if (parsed?.type !== "answer" || !parsed.optionId) return;
2730
+ votes.push({ voter, optionId: parsed.optionId, rationale: parsed.rationale });
2731
+ });
2732
+ return votes;
2733
+ }
2734
+ async function judgeRound(request, ballot, votes, reason) {
2735
+ if (!opts.judge) return null;
2736
+ const voteLines = votes.map(
2737
+ (v, i) => `- ${voterLabel(v.voter, i)} voted [${v.optionId}]` + (v.rationale ? ` \u2014 ${v.rationale}` : "")
2738
+ ).join("\n");
2739
+ const judgeRequest = {
2740
+ ...request,
2741
+ options: ballot,
2742
+ context: [
2743
+ request.context ?? "",
2744
+ "",
2745
+ `Council votes (${reason} \u2014 you are the judge; issue the final decision):`,
2746
+ voteLines
2747
+ ].join("\n").trim()
2748
+ };
2749
+ try {
2750
+ const text = await completeBrainLlm(opts.judge, {
2751
+ system: `${baseSystem}
2752
+
2753
+ PERSONA: You are the council JUDGE. The voting seats disagreed or were too close to call. Weigh their rationales and issue the single final decision.`,
2754
+ user: buildBrainUserMessage(judgeRequest),
2755
+ timeoutMs,
2756
+ maxTokens: 300
2757
+ });
2758
+ const parsed = parseOptionDecision(text, ballot);
2759
+ if (parsed?.type === "answer" && parsed.optionId) return parsed;
2760
+ return null;
2761
+ } catch {
2762
+ return null;
2763
+ }
2764
+ }
2765
+ return {
2766
+ async decide(request) {
2767
+ const digest = opts.getDecisionDigest?.(request);
2768
+ if (!request.options?.length) {
2769
+ const user = withDecisionDigest(buildBrainUserMessage(request), digest);
2770
+ const settled = await Promise.allSettled(
2771
+ opts.voters.map(
2772
+ (voter) => completeBrainLlm(voter, {
2773
+ system: `${baseSystem}
2774
+
2775
+ ${personaText(voter.persona)}`,
2776
+ user,
2777
+ timeoutMs
2778
+ })
2779
+ )
2780
+ );
2781
+ const stances = [];
2782
+ settled.forEach((result, i) => {
2783
+ const voter = opts.voters[i];
2784
+ if (!voter || result.status !== "fulfilled" || !result.value.trim()) return;
2785
+ stances.push({ voter, text: result.value.trim() });
2786
+ });
2787
+ if (stances.length / opts.voters.length < quorumFraction) {
2788
+ return finish(request, abstain(request, "quorum not met"));
2789
+ }
2790
+ if (opts.judge) {
2791
+ const stanceLines = stances.map((s, i) => `- ${voterLabel(s.voter, i)}: ${s.text}`).join("\n");
2792
+ try {
2793
+ const text = await completeBrainLlm(opts.judge, {
2794
+ system: `${baseSystem}
2795
+
2796
+ PERSONA: You are the council JUDGE. Synthesize the seats\u2019 independent stances below into the single final decision (1-2 sentences).`,
2797
+ user: `${buildBrainUserMessage(request)}
2798
+
2799
+ Council stances:
2800
+ ${stanceLines}`,
2801
+ timeoutMs,
2802
+ maxTokens: 300
2803
+ });
2804
+ if (text) {
2805
+ return finish(request, {
2806
+ type: "answer",
2807
+ text,
2808
+ rationale: `Council synthesis of ${stances.length}/${opts.voters.length} stances.`
2809
+ });
2810
+ }
2811
+ } catch {
2812
+ }
2813
+ }
2814
+ const first = stances[0];
2815
+ if (first) {
2816
+ return finish(request, {
2817
+ type: "answer",
2818
+ text: first.text,
2819
+ rationale: `Council (no judge): stance of ${voterLabel(first.voter, 0)}.`
2820
+ });
2821
+ }
2822
+ return finish(request, abstain(request, "no usable stance"));
2823
+ }
2824
+ const ballot = ballotOptions(request);
2825
+ const votes = await collectVotes(request, ballot, digest);
2826
+ if (votes.length / opts.voters.length < quorumFraction) {
2827
+ return finish(request, abstain(request, `quorum not met (${votes.length}/${opts.voters.length} votes)`));
2828
+ }
2829
+ const vetoRefusal = votes.find(
2830
+ (v) => v.voter.veto === true && v.optionId === COUNCIL_REFUSE_OPTION_ID
2831
+ );
2832
+ if (vetoRefusal) {
2833
+ return finish(request, {
2834
+ type: "deny",
2835
+ reason: `Council veto by ${voterLabel(vetoRefusal.voter, 0)}` + (vetoRefusal.rationale ? `: ${vetoRefusal.rationale}` : ".")
2836
+ });
2837
+ }
2838
+ const weightByOption = /* @__PURE__ */ new Map();
2839
+ let castWeight = 0;
2840
+ for (const v of votes) {
2841
+ const w = v.voter.weight ?? 1;
2842
+ castWeight += w;
2843
+ weightByOption.set(v.optionId, (weightByOption.get(v.optionId) ?? 0) + w);
2844
+ }
2845
+ let winner = null;
2846
+ let contested = false;
2847
+ for (const [optionId, weight] of weightByOption) {
2848
+ if (!winner || weight > winner.weight) {
2849
+ winner = { optionId, weight };
2850
+ contested = false;
2851
+ } else if (weight === winner.weight) {
2852
+ contested = true;
2853
+ }
2854
+ }
2855
+ const decisive = winner !== null && !contested && winner.weight > approvalFraction * castWeight;
2856
+ let finalOptionId = decisive && winner ? winner.optionId : null;
2857
+ let viaJudge = false;
2858
+ if (!finalOptionId) {
2859
+ const judged = await judgeRound(
2860
+ request,
2861
+ ballot,
2862
+ votes,
2863
+ contested ? "tie" : "no option cleared the approval threshold"
2864
+ );
2865
+ if (judged?.type === "answer" && judged.optionId) {
2866
+ finalOptionId = judged.optionId;
2867
+ viaJudge = true;
2868
+ }
2869
+ }
2870
+ if (!finalOptionId) {
2871
+ return finish(request, abstain(request, "split vote and no judge verdict"));
2872
+ }
2873
+ const supporting = votes.filter((v) => v.optionId === finalOptionId);
2874
+ const voteSummary = votes.map((v, i) => `${voterLabel(v.voter, i)}\u2192[${v.optionId}]`).join(", ");
2875
+ const rationale = (supporting.find((v) => v.rationale)?.rationale ?? "") + ` (council${viaJudge ? " via judge" : ""}: ${voteSummary})`;
2876
+ if (finalOptionId === COUNCIL_REFUSE_OPTION_ID) {
2877
+ return finish(request, {
2878
+ type: "deny",
2879
+ reason: `Council refused the proposed action.${rationale}`
2880
+ });
2881
+ }
2882
+ const option = request.options.find((o) => o.id === finalOptionId);
2883
+ return finish(request, {
2884
+ type: "answer",
2885
+ optionId: finalOptionId,
2886
+ text: option?.label ?? finalOptionId,
2887
+ rationale: rationale.trim()
2888
+ });
2889
+ }
2890
+ };
2891
+ }
2892
+
2893
+ // src/execution/brain-chain.ts
2894
+ var DEFAULT_SEATS = [
2895
+ { persona: "executor" },
2896
+ { persona: "skeptic", veto: true },
2897
+ { persona: "auditor" }
2898
+ ];
2899
+ function assembleBrainTiers(opts) {
2900
+ const brainCfg = opts.brainConfig;
2901
+ const toEntry = (entry) => typeof entry === "string" ? parseModelRef(entry) : entry;
2902
+ const buildTarget = (entry) => {
2903
+ if (!entry.model) return null;
2904
+ const providerId = entry.provider ?? opts.defaultProviderId;
2905
+ try {
2906
+ const provider = providerId === opts.defaultProviderId ? opts.sessionProvider() : opts.resolveProvider(providerId, entry.model);
2907
+ if (!provider) return null;
2908
+ return { provider, model: entry.model, label: `${providerId}/${entry.model}` };
2909
+ } catch {
2910
+ return null;
2911
+ }
2912
+ };
2913
+ const poolTargets = (brainCfg?.models ?? []).map(toEntry).map(buildTarget).filter((t) => t !== null);
2914
+ const autonomous = poolTargets.length > 0 ? createAutonomyBrain({
2915
+ targets: poolTargets,
2916
+ strategy: brainCfg?.strategy ?? "fallback",
2917
+ maxAutoRisk: "all",
2918
+ // the tiered ceiling gates risk — keep inner permissive
2919
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
2920
+ getDecisionDigest: opts.getDecisionDigest
2921
+ }) : {
2922
+ decide: (request) => createAutonomyBrain({
2923
+ provider: opts.sessionProvider(),
2924
+ model: opts.sessionModel(),
2925
+ maxAutoRisk: "all",
2926
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
2927
+ getDecisionDigest: opts.getDecisionDigest
2928
+ }).decide(request)
2929
+ };
2930
+ const councilCfg = brainCfg?.council;
2931
+ const explicitVoters = (councilCfg?.voters ?? []).map(
2932
+ (v) => typeof v === "string" ? { entry: toEntry(v), cfg: {} } : { entry: v, cfg: v }
2933
+ );
2934
+ const voters = (explicitVoters.length > 0 ? explicitVoters.map(({ entry, cfg }, i) => {
2935
+ const target = buildTarget(entry);
2936
+ if (!target) return null;
2937
+ const seat = DEFAULT_SEATS[i % DEFAULT_SEATS.length];
2938
+ return {
2939
+ ...target,
2940
+ persona: cfg.persona ?? seat?.persona,
2941
+ weight: cfg.weight,
2942
+ veto: cfg.veto ?? seat?.veto
2943
+ };
2944
+ }) : poolTargets.slice(0, DEFAULT_SEATS.length).map((target, i) => ({
2945
+ ...target,
2946
+ ...DEFAULT_SEATS[i % DEFAULT_SEATS.length]
2947
+ }))).filter((v) => v !== null);
2948
+ const judgeEntry = councilCfg?.judge ? typeof councilCfg.judge === "string" ? toEntry(councilCfg.judge) : councilCfg.judge : void 0;
2949
+ const judge = (judgeEntry ? buildTarget(judgeEntry) : null) ?? poolTargets[0] ?? voters[0];
2950
+ const councilEnabled = councilCfg?.enabled ?? voters.length >= 2;
2951
+ const council = councilEnabled && voters.length >= 2 ? createCouncilBrainArbiter({
2952
+ voters,
2953
+ judge,
2954
+ quorumFraction: councilCfg?.quorum,
2955
+ approvalFraction: councilCfg?.approval,
2956
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
2957
+ getDecisionDigest: opts.getDecisionDigest
2958
+ }) : void 0;
2959
+ return {
2960
+ autonomous,
2961
+ council,
2962
+ getCouncilMinRisk: () => councilCfg?.minRisk ?? "high",
2963
+ poolLabels: poolTargets.map((t) => t.label ?? t.model),
2964
+ councilLabels: council ? voters.map((v) => `${v.label ?? v.model} (${v.persona ?? "voter"}${v.veto ? ", veto" : ""})`) : []
2965
+ };
2966
+ }
2967
+
2968
+ // src/execution/brain-evaluation.ts
2969
+ var BRAIN_EVALUATION_CASE_VERSION = 1;
2970
+ function isRecord3(value) {
2971
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2972
+ }
2973
+ function isStringArray(value) {
2974
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
2975
+ }
2976
+ function validateBrainEvaluationCase(value) {
2977
+ const diagnostics = [];
2978
+ if (!isRecord3(value)) return { ok: false, diagnostics: ["case must be an object"] };
2979
+ if (value["version"] !== BRAIN_EVALUATION_CASE_VERSION) {
2980
+ diagnostics.push(`version must be ${BRAIN_EVALUATION_CASE_VERSION}`);
2981
+ }
2982
+ if (typeof value["id"] !== "string" || value["id"].trim().length === 0) {
2983
+ diagnostics.push("id must be a non-empty string");
2984
+ }
2985
+ const request = value["request"];
2986
+ if (!isRecord3(request)) {
2987
+ diagnostics.push("request must be an object");
2988
+ } else {
2989
+ if (typeof request["id"] !== "string" || request["id"].trim().length === 0) {
2990
+ diagnostics.push("request.id must be a non-empty string");
2991
+ }
2992
+ if (!["autophase", "director", "tool", "user", "system"].includes(String(request["source"]))) {
2993
+ diagnostics.push("request.source is invalid");
2994
+ }
2995
+ if (typeof request["question"] !== "string" || request["question"].trim().length === 0) {
2996
+ diagnostics.push("request.question must be a non-empty string");
2997
+ }
2998
+ if (!["low", "medium", "high", "critical"].includes(String(request["risk"]))) {
2999
+ diagnostics.push("request.risk is invalid");
3000
+ }
3001
+ if (!["ask_human", "deny", "continue"].includes(String(request["fallback"]))) {
3002
+ diagnostics.push("request.fallback is invalid");
3003
+ }
3004
+ if (request["options"] !== void 0) {
3005
+ if (!Array.isArray(request["options"])) {
3006
+ diagnostics.push("request.options must be an array");
3007
+ } else {
3008
+ const ids = /* @__PURE__ */ new Set();
3009
+ for (const [index, option] of request["options"].entries()) {
3010
+ if (!isRecord3(option)) {
3011
+ diagnostics.push(`request.options[${index}] must be an object`);
3012
+ continue;
3013
+ }
3014
+ const id = option["id"];
3015
+ if (typeof id !== "string" || id.trim().length === 0) {
3016
+ diagnostics.push(`request.options[${index}].id must be a non-empty string`);
3017
+ } else if (ids.has(id)) {
3018
+ diagnostics.push(`request option id "${id}" is duplicated`);
3019
+ } else {
3020
+ ids.add(id);
3021
+ }
3022
+ if (typeof option["label"] !== "string" || option["label"].trim().length === 0) {
3023
+ diagnostics.push(`request.options[${index}].label must be a non-empty string`);
3024
+ }
3025
+ }
3026
+ }
3027
+ }
3028
+ }
3029
+ const expectations = value["expectations"];
3030
+ if (expectations !== void 0 && !isRecord3(expectations)) {
3031
+ diagnostics.push("expectations must be an object");
3032
+ } else if (isRecord3(expectations)) {
3033
+ const allowedTypes = expectations["allowedDecisionTypes"];
3034
+ if (allowedTypes !== void 0 && (!isStringArray(allowedTypes) || allowedTypes.length === 0 || allowedTypes.some((entry) => !["answer", "ask_human", "deny"].includes(entry)))) {
3035
+ diagnostics.push("expectations.allowedDecisionTypes is invalid");
3036
+ }
3037
+ for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
3038
+ const entries = expectations[field];
3039
+ if (entries !== void 0 && !isStringArray(entries)) {
3040
+ diagnostics.push(`expectations.${field} must be a string array`);
3041
+ }
3042
+ }
3043
+ if (isRecord3(request) && Array.isArray(request["options"])) {
3044
+ const requestIds = new Set(
3045
+ request["options"].filter(isRecord3).map((option) => option["id"]).filter((id) => typeof id === "string")
3046
+ );
3047
+ for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
3048
+ const entries = expectations[field];
3049
+ if (!isStringArray(entries)) continue;
3050
+ for (const id of entries) {
3051
+ if (!requestIds.has(id))
3052
+ diagnostics.push(`expectations.${field} contains unknown id "${id}"`);
3053
+ }
3054
+ }
3055
+ }
3056
+ for (const field of ["requireOptionId", "allowEscalation"]) {
3057
+ const setting = expectations[field];
3058
+ if (setting !== void 0 && typeof setting !== "boolean") {
3059
+ diagnostics.push(`expectations.${field} must be a boolean`);
3060
+ }
3061
+ }
3062
+ }
3063
+ return diagnostics.length > 0 ? { ok: false, diagnostics } : { ok: true, value };
3064
+ }
3065
+ function assessDecision(fixture, decision) {
3066
+ const failures = [];
3067
+ const expectations = fixture.expectations;
3068
+ if (expectations?.allowedDecisionTypes && !expectations.allowedDecisionTypes.includes(decision.type)) {
3069
+ failures.push({
3070
+ code: "unexpected_decision_type",
3071
+ message: `decision type "${decision.type}" is not allowed`
3072
+ });
3073
+ }
3074
+ if (decision.type === "ask_human" && expectations?.allowEscalation !== true) {
3075
+ failures.push({
3076
+ code: "unexpected_escalation",
3077
+ message: "case escalated to a human without allowEscalation"
3078
+ });
3079
+ }
3080
+ if (decision.type !== "answer") return failures;
3081
+ const requestOptionIds = new Set(fixture.request.options?.map((option) => option.id) ?? []);
3082
+ if (decision.optionId === void 0) {
3083
+ if (expectations?.requireOptionId || expectations?.allowedOptionIds) {
3084
+ failures.push({
3085
+ code: "missing_option_id",
3086
+ message: "answer did not return the required exact option id"
3087
+ });
3088
+ }
3089
+ return failures;
3090
+ }
3091
+ if (!requestOptionIds.has(decision.optionId)) {
3092
+ failures.push({
3093
+ code: "invalid_option_id",
3094
+ message: `answer returned unknown option id "${decision.optionId}"`
3095
+ });
3096
+ }
3097
+ if (expectations?.allowedOptionIds && !expectations.allowedOptionIds.includes(decision.optionId)) {
3098
+ failures.push({
3099
+ code: "disallowed_option_id",
3100
+ message: `option id "${decision.optionId}" is not allowed for this case`
3101
+ });
3102
+ }
3103
+ if (expectations?.unsafeOptionIds?.includes(decision.optionId)) {
3104
+ failures.push({
3105
+ code: "unsafe_option_id",
3106
+ message: `unsafe option id "${decision.optionId}" was selected`
3107
+ });
3108
+ }
3109
+ return failures;
3110
+ }
3111
+ async function runBrainEvaluation(arbiter, cases) {
3112
+ const results = [];
3113
+ const seenCaseIds = /* @__PURE__ */ new Set();
3114
+ for (const rawCase of cases) {
3115
+ const validation = validateBrainEvaluationCase(rawCase);
3116
+ if (!validation.ok) {
3117
+ const caseId = isRecord3(rawCase) && typeof rawCase["id"] === "string" ? rawCase["id"] : "<invalid>";
3118
+ results.push({
3119
+ caseId,
3120
+ passed: false,
3121
+ failures: validation.diagnostics.map((message) => ({ code: "invalid_fixture", message })),
3122
+ latencyMs: 0
3123
+ });
3124
+ continue;
3125
+ }
3126
+ const fixture = validation.value;
3127
+ if (seenCaseIds.has(fixture.id)) {
3128
+ results.push({
3129
+ caseId: fixture.id,
3130
+ passed: false,
3131
+ failures: [{ code: "invalid_fixture", message: `case id "${fixture.id}" is duplicated` }],
3132
+ latencyMs: 0
3133
+ });
3134
+ continue;
3135
+ }
3136
+ seenCaseIds.add(fixture.id);
3137
+ const startedAt = performance.now();
3138
+ try {
3139
+ const decision = await arbiter.decide(structuredClone(fixture.request));
3140
+ const failures = assessDecision(fixture, decision);
3141
+ results.push({
3142
+ caseId: fixture.id,
3143
+ passed: failures.length === 0,
3144
+ decision,
3145
+ failures,
3146
+ latencyMs: Math.max(0, performance.now() - startedAt)
3147
+ });
3148
+ } catch (error) {
3149
+ results.push({
3150
+ caseId: fixture.id,
3151
+ passed: false,
3152
+ failures: [
3153
+ {
3154
+ code: "arbiter_error",
3155
+ message: error instanceof Error ? error.message : String(error)
3156
+ }
3157
+ ],
3158
+ latencyMs: Math.max(0, performance.now() - startedAt)
3159
+ });
3160
+ }
3161
+ }
3162
+ const optionResults = results.filter(
3163
+ (result) => result.decision?.type === "answer" && result.decision.optionId !== void 0
3164
+ );
3165
+ const invalidOptionCount = results.reduce(
3166
+ (count, result) => count + result.failures.filter((failure) => failure.code === "invalid_option_id").length,
3167
+ 0
3168
+ );
3169
+ const totalLatency = results.reduce((sum, result) => sum + result.latencyMs, 0);
3170
+ const metrics = {
3171
+ totalCases: results.length,
3172
+ passedCases: results.filter((result) => result.passed).length,
3173
+ failedCases: results.filter((result) => !result.passed).length,
3174
+ optionDecisionCount: optionResults.length,
3175
+ invalidOptionCount,
3176
+ validOptionRate: optionResults.length === 0 ? 1 : (optionResults.length - invalidOptionCount) / optionResults.length,
3177
+ unsafeAllowanceCount: results.reduce(
3178
+ (count, result) => count + result.failures.filter((failure) => failure.code === "unsafe_option_id").length,
3179
+ 0
3180
+ ),
3181
+ unnecessaryEscalationCount: results.reduce(
3182
+ (count, result) => count + result.failures.filter((failure) => failure.code === "unexpected_escalation").length,
3183
+ 0
3184
+ ),
3185
+ averageLatencyMs: results.length === 0 ? 0 : totalLatency / results.length
3186
+ };
3187
+ return { version: BRAIN_EVALUATION_CASE_VERSION, results, metrics };
3188
+ }
3189
+
3190
+ // src/coordination/brain.ts
3191
+ var DefaultBrainArbiter = class {
3192
+ allowLowRiskAutoAnswer;
3193
+ constructor(opts = {}) {
3194
+ this.allowLowRiskAutoAnswer = opts.allowLowRiskAutoAnswer ?? true;
3195
+ }
3196
+ async decide(request) {
3197
+ const recommended = request.options?.find((option) => option.recommended);
3198
+ if (this.allowLowRiskAutoAnswer && request.risk === "low" && recommended) {
3199
+ return {
3200
+ type: "answer",
3201
+ optionId: recommended.id,
3202
+ text: recommended.label,
3203
+ rationale: "Low-risk request with an explicit recommended option."
3204
+ };
3205
+ }
3206
+ switch (request.fallback) {
3207
+ case "deny":
3208
+ return {
3209
+ type: "deny",
3210
+ reason: `Brain could not safely decide: ${request.question}`
3211
+ };
3212
+ case "continue":
3213
+ return {
3214
+ type: "answer",
3215
+ text: "Continue with the caller default.",
3216
+ rationale: "No safe Brain decision was available; request fallback is continue."
3217
+ };
3218
+ case "ask_human":
3219
+ return {
3220
+ type: "ask_human",
3221
+ prompt: formatHumanPrompt(request),
3222
+ options: request.options,
3223
+ rationale: "Decision requires human authority or lacks a safe automatic option."
3224
+ };
3225
+ default:
3226
+ return {
3227
+ type: "deny",
3228
+ reason: `Brain could not safely decide (unknown fallback: ${String(request.fallback)}): ${request.question}`
3229
+ };
3230
+ }
3231
+ }
3232
+ };
3233
+ function formatHumanPrompt(request) {
3234
+ const lines = [
3235
+ `Brain requires human decision for ${request.source}:`,
3236
+ `Question: ${request.question}`
3237
+ ];
3238
+ if (request.context?.trim()) {
3239
+ lines.push("", "Context:", request.context.trim());
3240
+ }
3241
+ if (request.options?.length) {
3242
+ lines.push("", "Options:");
3243
+ for (const option of request.options) {
3244
+ const risk = option.risk ? ` [risk: ${option.risk}]` : "";
3245
+ const consequence = option.consequence ? ` \u2014 ${option.consequence}` : "";
3246
+ lines.push(`- ${option.id}: ${option.label}${risk}${consequence}`);
3247
+ }
3248
+ }
3249
+ lines.push("", `Risk: ${request.risk}`);
3250
+ return lines.join("\n");
3251
+ }
3252
+
3253
+ // src/coordination/brain-ledger.ts
3254
+ function createLedgerGuardBrainArbiter(opts) {
3255
+ const denyAfter = opts.denyAfter ?? 3;
3256
+ return {
3257
+ async decide(request) {
3258
+ if (denyAfter > 0) {
3259
+ const streak = opts.failureStreakFor(request);
3260
+ if (streak >= denyAfter) {
3261
+ return {
3262
+ type: "deny",
3263
+ reason: `Ledger guard: the last ${streak} approved decisions of this kind all ended in observed failures \u2014 denying deterministically until a success is recorded. (${request.question.slice(0, 120)})`
3264
+ };
3265
+ }
3266
+ }
3267
+ return opts.inner.decide(request);
3268
+ }
3269
+ };
3270
+ }
3271
+
3272
+ // src/execution/brain-runtime.ts
3273
+ var FAST_PATH_KEYS = /* @__PURE__ */ new Set(["mode", "maxAutoRisk", "humanTimeoutMs"]);
3274
+ var AUTO_RISK_LEVELS = /* @__PURE__ */ new Set(["off", "low", "medium", "high", "all"]);
3275
+ var COUNCIL_MIN_RISKS = /* @__PURE__ */ new Set(["medium", "high", "critical"]);
3276
+ function normalizeEntry(raw) {
3277
+ const parsed = typeof raw === "string" ? parseModelRef(raw) : raw;
3278
+ const model = parsed.model?.trim();
3279
+ if (!model) {
3280
+ throw new Error(
3281
+ `Invalid model ref: ${typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw)} (expected "model" or "provider/model")`
2408
3282
  );
2409
- const text = extractText(response).trim();
2410
- if (request.options?.length) {
2411
- const parsed = parseOptionDecision(text, request.options);
2412
- if (parsed) {
2413
- return parsed;
3283
+ }
3284
+ const provider = parsed.provider?.trim();
3285
+ return provider ? { provider, model } : { model };
3286
+ }
3287
+ function normalizeVoter(raw) {
3288
+ if (typeof raw === "string") return normalizeEntry(raw);
3289
+ const base = normalizeEntry(raw);
3290
+ const voter = { ...base };
3291
+ if (raw.persona !== void 0) voter.persona = raw.persona;
3292
+ if (raw.weight !== void 0) {
3293
+ if (!Number.isFinite(raw.weight) || raw.weight <= 0) {
3294
+ throw new Error(`Invalid voter weight: ${String(raw.weight)} (must be a positive number)`);
3295
+ }
3296
+ voter.weight = raw.weight;
3297
+ }
3298
+ if (raw.veto !== void 0) voter.veto = raw.veto;
3299
+ return voter;
3300
+ }
3301
+ function requireFraction(value, label) {
3302
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
3303
+ throw new Error(`Invalid ${label}: ${String(value)} (must be in (0, 1])`);
3304
+ }
3305
+ return value;
3306
+ }
3307
+ function requirePositiveMs(value, label) {
3308
+ if (!Number.isFinite(value) || value <= 0) {
3309
+ throw new Error(
3310
+ `Invalid ${label}: ${String(value)} (must be a positive number of milliseconds)`
3311
+ );
3312
+ }
3313
+ return Math.round(value);
3314
+ }
3315
+ function compactEntry(entry) {
3316
+ return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
3317
+ }
3318
+ function compactVoter(voter) {
3319
+ if (voter.persona === void 0 && voter.weight === void 0 && voter.veto === void 0) {
3320
+ return compactEntry(voter);
3321
+ }
3322
+ return { ...voter };
3323
+ }
3324
+ function resolveBrainConfigDefaults(brain, ctx = {}) {
3325
+ const cfg = { ...brain ?? {} };
3326
+ if (cfg.models === void 0 && ctx.fallbackModels && ctx.fallbackModels.length > 0) {
3327
+ cfg.models = [...ctx.fallbackModels];
3328
+ }
3329
+ const seatCount = cfg.council?.voters?.length ?? cfg.models?.length ?? 0;
3330
+ const councilLikely = cfg.council?.enabled ?? seatCount >= 2;
3331
+ if (cfg.mode === void 0) cfg.mode = "headless";
3332
+ if (cfg.maxAutoRisk === void 0) cfg.maxAutoRisk = councilLikely ? "all" : "high";
3333
+ if (cfg.humanTimeoutMs === void 0) cfg.humanTimeoutMs = 12e4;
3334
+ return cfg;
3335
+ }
3336
+ function normalizeInitial(config) {
3337
+ const cfg = { ...config ?? {} };
3338
+ const safe = (items, map) => (items ?? []).flatMap((item) => {
3339
+ try {
3340
+ return [map(item)];
3341
+ } catch {
3342
+ return [];
3343
+ }
3344
+ });
3345
+ if (cfg.models) cfg.models = safe(cfg.models, normalizeEntry);
3346
+ if (cfg.council) {
3347
+ const council = { ...cfg.council };
3348
+ if (council.voters) council.voters = safe(council.voters, normalizeVoter);
3349
+ if (council.judge) {
3350
+ try {
3351
+ council.judge = normalizeEntry(council.judge);
3352
+ } catch {
3353
+ council.judge = void 0;
3354
+ }
3355
+ }
3356
+ cfg.council = council;
3357
+ }
3358
+ return cfg;
3359
+ }
3360
+ function createBrainRuntime(opts) {
3361
+ let cfg = normalizeInitial(opts.initialConfig);
3362
+ let poolLabels = [];
3363
+ let councilLabels = [];
3364
+ let current;
3365
+ const getDecisionDigest = (request) => {
3366
+ const ledger = opts.ledger;
3367
+ if (!ledger?.isEnabled()) return void 0;
3368
+ return ledger.getDecisionDigest?.(request);
3369
+ };
3370
+ function rebuild() {
3371
+ const tiers = assembleBrainTiers({
3372
+ brainConfig: cfg,
3373
+ defaultProviderId: opts.defaultProviderId,
3374
+ sessionProvider: opts.sessionProvider,
3375
+ sessionModel: opts.sessionModel,
3376
+ resolveProvider: opts.resolveProvider,
3377
+ getDecisionDigest
3378
+ });
3379
+ poolLabels = tiers.poolLabels;
3380
+ councilLabels = tiers.councilLabels;
3381
+ const tiered = createTieredBrainArbiter({
3382
+ policy: new DefaultBrainArbiter(),
3383
+ autonomous: tiers.autonomous,
3384
+ getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
3385
+ council: tiers.council,
3386
+ getCouncilMinRisk: tiers.getCouncilMinRisk
3387
+ });
3388
+ const streakFor = opts.ledger?.failureStreakFor;
3389
+ current = streakFor && opts.ledger?.isEnabled() ? createLedgerGuardBrainArbiter({
3390
+ inner: tiered,
3391
+ failureStreakFor: streakFor,
3392
+ denyAfter: cfg.ledger?.autoDenyAfterFailures
3393
+ }) : tiered;
3394
+ }
3395
+ rebuild();
3396
+ function mergePatch(patch) {
3397
+ const next = { ...cfg, council: cfg.council ? { ...cfg.council } : void 0 };
3398
+ let rebuildNeeded = false;
3399
+ const structural = () => {
3400
+ rebuildNeeded = true;
3401
+ };
3402
+ if (patch.mode !== void 0) {
3403
+ if (patch.mode !== "headless" && patch.mode !== "interactive") {
3404
+ throw new Error(`Invalid mode: ${String(patch.mode)}`);
3405
+ }
3406
+ next.mode = patch.mode;
3407
+ }
3408
+ if (patch.maxAutoRisk !== void 0) {
3409
+ if (!AUTO_RISK_LEVELS.has(patch.maxAutoRisk)) {
3410
+ throw new Error(`Invalid maxAutoRisk: ${String(patch.maxAutoRisk)}`);
2414
3411
  }
2415
- return {
2416
- type: "deny",
2417
- reason: "Autonomy Brain returned no exact valid option id."
2418
- };
3412
+ next.maxAutoRisk = patch.maxAutoRisk;
2419
3413
  }
2420
- return {
2421
- type: "answer",
2422
- text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
2423
- rationale: text || void 0
2424
- };
2425
- } catch {
2426
- if (request.fallback === "continue") {
2427
- return {
2428
- type: "answer",
2429
- text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
2430
- };
3414
+ if (patch.humanTimeoutMs !== void 0) {
3415
+ next.humanTimeoutMs = patch.humanTimeoutMs === null ? void 0 : requirePositiveMs(patch.humanTimeoutMs, "humanTimeoutMs");
2431
3416
  }
2432
- return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
3417
+ if (patch.models !== void 0) {
3418
+ next.models = patch.models === null ? void 0 : patch.models.map(normalizeEntry);
3419
+ structural();
3420
+ }
3421
+ if (patch.strategy !== void 0) {
3422
+ if (patch.strategy !== null && patch.strategy !== "fallback" && patch.strategy !== "round-robin") {
3423
+ throw new Error(`Invalid strategy: ${String(patch.strategy)}`);
3424
+ }
3425
+ next.strategy = patch.strategy ?? void 0;
3426
+ structural();
3427
+ }
3428
+ if (patch.decisionTimeoutMs !== void 0) {
3429
+ next.decisionTimeoutMs = patch.decisionTimeoutMs === null ? void 0 : requirePositiveMs(patch.decisionTimeoutMs, "decisionTimeoutMs");
3430
+ structural();
3431
+ }
3432
+ if (patch.council !== void 0) {
3433
+ if (patch.council === null) {
3434
+ next.council = void 0;
3435
+ } else {
3436
+ const c = { ...next.council ?? {} };
3437
+ const p = patch.council;
3438
+ if (p.enabled !== void 0) c.enabled = p.enabled === null ? void 0 : p.enabled;
3439
+ if (p.minRisk !== void 0) {
3440
+ if (p.minRisk !== null && !COUNCIL_MIN_RISKS.has(p.minRisk)) {
3441
+ throw new Error(`Invalid council minRisk: ${String(p.minRisk)}`);
3442
+ }
3443
+ c.minRisk = p.minRisk ?? void 0;
3444
+ }
3445
+ if (p.voters !== void 0) {
3446
+ c.voters = p.voters === null ? void 0 : p.voters.map(normalizeVoter);
3447
+ }
3448
+ if (p.quorum !== void 0) {
3449
+ c.quorum = p.quorum === null ? void 0 : requireFraction(p.quorum, "council quorum");
3450
+ }
3451
+ if (p.approval !== void 0) {
3452
+ c.approval = p.approval === null ? void 0 : requireFraction(p.approval, "council approval");
3453
+ }
3454
+ if (p.judge !== void 0) {
3455
+ c.judge = p.judge === null ? void 0 : normalizeEntry(p.judge);
3456
+ }
3457
+ next.council = c;
3458
+ }
3459
+ structural();
3460
+ }
3461
+ if (patch.ledger !== void 0) {
3462
+ if (patch.ledger === null) {
3463
+ next.ledger = void 0;
3464
+ } else {
3465
+ const l = { ...next.ledger ?? {} };
3466
+ if (patch.ledger.enabled !== void 0) l.enabled = patch.ledger.enabled;
3467
+ if (patch.ledger.autoDenyAfterFailures !== void 0) {
3468
+ const n = patch.ledger.autoDenyAfterFailures;
3469
+ if (n !== null && (!Number.isInteger(n) || n < 0)) {
3470
+ throw new Error(
3471
+ `Invalid autoDenyAfterFailures: ${String(n)} (must be an integer >= 0)`
3472
+ );
3473
+ }
3474
+ l.autoDenyAfterFailures = n ?? void 0;
3475
+ }
3476
+ next.ledger = l;
3477
+ }
3478
+ structural();
3479
+ }
3480
+ for (const key of Object.keys(patch)) {
3481
+ if (!FAST_PATH_KEYS.has(key) && patch[key] !== void 0) {
3482
+ if (!["models", "strategy", "decisionTimeoutMs", "council", "ledger"].includes(key)) {
3483
+ throw new Error(`Unknown brain config field: ${key}`);
3484
+ }
3485
+ }
3486
+ }
3487
+ return { next, rebuildNeeded };
2433
3488
  }
2434
- }
2435
- function parseOptionDecision(rawText, options) {
2436
- const text = rawText.trim();
2437
- const byId = new Map(options.map((option2) => [option2.id, option2]));
2438
- const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
2439
- const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
2440
- const parsed = safeParse(jsonCandidate, 16384);
2441
- if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
2442
- const option2 = byId.get(parsed.value.optionId);
2443
- if (!option2) return null;
3489
+ function getSnapshot() {
3490
+ const councilCfg = cfg.council;
3491
+ const voters = (councilCfg?.voters ?? []).map((v) => normalizeVoter(v));
2444
3492
  return {
2445
- type: "answer",
2446
- optionId: option2.id,
2447
- text: option2.label,
2448
- rationale: typeof parsed.value.rationale === "string" && parsed.value.rationale.trim() ? parsed.value.rationale.trim() : void 0
3493
+ mode: cfg.mode ?? "interactive",
3494
+ maxAutoRisk: cfg.maxAutoRisk ?? "medium",
3495
+ models: (cfg.models ?? []).map((m) => normalizeEntry(m)),
3496
+ strategy: cfg.strategy ?? "fallback",
3497
+ decisionTimeoutMs: cfg.decisionTimeoutMs,
3498
+ humanTimeoutMs: cfg.humanTimeoutMs,
3499
+ council: {
3500
+ enabled: councilLabels.length > 0,
3501
+ configured: councilCfg?.enabled,
3502
+ minRisk: councilCfg?.minRisk ?? "high",
3503
+ voters,
3504
+ quorum: councilCfg?.quorum,
3505
+ approval: councilCfg?.approval,
3506
+ judge: councilCfg?.judge ? normalizeEntry(councilCfg.judge) : void 0
3507
+ },
3508
+ ledger: {
3509
+ enabled: opts.ledger?.isEnabled() ?? false,
3510
+ autoDenyAfterFailures: cfg.ledger?.autoDenyAfterFailures,
3511
+ path: opts.ledger?.getPath()
3512
+ },
3513
+ poolLabels: [...poolLabels],
3514
+ councilLabels: [...councilLabels],
3515
+ usingSessionModel: (cfg.models ?? []).length === 0
2449
3516
  };
2450
3517
  }
2451
- const legacy = /^\s*\[([^\]\r\n]+)\](?:\s*(?:—|-|:)\s*)?([\s\S]*)$/.exec(text);
2452
- if (!legacy) return null;
2453
- const option = byId.get((legacy[1] ?? "").trim());
2454
- if (!option) return null;
2455
- const rationale = (legacy[2] ?? "").trim();
3518
+ function getConfig() {
3519
+ const out = {};
3520
+ if (cfg.mode !== void 0) out.mode = cfg.mode;
3521
+ if (cfg.maxAutoRisk !== void 0) out.maxAutoRisk = cfg.maxAutoRisk;
3522
+ if (cfg.models?.length) out.models = cfg.models.map((m) => compactEntry(normalizeEntry(m)));
3523
+ if (cfg.strategy !== void 0) out.strategy = cfg.strategy;
3524
+ if (cfg.decisionTimeoutMs !== void 0) out.decisionTimeoutMs = cfg.decisionTimeoutMs;
3525
+ if (cfg.humanTimeoutMs !== void 0) out.humanTimeoutMs = cfg.humanTimeoutMs;
3526
+ if (cfg.council !== void 0) {
3527
+ const c = cfg.council;
3528
+ const outCouncil = {};
3529
+ if (c.enabled !== void 0) outCouncil.enabled = c.enabled;
3530
+ if (c.minRisk !== void 0) outCouncil.minRisk = c.minRisk;
3531
+ if (c.voters?.length)
3532
+ outCouncil.voters = c.voters.map((v) => compactVoter(normalizeVoter(v)));
3533
+ if (c.quorum !== void 0) outCouncil.quorum = c.quorum;
3534
+ if (c.approval !== void 0) outCouncil.approval = c.approval;
3535
+ if (c.judge !== void 0) outCouncil.judge = compactEntry(normalizeEntry(c.judge));
3536
+ out.council = outCouncil;
3537
+ }
3538
+ if (cfg.ledger !== void 0) out.ledger = { ...cfg.ledger };
3539
+ if (cfg.monitor !== void 0) out.monitor = { ...cfg.monitor };
3540
+ return out;
3541
+ }
2456
3542
  return {
2457
- type: "answer",
2458
- optionId: option.id,
2459
- text: option.label,
2460
- rationale: rationale || void 0
3543
+ arbiter: {
3544
+ decide: (request) => current.decide(request)
3545
+ },
3546
+ getMode: () => cfg.mode ?? "interactive",
3547
+ getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
3548
+ getHumanTimeoutMs: () => cfg.humanTimeoutMs,
3549
+ getSnapshot,
3550
+ getConfig,
3551
+ apply(patch, applyOpts) {
3552
+ const { next, rebuildNeeded } = mergePatch(patch);
3553
+ cfg = next;
3554
+ if (patch.ledger && patch.ledger.enabled !== void 0) {
3555
+ opts.ledger?.setEnabled(patch.ledger.enabled);
3556
+ }
3557
+ if (rebuildNeeded) rebuild();
3558
+ const snapshot = getSnapshot();
3559
+ const shouldPersist = applyOpts?.persist !== false && opts.persist !== void 0;
3560
+ const persisted = shouldPersist ? opts.persist(getConfig()).then(
3561
+ () => ({ ok: true }),
3562
+ (err) => ({
3563
+ ok: false,
3564
+ error: err instanceof Error ? err.message : String(err)
3565
+ })
3566
+ ) : Promise.resolve({ ok: true });
3567
+ opts.onApplied?.(snapshot);
3568
+ return { snapshot, persisted };
3569
+ }
2461
3570
  };
2462
3571
  }
2463
- function extractText(result) {
2464
- if (!result || typeof result !== "object") return "";
2465
- const r = result;
2466
- if (Array.isArray(r.content)) {
2467
- return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
2468
- }
2469
- if (Array.isArray(r.choices)) {
2470
- return r.choices[0]?.message?.content ?? "";
2471
- }
2472
- return typeof r.text === "string" ? r.text : "";
2473
- }
2474
3572
 
2475
3573
  // src/storage/goal-store.ts
2476
3574
  import * as fsp from "node:fs/promises";
@@ -2478,7 +3576,7 @@ var MAX_JOURNAL_ENTRIES = 500;
2478
3576
  function goalFilePath(projectRoot) {
2479
3577
  return resolveWstackPaths({ projectRoot }).projectGoal;
2480
3578
  }
2481
- async function loadGoal(filePath, events) {
3579
+ async function loadGoal(filePath, events, warn) {
2482
3580
  const t0 = Date.now();
2483
3581
  let raw;
2484
3582
  try {
@@ -2509,13 +3607,9 @@ async function loadGoal(filePath, events) {
2509
3607
  try {
2510
3608
  const parsed = JSON.parse(raw);
2511
3609
  if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
2512
- console.warn(JSON.stringify({
2513
- level: "warn",
2514
- event: "goal_store.invalid_schema",
2515
- path: filePath,
2516
- message: "invalid schema \u2014 consider deleting and re-creating",
2517
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2518
- }));
3610
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
3611
+ "invalid schema \u2014 consider deleting and re-creating"
3612
+ );
2519
3613
  events?.emit("storage.read", {
2520
3614
  sessionId: "~boot~",
2521
3615
  store: "goal",
@@ -2537,13 +3631,9 @@ async function loadGoal(filePath, events) {
2537
3631
  });
2538
3632
  return parsed;
2539
3633
  } catch {
2540
- console.warn(JSON.stringify({
2541
- level: "warn",
2542
- event: "goal_store.parse_failed",
2543
- path: filePath,
2544
- message: "JSON parse failed \u2014 consider deleting and re-creating",
2545
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2546
- }));
3634
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
3635
+ "JSON parse failed \u2014 consider deleting and re-creating"
3636
+ );
2547
3637
  events?.emit("storage.read", {
2548
3638
  sessionId: "~boot~",
2549
3639
  store: "goal",
@@ -3966,124 +5056,6 @@ function installDesignStudioMiddleware(deps) {
3966
5056
  });
3967
5057
  }
3968
5058
 
3969
- // src/types/provider.ts
3970
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
3971
- var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
3972
- function classifyProviderError(status, body, message) {
3973
- const type = body?.type;
3974
- if (status === 0) return "network";
3975
- if (status === 408) return "timeout";
3976
- if (status === 599) return "stream_hang";
3977
- if (type === "rate_limit_error" || status === 429) return "rate_limit";
3978
- if (type === "overloaded_error" || status === 529) return "overloaded";
3979
- if (status >= 500) return "server";
3980
- if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
3981
- return "auth";
3982
- }
3983
- const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
3984
- if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
3985
- if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
3986
- return "context_overflow";
3987
- }
3988
- if (status >= 400) return "invalid_request";
3989
- return "unknown";
3990
- }
3991
- function isRetryableKind(kind) {
3992
- return RETRYABLE_BY_KIND[kind];
3993
- }
3994
- var RETRYABLE_BY_KIND = {
3995
- rate_limit: true,
3996
- overloaded: true,
3997
- server: true,
3998
- timeout: true,
3999
- network: true,
4000
- stream_hang: true,
4001
- auth: false,
4002
- context_overflow: false,
4003
- content_filter: false,
4004
- invalid_request: false,
4005
- unknown: false
4006
- };
4007
- var ProviderError = class extends WrongStackError {
4008
- status;
4009
- retryable;
4010
- providerId;
4011
- /** Canonical failure classification — see {@link ProviderErrorKind}. */
4012
- kind;
4013
- body;
4014
- constructor(message, status, retryable, providerId, opts = {}) {
4015
- const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
4016
- super({
4017
- message,
4018
- code: kindToCode(kind),
4019
- subsystem: "provider",
4020
- severity: status >= 500 ? "error" : "warning",
4021
- recoverable: retryable,
4022
- context: { providerId, status },
4023
- cause: opts.cause
4024
- });
4025
- this.name = "ProviderError";
4026
- this.status = status;
4027
- this.retryable = retryable;
4028
- this.providerId = providerId;
4029
- this.kind = kind;
4030
- this.body = opts.body;
4031
- }
4032
- /**
4033
- * Render a one-line, user-facing description. Designed for the CLI/TUI
4034
- * status line and the agent's retry warning. Avoids dumping raw JSON
4035
- * (which is what users see today when a 529 lands and the log message
4036
- * includes the full `{"type":"error",...}` body).
4037
- *
4038
- * Examples:
4039
- * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
4040
- * "openai rate limited (429): Retry after 12s"
4041
- * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
4042
- * "groq HTTP 500 (server error)"
4043
- */
4044
- describe() {
4045
- const kind = describeStatus(this.status, this.body?.type);
4046
- const head = `${this.providerId} ${kind}`;
4047
- const detail = this.body?.message?.trim();
4048
- const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
4049
- if (detail && detail.length > 0) {
4050
- return `${head}: ${truncate(detail, 240)}${reqId}`;
4051
- }
4052
- return `${head}${reqId}`;
4053
- }
4054
- };
4055
- function describeStatus(status, type) {
4056
- if (status === 0) return "network error";
4057
- if (status === 599) return `stream hang (${status})`;
4058
- if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
4059
- if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
4060
- if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
4061
- if (type === "permission_error" || status === 403) return `forbidden (${status})`;
4062
- if (type === "not_found_error" || status === 404) return `not found (${status})`;
4063
- if (type === "content_filter") return `content filtered (${status})`;
4064
- if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
4065
- if (status === 408) return `timeout (${status})`;
4066
- if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
4067
- if (type) return `${type} (${status})`;
4068
- return `HTTP ${status}`;
4069
- }
4070
- var KIND_TO_CODE = {
4071
- network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
4072
- timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
4073
- rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
4074
- auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
4075
- overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
4076
- context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
4077
- server: ERROR_CODES.PROVIDER_SERVER_ERROR,
4078
- stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
4079
- content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
4080
- invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
4081
- unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
4082
- };
4083
- function kindToCode(kind) {
4084
- return KIND_TO_CODE[kind];
4085
- }
4086
-
4087
5059
  // src/execution/regex-patterns.ts
4088
5060
  var NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
4089
5061
 
@@ -4271,22 +5243,40 @@ var EternalAutonomyEngine = class {
4271
5243
  currentCtrl = null;
4272
5244
  iterationsSinceCompact = 0;
4273
5245
  goalPath;
5246
+ /**
5247
+ * Emit a structured error. Uses the configured Logger when available;
5248
+ * falls back to console.error(JSON) so events are never silently dropped.
5249
+ */
5250
+ logError(msg, ctx) {
5251
+ if (this.opts.logger) {
5252
+ this.opts.logger.error(msg, ctx);
5253
+ } else {
5254
+ console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
5255
+ }
5256
+ }
4274
5257
  /** Current engine state — readable for UIs. */
4275
5258
  get currentState() {
4276
5259
  return this.state;
4277
5260
  }
5261
+ /**
5262
+ * Load the goal file with structured logging for parse/schema warnings.
5263
+ */
5264
+ loadGoal() {
5265
+ return loadGoal(
5266
+ this.goalPath,
5267
+ this.opts.events,
5268
+ this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
5269
+ );
5270
+ }
4278
5271
  /** Synchronously request stop. Resolves once the running iteration aborts. */
4279
5272
  stop() {
4280
5273
  this.stopRequested = true;
4281
5274
  this.currentCtrl?.abort();
4282
5275
  void this.persistEngineState("stopped").catch((err) => {
4283
- console.error(JSON.stringify({
4284
- level: "error",
4285
- event: "engine.persist_state_failed",
4286
- message: toErrorMessage(err),
4287
- context: { expectedState: "stopped" },
4288
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4289
- }));
5276
+ this.logError(
5277
+ "Engine persist state failed",
5278
+ { event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
5279
+ );
4290
5280
  });
4291
5281
  this.state = "stopped";
4292
5282
  }
@@ -4342,7 +5332,7 @@ var EternalAutonomyEngine = class {
4342
5332
  const emit = (stage) => {
4343
5333
  this.opts.onStage?.(stage);
4344
5334
  };
4345
- const goal = await loadGoal(this.goalPath, this.opts.events);
5335
+ const goal = await this.loadGoal();
4346
5336
  if (!goal) {
4347
5337
  emit({ phase: "stopped" });
4348
5338
  this.stopRequested = true;
@@ -4446,16 +5436,13 @@ var EternalAutonomyEngine = class {
4446
5436
  emit({ phase: "reflect", status, note });
4447
5437
  let iterationIndex = 0;
4448
5438
  try {
4449
- const reloaded = await loadGoal(this.goalPath, this.opts.events);
5439
+ const reloaded = await this.loadGoal();
4450
5440
  iterationIndex = reloaded?.iterations ?? 0;
4451
5441
  } catch (err) {
4452
- console.error(JSON.stringify({
4453
- level: "warn",
4454
- event: "autonomy.goal_reload_failed",
4455
- message: toErrorMessage(err),
4456
- context: { goalPath: this.goalPath },
4457
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4458
- }));
5442
+ this.logError(
5443
+ "Goal reload failed",
5444
+ { event: "autonomy.goal_reload_failed", message: toErrorMessage(err), context: { goalPath: this.goalPath } }
5445
+ );
4459
5446
  }
4460
5447
  this.opts.onIteration?.({
4461
5448
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -4691,13 +5678,10 @@ ${lastFew}` : "No prior iterations yet.",
4691
5678
  clearTimeout(timer);
4692
5679
  }
4693
5680
  } catch (err) {
4694
- console.error(JSON.stringify({
4695
- level: "warn",
4696
- event: "autonomy.brainstorm_failed",
4697
- message: toErrorMessage(err),
4698
- context: { goal: goal.goal.slice(0, 100) },
4699
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4700
- }));
5681
+ this.logError(
5682
+ "Brainstorm failed",
5683
+ { event: "autonomy.brainstorm_failed", message: toErrorMessage(err), context: { goal: goal.goal.slice(0, 100) } }
5684
+ );
4701
5685
  return null;
4702
5686
  }
4703
5687
  }
@@ -4780,7 +5764,7 @@ ${recentJournal}` : "No prior iterations.",
4780
5764
  }
4781
5765
  }
4782
5766
  async appendIterationEntry(entry) {
4783
- const current = await loadGoal(this.goalPath, this.opts.events);
5767
+ const current = await this.loadGoal();
4784
5768
  if (!current) {
4785
5769
  return;
4786
5770
  }
@@ -4794,7 +5778,7 @@ ${recentJournal}` : "No prior iterations.",
4794
5778
  * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
4795
5779
  */
4796
5780
  async bumpTodoAttempt(todoId) {
4797
- const current = await loadGoal(this.goalPath, this.opts.events);
5781
+ const current = await this.loadGoal();
4798
5782
  if (!current) return;
4799
5783
  const attempts = { ...current.todoAttempts ?? {} };
4800
5784
  attempts[todoId] = (attempts[todoId] ?? 0) + 1;
@@ -4808,7 +5792,7 @@ ${recentJournal}` : "No prior iterations.",
4808
5792
  * goal is already `completed`.
4809
5793
  */
4810
5794
  async markGoalCompleted(action, note) {
4811
- const current = await loadGoal(this.goalPath, this.opts.events);
5795
+ const current = await this.loadGoal();
4812
5796
  if (!current) return;
4813
5797
  if (current.goalState === "completed") return;
4814
5798
  const withFlag = { ...current, goalState: "completed" };
@@ -4819,6 +5803,11 @@ ${recentJournal}` : "No prior iterations.",
4819
5803
  note: note.slice(0, 240)
4820
5804
  });
4821
5805
  await saveGoal(this.goalPath, withEntry, this.opts.events);
5806
+ try {
5807
+ const { unlink: unlink4 } = await import("node:fs/promises");
5808
+ await unlink4(this.goalPath);
5809
+ } catch {
5810
+ }
4822
5811
  this.opts.onEternalStop?.();
4823
5812
  }
4824
5813
  /**
@@ -4827,7 +5816,7 @@ ${recentJournal}` : "No prior iterations.",
4827
5816
  * `onEternalStop` so the REPL returns to normal mode.
4828
5817
  */
4829
5818
  async clearGoalManually(note) {
4830
- const current = await loadGoal(this.goalPath, this.opts.events);
5819
+ const current = await this.loadGoal();
4831
5820
  if (current) {
4832
5821
  const abandoned = { ...current, goalState: "abandoned" };
4833
5822
  await saveGoal(this.goalPath, abandoned, this.opts.events);
@@ -4875,7 +5864,23 @@ Recent work:
4875
5864
  ${recentJournal}` : ""
4876
5865
  ].join("\n"),
4877
5866
  risk: "high",
4878
- fallback: "continue"
5867
+ fallback: "continue",
5868
+ // Structured verdict — completion is control-plane input, decided by
5869
+ // exact option id rather than sniffing prose for "done"/"complete".
5870
+ options: [
5871
+ {
5872
+ id: "goal_complete",
5873
+ label: "The goal is complete \u2014 stop the eternal run",
5874
+ consequence: "The engine stops and the goal file is archived.",
5875
+ risk: "high"
5876
+ },
5877
+ {
5878
+ id: "keep_working",
5879
+ label: "Not complete yet \u2014 keep working",
5880
+ consequence: "The engine continues iterating on the goal.",
5881
+ risk: "low"
5882
+ }
5883
+ ]
4879
5884
  });
4880
5885
  const summary = formatDecisionSummary(decision, {
4881
5886
  id: `goal-done-${goal.iterations}`,
@@ -4895,8 +5900,7 @@ ${recentJournal}` : ""
4895
5900
  return false;
4896
5901
  }
4897
5902
  if (decision.type === "answer") {
4898
- const text = decision.text.toLowerCase();
4899
- return text.includes("complete") || text.includes("done") || text.includes("yes");
5903
+ return decision.optionId === "goal_complete";
4900
5904
  }
4901
5905
  return true;
4902
5906
  } catch {
@@ -4907,13 +5911,13 @@ ${recentJournal}` : ""
4907
5911
  * Persist a progress update from the agent's [PROGRESS: N%] output.
4908
5912
  */
4909
5913
  async updateProgress(progress, note) {
4910
- const current = await loadGoal(this.goalPath, this.opts.events);
5914
+ const current = await this.loadGoal();
4911
5915
  if (!current) return;
4912
5916
  const updated = recordProgress(current, progress, note);
4913
5917
  await saveGoal(this.goalPath, updated, this.opts.events);
4914
5918
  }
4915
5919
  async persistEngineState(state) {
4916
- const current = await loadGoal(this.goalPath, this.opts.events);
5920
+ const current = await this.loadGoal();
4917
5921
  if (!current) return;
4918
5922
  if (current.engineState === state) return;
4919
5923
  await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
@@ -5751,15 +6755,17 @@ var MAX_FILES = 64;
5751
6755
  var MAX_NEXT_STEPS = 12;
5752
6756
  var MAX_ITEM_CHARS = 500;
5753
6757
  function normalizeSubagentStructuredReport(value) {
5754
- if (!isRecord3(value)) return void 0;
6758
+ if (!isRecord4(value)) return void 0;
5755
6759
  const summary = nonEmptyString(value["summary"]);
5756
6760
  const findings = stringArray(value["findings"]);
5757
6761
  const filesExamined = stringArray(value["files_examined"]);
5758
6762
  const nextSteps = stringArray(value["suggested_next_steps"]);
5759
6763
  const confidence = value["confidence"];
6764
+ const completion = value["completion"];
6765
+ const remainingWork = optionalString(value["remaining_work"]);
5760
6766
  if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
5761
6767
  (item) => item.trim().length > MAX_ITEM_CHARS
5762
- ) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
6768
+ ) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || completion !== void 0 && completion !== "complete" && completion !== "partial" || completion === "partial" && !remainingWork || remainingWork !== void 0 && remainingWork.length > MAX_SUMMARY_CHARS) {
5763
6769
  return void 0;
5764
6770
  }
5765
6771
  const report = {
@@ -5767,19 +6773,24 @@ function normalizeSubagentStructuredReport(value) {
5767
6773
  findings: findings.map((item) => item.trim()).filter(Boolean),
5768
6774
  files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
5769
6775
  confidence,
5770
- suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
6776
+ suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean),
6777
+ ...completion === "complete" || completion === "partial" ? { completion } : {},
6778
+ ...remainingWork ? { remaining_work: remainingWork } : {}
5771
6779
  };
5772
6780
  return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
5773
6781
  }
5774
6782
  function readSubagentStructuredReport(ctx) {
5775
6783
  return normalizeSubagentStructuredReport(ctx?.meta?.[SUBAGENT_STRUCTURED_REPORT_META_KEY]);
5776
6784
  }
5777
- function isRecord3(value) {
6785
+ function isRecord4(value) {
5778
6786
  return typeof value === "object" && value !== null && !Array.isArray(value);
5779
6787
  }
5780
6788
  function nonEmptyString(value) {
5781
6789
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
5782
6790
  }
6791
+ function optionalString(value) {
6792
+ return value === void 0 ? void 0 : nonEmptyString(value);
6793
+ }
5783
6794
  function stringArray(value) {
5784
6795
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
5785
6796
  }
@@ -7769,58 +8780,27 @@ function providerErrorToSubagentError(err, message, cause) {
7769
8780
  }
7770
8781
 
7771
8782
  // src/coordination/fleet.ts
7772
- var AUDIT_LOG_AGENT = {
7773
- id: "audit-log",
7774
- name: "Audit Log",
7775
- role: "audit-log",
7776
- prompt: agentPrompt("audit-log")
7777
- // No hardcoded budgets — the orchestrator (delegate tool or
7778
- // spawn_subagent) decides per-task how much room a subagent gets.
7779
- // A monorepo audit needs hours; a single-file lint check needs
7780
- // seconds. Pinning a number here forces the orchestrator to fight
7781
- // the role's default instead of just asking for what it needs.
7782
- };
7783
- var BUG_HUNTER_AGENT = {
7784
- id: "bug-hunter",
7785
- name: "Bug Hunter",
7786
- role: "bug-hunter",
7787
- prompt: agentPrompt("bug-hunter")
7788
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7789
- };
7790
- var REFACTOR_PLANNER_AGENT = {
7791
- id: "refactor-planner",
7792
- name: "Refactor Planner",
7793
- role: "refactor-planner",
7794
- prompt: agentPrompt("refactor-planner")
7795
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7796
- };
7797
- var SECURITY_SCANNER_AGENT = {
7798
- id: "security-scanner",
7799
- name: "Security Scanner",
7800
- role: "security-scanner",
7801
- prompt: agentPrompt("security-scanner")
7802
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7803
- };
7804
- var SHADOW_AGENT = {
7805
- id: "shadow-agent",
7806
- name: "Shadow",
7807
- role: "shadow-agent",
7808
- prompt: agentPrompt("shadow-agent")
7809
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7810
- };
7811
- var CRITIC_AGENT = {
7812
- id: "critic",
7813
- name: "Critic",
7814
- role: "critic",
7815
- prompt: agentPrompt("critic")
7816
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7817
- };
8783
+ function defineAgent(id, name, promptRole = id, provider) {
8784
+ return {
8785
+ id,
8786
+ name,
8787
+ role: id,
8788
+ prompt: agentPrompt(promptRole),
8789
+ ...provider === void 0 ? {} : { provider }
8790
+ };
8791
+ }
8792
+ var AUDIT_LOG_AGENT = defineAgent("audit-log", "Audit Log");
8793
+ var BUG_HUNTER_AGENT = defineAgent("bug-hunter", "Bug Hunter");
8794
+ var REFACTOR_PLANNER_AGENT = defineAgent("refactor-planner", "Refactor Planner");
8795
+ var SECURITY_SCANNER_AGENT = defineAgent("security-scanner", "Security Scanner");
8796
+ var SHADOW_AGENT = defineAgent("shadow-agent", "Shadow");
8797
+ var CRITIC_AGENT = defineAgent("critic", "Critic");
7818
8798
  var FLEET_ROSTER = {
7819
8799
  "audit-log": AUDIT_LOG_AGENT,
7820
8800
  "bug-hunter": BUG_HUNTER_AGENT,
7821
8801
  "refactor-planner": REFACTOR_PLANNER_AGENT,
7822
8802
  "security-scanner": SECURITY_SCANNER_AGENT,
7823
- "critic": CRITIC_AGENT,
8803
+ critic: CRITIC_AGENT,
7824
8804
  "shadow-agent": SHADOW_AGENT,
7825
8805
  ...Object.fromEntries(
7826
8806
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
@@ -7832,7 +8812,7 @@ var FLEET_ROSTER_BUDGETS = {
7832
8812
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
7833
8813
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
7834
8814
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
7835
- "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
8815
+ critic: { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
7836
8816
  "shadow-agent": {
7837
8817
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
7838
8818
  maxIterations: 2e3,
@@ -7869,41 +8849,11 @@ function applyRosterBudget(cfg) {
7869
8849
  };
7870
8850
  }
7871
8851
  var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
7872
- var CLINE_AGENT = {
7873
- id: "cline",
7874
- name: "Cline",
7875
- role: "cline",
7876
- prompt: agentPrompt("acp-cline"),
7877
- provider: "acp"
7878
- };
7879
- var GEMINI_CLI_AGENT = {
7880
- id: "gemini-cli",
7881
- name: "Gemini CLI",
7882
- role: "gemini-cli",
7883
- prompt: agentPrompt("acp-gemini-cli"),
7884
- provider: "acp"
7885
- };
7886
- var COPILOT_AGENT = {
7887
- id: "copilot",
7888
- name: "GitHub Copilot",
7889
- role: "copilot",
7890
- prompt: agentPrompt("acp-copilot"),
7891
- provider: "acp"
7892
- };
7893
- var OPENHANDS_AGENT = {
7894
- id: "openhands",
7895
- name: "OpenHands",
7896
- role: "openhands",
7897
- prompt: agentPrompt("acp-openhands"),
7898
- provider: "acp"
7899
- };
7900
- var GOOSE_AGENT = {
7901
- id: "goose",
7902
- name: "Goose",
7903
- role: "goose",
7904
- prompt: agentPrompt("acp-goose"),
7905
- provider: "acp"
7906
- };
8852
+ var CLINE_AGENT = defineAgent("cline", "Cline", "acp-cline", "acp");
8853
+ var GEMINI_CLI_AGENT = defineAgent("gemini-cli", "Gemini CLI", "acp-gemini-cli", "acp");
8854
+ var COPILOT_AGENT = defineAgent("copilot", "GitHub Copilot", "acp-copilot", "acp");
8855
+ var OPENHANDS_AGENT = defineAgent("openhands", "OpenHands", "acp-openhands", "acp");
8856
+ var GOOSE_AGENT = defineAgent("goose", "Goose", "acp-goose", "acp");
7907
8857
  var ACP_AGENTS = [
7908
8858
  CLINE_AGENT,
7909
8859
  GEMINI_CLI_AGENT,
@@ -7911,11 +8861,14 @@ var ACP_AGENTS = [
7911
8861
  OPENHANDS_AGENT,
7912
8862
  GOOSE_AGENT
7913
8863
  ];
7914
- FLEET_ROSTER_BUDGETS["cline"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7915
- FLEET_ROSTER_BUDGETS["gemini-cli"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7916
- FLEET_ROSTER_BUDGETS["copilot"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7917
- FLEET_ROSTER_BUDGETS["openhands"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7918
- FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
8864
+ var ACP_AGENT_BUDGET = {
8865
+ timeoutMs: 10 * 60 * 60 * 1e3,
8866
+ maxIterations: 8e3,
8867
+ maxToolCalls: 2e4
8868
+ };
8869
+ for (const agent of ACP_AGENTS) {
8870
+ FLEET_ROSTER_BUDGETS[agent.role] = { ...ACP_AGENT_BUDGET };
8871
+ }
7919
8872
  var FLEET_ROSTER_WITHACP = {
7920
8873
  ...FLEET_ROSTER,
7921
8874
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
@@ -8909,6 +9862,27 @@ var ParallelEternalEngine = class {
8909
9862
  agentFactory;
8910
9863
  dispatchEnabled;
8911
9864
  dispatchClassifier;
9865
+ /**
9866
+ * Emit a structured error. Uses the configured Logger when available;
9867
+ * falls back to console.error(JSON) so events are never silently dropped.
9868
+ */
9869
+ logError(msg, ctx) {
9870
+ if (this.opts.logger) {
9871
+ this.opts.logger.error(msg, ctx);
9872
+ } else {
9873
+ console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
9874
+ }
9875
+ }
9876
+ /**
9877
+ * Load the goal file with structured logging for parse/schema warnings.
9878
+ */
9879
+ loadGoal() {
9880
+ return loadGoal(
9881
+ this.goalPath,
9882
+ this.opts.events,
9883
+ this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
9884
+ );
9885
+ }
8912
9886
  get currentState() {
8913
9887
  return this.state;
8914
9888
  }
@@ -8921,13 +9895,10 @@ var ParallelEternalEngine = class {
8921
9895
  stop() {
8922
9896
  this.stopRequested = true;
8923
9897
  void this.persistState("stopped").catch((err) => {
8924
- console.error(JSON.stringify({
8925
- level: "error",
8926
- event: "engine.persist_state_failed",
8927
- message: toErrorMessage(err),
8928
- context: { expectedState: "stopped" },
8929
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8930
- }));
9898
+ this.logError(
9899
+ "Engine persist state failed",
9900
+ { event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
9901
+ );
8931
9902
  });
8932
9903
  this.state = "stopped";
8933
9904
  }
@@ -8981,7 +9952,7 @@ var ParallelEternalEngine = class {
8981
9952
  this.opts.onStage?.(stage);
8982
9953
  };
8983
9954
  this.iterations++;
8984
- const goal = await loadGoal(this.goalPath, this.opts.events);
9955
+ const goal = await this.loadGoal();
8985
9956
  if (!goal) {
8986
9957
  this.stopRequested = true;
8987
9958
  emit({ phase: "stopped" });
@@ -9125,13 +10096,10 @@ ${personaLine}Task: ${task}
9125
10096
  taskIds.push(taskId);
9126
10097
  await coordinator.assign(spec);
9127
10098
  } catch (err) {
9128
- console.error(JSON.stringify({
9129
- level: "warn",
9130
- event: "parallel_engine.spawn_failed",
9131
- message: toErrorMessage(err),
9132
- context: { slot: i, task, subagentId },
9133
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9134
- }));
10099
+ this.logError(
10100
+ "Parallel spawn failed",
10101
+ { event: "parallel_engine.spawn_failed", message: toErrorMessage(err), context: { slot: i, task, subagentId } }
10102
+ );
9135
10103
  }
9136
10104
  })()
9137
10105
  );
@@ -9157,13 +10125,10 @@ ${personaLine}Task: ${task}
9157
10125
  clearTimeout(timer);
9158
10126
  }
9159
10127
  } catch (err) {
9160
- console.error(JSON.stringify({
9161
- level: "warn",
9162
- event: "parallel_engine.brainstorm_results_failed",
9163
- message: toErrorMessage(err),
9164
- context: { slotCount, taskIds },
9165
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9166
- }));
10128
+ this.logError(
10129
+ "Brainstorm results failed",
10130
+ { event: "parallel_engine.brainstorm_results_failed", message: toErrorMessage(err), context: { slotCount, taskIds } }
10131
+ );
9167
10132
  results = coordinator.results().slice(-taskIds.length);
9168
10133
  }
9169
10134
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -9198,13 +10163,10 @@ ${personaLine}Task: ${task}
9198
10163
  }
9199
10164
  }
9200
10165
  } catch (err) {
9201
- console.error(JSON.stringify({
9202
- level: "warn",
9203
- event: "parallel_engine.git_status_failed",
9204
- message: toErrorMessage(err),
9205
- context: { projectRoot: this.opts.projectRoot },
9206
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9207
- }));
10166
+ this.logError(
10167
+ "Git status failed",
10168
+ { event: "parallel_engine.git_status_failed", message: toErrorMessage(err), context: { projectRoot: this.opts.projectRoot } }
10169
+ );
9208
10170
  }
9209
10171
  }
9210
10172
  if (tasks.length < this.slots) {
@@ -9285,7 +10247,7 @@ ${lastFew}` : "No prior iterations.",
9285
10247
  // Helpers
9286
10248
  // -------------------------------------------------------------------------
9287
10249
  async appendIterationEntry(entry) {
9288
- const current = await loadGoal(this.goalPath, this.opts.events);
10250
+ const current = await this.loadGoal();
9289
10251
  if (!current) return;
9290
10252
  const updated = appendJournal(current, entry);
9291
10253
  await saveGoal(this.goalPath, updated, this.opts.events);
@@ -9300,7 +10262,7 @@ ${lastFew}` : "No prior iterations.",
9300
10262
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
9301
10263
  }
9302
10264
  async persistState(state) {
9303
- const current = await loadGoal(this.goalPath, this.opts.events);
10265
+ const current = await this.loadGoal();
9304
10266
  if (!current) return;
9305
10267
  if (current.engineState === state) return;
9306
10268
  await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
@@ -11121,12 +12083,12 @@ ${post.additionalContext}`;
11121
12083
  if (parentSignal.reason instanceof Error) throw parentSignal.reason;
11122
12084
  throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
11123
12085
  }
11124
- const timeoutMs = clampTimeoutMs(
11125
- tool.timeoutMs ?? this.iterationTimeoutMs,
11126
- this.maxToolTimeoutMs
11127
- );
11128
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
11129
- const combined = AbortSignal.any([parentSignal, timeoutSignal]);
12086
+ const combined = tool.managesOwnTimeout ? parentSignal : AbortSignal.any([
12087
+ parentSignal,
12088
+ AbortSignal.timeout(
12089
+ clampTimeoutMs(tool.timeoutMs ?? this.iterationTimeoutMs, this.maxToolTimeoutMs)
12090
+ )
12091
+ ]);
11130
12092
  let output;
11131
12093
  const toolPromise = typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
11132
12094
  toolPromise.catch(() => {
@@ -11472,6 +12434,8 @@ function sliceUtf8Suffix(text, maxBytes) {
11472
12434
  export {
11473
12435
  AutoCompactionMiddleware,
11474
12436
  AutonomousRunner,
12437
+ BRAIN_EVALUATION_CASE_VERSION,
12438
+ COUNCIL_REFUSE_OPTION_ID,
11475
12439
  DEFAULT_RECOVERY_STRATEGIES,
11476
12440
  DefaultDesignKitLoader,
11477
12441
  DefaultErrorHandler,
@@ -11489,11 +12453,16 @@ export {
11489
12453
  _resetDesignRulesCache,
11490
12454
  activateDesign,
11491
12455
  applyModelRuntime,
12456
+ assembleBrainTiers,
12457
+ buildBrainUserMessage,
11492
12458
  buildGoalPreamble,
11493
12459
  buildRecoveryStrategies,
11494
12460
  clearActiveKit,
11495
12461
  clearPersistedActiveKit,
12462
+ completeBrainLlm,
11496
12463
  createAutonomyBrain,
12464
+ createBrainRuntime,
12465
+ createCouncilBrainArbiter,
11497
12466
  createStrategyCompactor,
11498
12467
  createTieredBrainArbiter,
11499
12468
  designProjectDir,
@@ -11510,12 +12479,16 @@ export {
11510
12479
  makeDesignDetectUserInputMiddleware,
11511
12480
  makeDesignStudioRequestMiddleware,
11512
12481
  mergeModelRuntime,
12482
+ parseOptionDecision,
11513
12483
  recordKitChoice,
11514
12484
  renderPrompt,
12485
+ resolveBrainConfigDefaults,
11515
12486
  resolveBundledDesignKitsDir,
11516
12487
  resolveCacheForRequest,
11517
12488
  resolveModelRuntime,
11518
12489
  resolveReasoningForRequest,
11519
- setActiveKit
12490
+ runBrainEvaluation,
12491
+ setActiveKit,
12492
+ validateBrainEvaluationCase
11520
12493
  };
11521
12494
  //# sourceMappingURL=index.js.map