zelari-code 1.48.1 → 1.49.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 (55) hide show
  1. package/dist/cli/desktopConfig.js +51 -4
  2. package/dist/cli/desktopConfig.js.map +1 -1
  3. package/dist/cli/hooks/useChatTurn.js +80 -0
  4. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  5. package/dist/cli/kraken/candidateRegistry.js +195 -0
  6. package/dist/cli/kraken/candidateRegistry.js.map +1 -0
  7. package/dist/cli/kraken/candidateRegistry.test.js +211 -0
  8. package/dist/cli/kraken/candidateRegistry.test.js.map +1 -0
  9. package/dist/cli/kraken/completionGate.js +116 -0
  10. package/dist/cli/kraken/completionGate.js.map +1 -0
  11. package/dist/cli/kraken/completionGate.test.js +172 -0
  12. package/dist/cli/kraken/completionGate.test.js.map +1 -0
  13. package/dist/cli/kraken/metrics.js +96 -0
  14. package/dist/cli/kraken/metrics.js.map +1 -0
  15. package/dist/cli/kraken/metrics.test.js +145 -0
  16. package/dist/cli/kraken/metrics.test.js.map +1 -0
  17. package/dist/cli/kraken/selectionPlaybook.js +23 -0
  18. package/dist/cli/kraken/selectionPlaybook.js.map +1 -0
  19. package/dist/cli/kraken/selectionPlaybook.test.js +55 -0
  20. package/dist/cli/kraken/selectionPlaybook.test.js.map +1 -0
  21. package/dist/cli/kraken/turnRuntime.js +207 -0
  22. package/dist/cli/kraken/turnRuntime.js.map +1 -0
  23. package/dist/cli/kraken/turnRuntime.test.js +377 -0
  24. package/dist/cli/kraken/turnRuntime.test.js.map +1 -0
  25. package/dist/cli/kraken/verifier.js +233 -0
  26. package/dist/cli/kraken/verifier.js.map +1 -0
  27. package/dist/cli/kraken/verifier.test.js +232 -0
  28. package/dist/cli/kraken/verifier.test.js.map +1 -0
  29. package/dist/cli/kraken/verifierSettings.test.js +237 -0
  30. package/dist/cli/kraken/verifierSettings.test.js.map +1 -0
  31. package/dist/cli/kraken/verifyReport.js +115 -0
  32. package/dist/cli/kraken/verifyReport.js.map +1 -0
  33. package/dist/cli/kraken/verifyReport.test.js +111 -0
  34. package/dist/cli/kraken/verifyReport.test.js.map +1 -0
  35. package/dist/cli/main.bundled.js +1343 -107
  36. package/dist/cli/main.bundled.js.map +4 -4
  37. package/dist/cli/providerConfig.js +46 -0
  38. package/dist/cli/providerConfig.js.map +1 -1
  39. package/dist/cli/runHeadless.js +85 -0
  40. package/dist/cli/runHeadless.js.map +1 -1
  41. package/dist/cli/toolRegistry.js +50 -6
  42. package/dist/cli/toolRegistry.js.map +1 -1
  43. package/dist/cli/tools/krakenSelectTool.js +165 -0
  44. package/dist/cli/tools/krakenSelectTool.js.map +1 -0
  45. package/dist/cli/tools/krakenSelectTool.test.js +246 -0
  46. package/dist/cli/tools/krakenSelectTool.test.js.map +1 -0
  47. package/dist/cli/tools/taskTool.dynamicChecks.test.js +156 -0
  48. package/dist/cli/tools/taskTool.dynamicChecks.test.js.map +1 -0
  49. package/dist/cli/tools/taskTool.js +174 -6
  50. package/dist/cli/tools/taskTool.js.map +1 -1
  51. package/dist/cli/tools/taskTool.planSafety.test.js +280 -0
  52. package/dist/cli/tools/taskTool.planSafety.test.js.map +1 -0
  53. package/dist/cli/tools/taskTool.verifyReport.test.js +140 -0
  54. package/dist/cli/tools/taskTool.verifyReport.test.js.map +1 -0
  55. package/package.json +1 -1
@@ -1473,27 +1473,27 @@ function readStore() {
1473
1473
  }
1474
1474
  return { providers: {} };
1475
1475
  }
1476
- function writeStore(store4) {
1476
+ function writeStore(store6) {
1477
1477
  const file2 = getKeyStorePath();
1478
1478
  mkdirSync2(path3.dirname(file2), { recursive: true });
1479
- writeFileSync2(file2, JSON.stringify(store4, null, 2), { encoding: "utf-8", mode: 384 });
1479
+ writeFileSync2(file2, JSON.stringify(store6, null, 2), { encoding: "utf-8", mode: 384 });
1480
1480
  }
1481
1481
  function setApiKey(providerId, key) {
1482
- const store4 = readStore();
1483
- store4.providers[providerId] = { apiKey: key };
1484
- writeStore(store4);
1482
+ const store6 = readStore();
1483
+ store6.providers[providerId] = { apiKey: key };
1484
+ writeStore(store6);
1485
1485
  }
1486
1486
  function clearApiKey(providerId) {
1487
- const store4 = readStore();
1488
- delete store4.providers[providerId];
1489
- writeStore(store4);
1487
+ const store6 = readStore();
1488
+ delete store6.providers[providerId];
1489
+ writeStore(store6);
1490
1490
  }
1491
1491
  function getStoredApiKey(providerId) {
1492
- const store4 = readStore();
1493
- return store4.providers[providerId]?.apiKey ?? null;
1492
+ const store6 = readStore();
1493
+ return store6.providers[providerId]?.apiKey ?? null;
1494
1494
  }
1495
1495
  function setOAuthToken(providerId, token) {
1496
- const store4 = readStore();
1496
+ const store6 = readStore();
1497
1497
  const entry = { apiKey: token.apiKey };
1498
1498
  if (typeof token.expiresAt === "number" && Number.isFinite(token.expiresAt)) {
1499
1499
  entry.expiresAt = token.expiresAt;
@@ -1507,12 +1507,12 @@ function setOAuthToken(providerId, token) {
1507
1507
  if (typeof token.idToken === "string" && token.idToken.length > 0) {
1508
1508
  entry.idToken = token.idToken;
1509
1509
  }
1510
- store4.providers[providerId] = entry;
1511
- writeStore(store4);
1510
+ store6.providers[providerId] = entry;
1511
+ writeStore(store6);
1512
1512
  }
1513
1513
  function getOAuthToken(providerId) {
1514
- const store4 = readStore();
1515
- return store4.providers[providerId] ?? null;
1514
+ const store6 = readStore();
1515
+ return store6.providers[providerId] ?? null;
1516
1516
  }
1517
1517
  function resolveApiKey(providerId) {
1518
1518
  const spec = getProviderSpec(providerId);
@@ -1571,8 +1571,8 @@ async function forceRefreshOAuth(providerId, options = {}) {
1571
1571
  function readStoreDirect() {
1572
1572
  return readStore();
1573
1573
  }
1574
- function writeStoreDirect(store4) {
1575
- writeStore(store4);
1574
+ function writeStoreDirect(store6) {
1575
+ writeStore(store6);
1576
1576
  }
1577
1577
  function maskKey(key) {
1578
1578
  if (key.length <= 12) return "****";
@@ -1917,9 +1917,11 @@ var init_thinking = __esm({
1917
1917
  var providerConfig_exports = {};
1918
1918
  __export(providerConfig_exports, {
1919
1919
  clearCustomEndpoint: () => clearCustomEndpoint,
1920
+ clearKrakenVerifier: () => clearKrakenVerifier,
1920
1921
  getActiveModel: () => getActiveModel,
1921
1922
  getActiveProvider: () => getActiveProvider,
1922
1923
  getCustomEndpoint: () => getCustomEndpoint,
1924
+ getKrakenVerifierOverride: () => getKrakenVerifierOverride,
1923
1925
  getModelForProvider: () => getModelForProvider,
1924
1926
  getProviderConfig: () => getProviderConfig,
1925
1927
  getProviderConfigPath: () => getProviderConfigPath,
@@ -1927,6 +1929,7 @@ __export(providerConfig_exports, {
1927
1929
  loadProviderConfig: () => loadProviderConfig,
1928
1930
  setActiveProviderId: () => setActiveProviderId,
1929
1931
  setCustomEndpoint: () => setCustomEndpoint,
1932
+ setKrakenVerifier: () => setKrakenVerifier,
1930
1933
  setModelForProvider: () => setModelForProvider,
1931
1934
  setThinkingForProvider: () => setThinkingForProvider
1932
1935
  });
@@ -1950,7 +1953,8 @@ function getProviderConfig() {
1950
1953
  activeProviderId: parsed.activeProviderId,
1951
1954
  modelByProvider: { ...DEFAULTS.modelByProvider, ...parsed.modelByProvider },
1952
1955
  thinkingByProvider: { ...DEFAULTS.thinkingByProvider, ...parsed.thinkingByProvider },
1953
- customEndpoints: mergeCustomEndpoints(parsed.customEndpoints)
1956
+ customEndpoints: mergeCustomEndpoints(parsed.customEndpoints),
1957
+ krakenVerifier: mergeKrakenVerifier(parsed.krakenVerifier)
1954
1958
  };
1955
1959
  }
1956
1960
  } catch {
@@ -2019,6 +2023,35 @@ function clearCustomEndpoint(id) {
2019
2023
  delete config2.customEndpoints[id];
2020
2024
  writeProviderConfig(config2);
2021
2025
  }
2026
+ function mergeKrakenVerifier(raw) {
2027
+ if (!raw || typeof raw !== "object") return void 0;
2028
+ const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
2029
+ const model = typeof raw.model === "string" ? raw.model.trim() : "";
2030
+ if (!provider || !model) return void 0;
2031
+ if (!PROVIDERS.some((p3) => p3.id === provider)) return void 0;
2032
+ return { provider, model };
2033
+ }
2034
+ function getKrakenVerifierOverride() {
2035
+ return getProviderConfig().krakenVerifier;
2036
+ }
2037
+ function setKrakenVerifier(provider, model) {
2038
+ const spec = PROVIDERS.find((p3) => p3.id === provider);
2039
+ if (!spec) {
2040
+ throw new Error(`Unknown provider id: "${provider}". Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`);
2041
+ }
2042
+ if (!model || model.trim().length === 0) {
2043
+ throw new Error("Verifier model cannot be empty. Use clearKrakenVerifier() to inherit.");
2044
+ }
2045
+ const config2 = getProviderConfig();
2046
+ config2.krakenVerifier = { provider, model: model.trim() };
2047
+ writeProviderConfig(config2);
2048
+ }
2049
+ function clearKrakenVerifier() {
2050
+ const config2 = getProviderConfig();
2051
+ if (!config2.krakenVerifier) return;
2052
+ delete config2.krakenVerifier;
2053
+ writeProviderConfig(config2);
2054
+ }
2022
2055
  function setActiveProviderId(id) {
2023
2056
  const spec = PROVIDERS.find((p3) => p3.id === id);
2024
2057
  if (!spec) {
@@ -20258,7 +20291,7 @@ function getBasePromptModules(mode = "council") {
20258
20291
  function getPromptModule(type) {
20259
20292
  return getBasePromptModules("council").find((m) => m.type === type);
20260
20293
  }
20261
- var CODING_CAPABLE_IDENTITY, COUNCIL_IDENTITY, BEHAVIOR_AGENT, BEHAVIOR_COUNCIL, SAFETY, CONTEXT_SHARING_COUNCIL, OUTPUT_FORMATTING, NATIVE_TOOL_PROTOCOL_MODULE, CODING_PRACTICES_MODULE, TURN_COMPLETION_MODULE, CLARIFICATION_PROTOCOL_MODULE, PROMPT_MODULES, KRAKEN_IDENTITY_MODULE, KRAKEN_LEAD_PLAYBOOK_MODULE, SINGLE_AGENT_IDENTITY_MODULE;
20294
+ var CODING_CAPABLE_IDENTITY, COUNCIL_IDENTITY, BEHAVIOR_AGENT, BEHAVIOR_COUNCIL, SAFETY, CONTEXT_SHARING_COUNCIL, OUTPUT_FORMATTING, NATIVE_TOOL_PROTOCOL_MODULE, CODING_PRACTICES_MODULE, TURN_COMPLETION_MODULE, CLARIFICATION_PROTOCOL_MODULE, PROMPT_MODULES, KRAKEN_IDENTITY_MODULE, KRAKEN_LEAD_PLAYBOOK_MODULE, KRAKEN_SELECTION_PLAYBOOK_MODULE, SINGLE_AGENT_IDENTITY_MODULE;
20262
20295
  var init_promptModules = __esm({
20263
20296
  "packages/core/dist/agents/promptModules.js"() {
20264
20297
  "use strict";
@@ -20456,6 +20489,32 @@ Rules:
20456
20489
  priority: 25,
20457
20490
  content: "# Kraken Lead Playbook (super-agent)\n\nYou are the **parent brain**. Sub-agents spawned with task are tentacles: they cannot see this chat and cannot nest further task calls.\n\n## Default workflow (non-trivial work)\n1. **Orient** - list/read key files; optionally task explore (parallel OK for disjoint questions).\n2. **Decompose** - todo_write with concrete slices and acceptance criteria.\n3. **Implement** - one slice at a time via tools or task agent=general for a bounded unit.\n4. **Verify** - after meaningful writes, run checks yourself (bash / typecheck / tests) or task agent=verify. Do not claim done without on-disk evidence.\n5. **Integrate** - summarize files touched + how to verify; if more remains, checkpoint and ask.\n\n## When to spawn task\n- **explore**: unfamiliar area, multi-file search, map call sites (prefer parallel explores).\n- **general**: isolated implement slice with clear path scope (serial writers unless worktree isolation is on).\n- **verify**: post-implement gate (tests/typecheck/smoke).\n\n## Task contracts (required quality)\nEvery task prompt must be self-contained and include:\n- **Goal** (one sentence)\n- **Scope** (paths / symbols allowed; what is out of scope)\n- **Acceptance** (how the parent will know it succeeded)\n- **Constraints** (no drive-by refactors; match existing style)\n\nOptional tool fields: scope (path allowlist hint), acceptance (checklist). Prefer them when available.\n\n## Caps and discipline\n- Prefer at most 4 explore and 2 general spawns per user turn unless the user asks for more.\n- Do not expand scope beyond the user request.\n- Parallel: many explore OK; general writers stay serial unless ZELARI_KRAKEN_WORKTREE=1.\n- Nested task from children is disabled - you are the only orchestrator.\n- Cheap thoroughness defaults: explore=quick|medium; deep only when stuck.\n- Model routing: explore/verify may use ZELARI_KRAKEN_SUB_MODEL (cheaper); general stays on parent model unless ZELARI_KRAKEN_GENERAL_MODEL is set.\n- After every successful task general, spawn task verify (or run tests yourself) before claiming done - the tool appends a verify-hint footer.\n- Opt-in isolation: ZELARI_KRAKEN_WORKTREE=1 runs general tentacles in a git worktree under .zelari/worktrees/ (KEEP=1 to retain branch for manual merge).\n- Progress bus: tentacle spawns log to .zelari/radio/<session>.jsonl - slash command /kraken shows status.\n\n## Done means verified\nNever end with status theater. Either tools ran and files changed, or you stop with a short report and ask whether to continue."
20458
20491
  };
20492
+ KRAKEN_SELECTION_PLAYBOOK_MODULE = {
20493
+ type: "behavior-rules",
20494
+ title: "Kraken Verified Selection (alpha)",
20495
+ // priority 26 = right after the lead playbook (25); +1000 via custom modules.
20496
+ priority: 26,
20497
+ content: [
20498
+ "# Kraken Verified Selection (alpha)",
20499
+ "",
20500
+ "You can explore competing hypotheses before committing to one implementation path.",
20501
+ "The runtime registers candidates, preserves their evidence verbatim, and judges them via the kraken_select tool.",
20502
+ "",
20503
+ "## When to explore candidates",
20504
+ "- **Simple task** (rename, typo fix, small requested edit, single obvious change): go DIRECT. No candidates, no kraken_select.",
20505
+ "- **Ambiguous task** (two or more plausible root causes or designs): spawn 2 candidates.",
20506
+ "- **High uncertainty** (intermittent bug, race condition, architecture decision with trade-offs): spawn up to 3 candidates.",
20507
+ "",
20508
+ "## Rules",
20509
+ '- Spawn candidates with the task tool using purpose="candidate" - explore-only tentacles; they never write.',
20510
+ "- Each candidate must test a DIFFERENT normalized hypothesis. If two candidates would test the same theory, keep one.",
20511
+ "- Wait for all candidate reports, then call kraken_select exactly once.",
20512
+ "- If the verdict is needs_more_evidence: run at most one more targeted explore, then either re-select or proceed with the best-grounded candidate.",
20513
+ "- Implement ONLY the selected path. Never blend multiple candidates.",
20514
+ "- If the verdict includes required checks: in PLAN fold them into the final plan verification section; in BUILD pass them as the Acceptance criteria of your verify tentacle.",
20515
+ "- A degraded, timed-out, or inconclusive observation is never proof of absence."
20516
+ ].join("\n")
20517
+ };
20459
20518
  SINGLE_AGENT_IDENTITY_MODULE = KRAKEN_IDENTITY_MODULE;
20460
20519
  }
20461
20520
  });
@@ -21014,6 +21073,7 @@ __export(skills_exports, {
21014
21073
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
21015
21074
  KRAKEN_IDENTITY_MODULE: () => KRAKEN_IDENTITY_MODULE,
21016
21075
  KRAKEN_LEAD_PLAYBOOK_MODULE: () => KRAKEN_LEAD_PLAYBOOK_MODULE,
21076
+ KRAKEN_SELECTION_PLAYBOOK_MODULE: () => KRAKEN_SELECTION_PLAYBOOK_MODULE,
21017
21077
  LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
21018
21078
  NATIVE_TOOL_PROTOCOL_MODULE: () => NATIVE_TOOL_PROTOCOL_MODULE,
21019
21079
  SINGLE_AGENT_IDENTITY_MODULE: () => SINGLE_AGENT_IDENTITY_MODULE,
@@ -21260,6 +21320,12 @@ function isBrainTaskSnapshotEvent(e) {
21260
21320
  function isBrainContextMetricsEvent(e) {
21261
21321
  return e.type === "context_metrics";
21262
21322
  }
21323
+ function isBrainKrakenProgressEvent(e) {
21324
+ return e.type === "kraken_progress";
21325
+ }
21326
+ function isBrainKrakenMetricsEvent(e) {
21327
+ return e.type === "kraken_metrics";
21328
+ }
21263
21329
  function createBrainEvent(type, sessionId2, data) {
21264
21330
  return {
21265
21331
  type,
@@ -26972,6 +27038,7 @@ __export(council_exports, {
26972
27038
  IMPLEMENTATION_WRITE_REQUIREMENTS: () => IMPLEMENTATION_WRITE_REQUIREMENTS,
26973
27039
  KRAKEN_IDENTITY_MODULE: () => KRAKEN_IDENTITY_MODULE,
26974
27040
  KRAKEN_LEAD_PLAYBOOK_MODULE: () => KRAKEN_LEAD_PLAYBOOK_MODULE,
27041
+ KRAKEN_SELECTION_PLAYBOOK_MODULE: () => KRAKEN_SELECTION_PLAYBOOK_MODULE,
26975
27042
  LAYOUT_MOTION_PROPS: () => LAYOUT_MOTION_PROPS,
26976
27043
  LESSONS_FILE: () => LESSONS_FILE,
26977
27044
  MAX_DELIVERY_ATTEMPTS: () => MAX_DELIVERY_ATTEMPTS,
@@ -28211,6 +28278,7 @@ __export(dist_exports, {
28211
28278
  IMPLEMENTATION_WRITE_REQUIREMENTS: () => IMPLEMENTATION_WRITE_REQUIREMENTS,
28212
28279
  KRAKEN_IDENTITY_MODULE: () => KRAKEN_IDENTITY_MODULE,
28213
28280
  KRAKEN_LEAD_PLAYBOOK_MODULE: () => KRAKEN_LEAD_PLAYBOOK_MODULE,
28281
+ KRAKEN_SELECTION_PLAYBOOK_MODULE: () => KRAKEN_SELECTION_PLAYBOOK_MODULE,
28214
28282
  LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
28215
28283
  LAYOUT_MOTION_PROPS: () => LAYOUT_MOTION_PROPS,
28216
28284
  LESSONS_FILE: () => LESSONS_FILE,
@@ -28335,6 +28403,8 @@ __export(dist_exports, {
28335
28403
  isBrainContextMetricsEvent: () => isBrainContextMetricsEvent,
28336
28404
  isBrainCouncilModeEvent: () => isBrainCouncilModeEvent,
28337
28405
  isBrainErrorEvent: () => isBrainErrorEvent,
28406
+ isBrainKrakenMetricsEvent: () => isBrainKrakenMetricsEvent,
28407
+ isBrainKrakenProgressEvent: () => isBrainKrakenProgressEvent,
28338
28408
  isBrainMemberCostEvent: () => isBrainMemberCostEvent,
28339
28409
  isBrainMessageDeltaEvent: () => isBrainMessageDeltaEvent,
28340
28410
  isBrainMessageEndEvent: () => isBrainMessageEndEvent,
@@ -31155,6 +31225,362 @@ var init_krakenWorktree = __esm({
31155
31225
  }
31156
31226
  });
31157
31227
 
31228
+ // src/cli/kraken/candidateRegistry.ts
31229
+ function isKrakenSelectionEnabled() {
31230
+ return process.env.ZELARI_KRAKEN_SELECTION === "1";
31231
+ }
31232
+ function store2() {
31233
+ const g = globalThis;
31234
+ if (!g.__zelariKrakenCandidates) g.__zelariKrakenCandidates = [];
31235
+ return g.__zelariKrakenCandidates;
31236
+ }
31237
+ function resetKrakenCandidates() {
31238
+ const g = globalThis;
31239
+ g.__zelariKrakenCandidates = [];
31240
+ g.__zelariKrakenSelection = null;
31241
+ g.__zelariKrakenCheckResults = null;
31242
+ }
31243
+ function setKrakenSelection(verdict) {
31244
+ const g = globalThis;
31245
+ g.__zelariKrakenSelection = verdict;
31246
+ }
31247
+ function getKrakenSelection() {
31248
+ const g = globalThis;
31249
+ return g.__zelariKrakenSelection ?? null;
31250
+ }
31251
+ function krakenRequiredChecks() {
31252
+ const verdict = getKrakenSelection();
31253
+ if (!verdict || verdict.status !== "selected") return [];
31254
+ return verdict.requiredChecks;
31255
+ }
31256
+ function setKrakenCheckResults(results) {
31257
+ const g = globalThis;
31258
+ g.__zelariKrakenCheckResults = results;
31259
+ }
31260
+ function getKrakenCheckResults() {
31261
+ const g = globalThis;
31262
+ const results = g.__zelariKrakenCheckResults;
31263
+ return results ? [...results] : null;
31264
+ }
31265
+ function krakenChecksPassed() {
31266
+ const results = getKrakenCheckResults();
31267
+ if (!results) return void 0;
31268
+ return results.filter((r) => r.status === "pass").length;
31269
+ }
31270
+ function krakenCandidates() {
31271
+ return [...store2()];
31272
+ }
31273
+ function reserveCandidateSlot() {
31274
+ const n = store2().length;
31275
+ if (n >= KRAKEN_CANDIDATE_CAP) {
31276
+ return {
31277
+ error: `task: candidate cap reached (${KRAKEN_CANDIDATE_CAP}). Compare the existing candidates instead of spawning more.`
31278
+ };
31279
+ }
31280
+ return { index: n + 1 };
31281
+ }
31282
+ function parseCandidateReport(raw) {
31283
+ const open = raw.lastIndexOf(REPORT_OPEN);
31284
+ const close = raw.lastIndexOf(REPORT_CLOSE);
31285
+ if (open === -1 || close === -1 || close < open) {
31286
+ return { ok: false, error: "missing report block" };
31287
+ }
31288
+ const body = raw.slice(open + REPORT_OPEN.length, close).trim();
31289
+ let parsed;
31290
+ try {
31291
+ parsed = JSON.parse(body);
31292
+ } catch (err) {
31293
+ return {
31294
+ ok: false,
31295
+ error: `invalid JSON (${err instanceof Error ? err.message : String(err)})`
31296
+ };
31297
+ }
31298
+ if (typeof parsed !== "object" || parsed === null) {
31299
+ return { ok: false, error: "report is not a JSON object" };
31300
+ }
31301
+ const obj = parsed;
31302
+ const evidence = Array.isArray(obj.evidence) ? obj.evidence.filter((e) => typeof e === "object" && e !== null).map((e) => ({
31303
+ claim: typeof e.claim === "string" ? e.claim : "",
31304
+ basis: typeof e.basis === "string" ? e.basis : "",
31305
+ degraded: e.degraded === true
31306
+ })).filter((e) => e.claim.trim().length > 0) : [];
31307
+ const risks = Array.isArray(obj.risks) ? obj.risks.filter((r) => typeof r === "string" && r.trim().length > 0) : [];
31308
+ return {
31309
+ ok: true,
31310
+ report: {
31311
+ hypothesis: typeof obj.hypothesis === "string" ? obj.hypothesis.trim() : "",
31312
+ evidence,
31313
+ risks,
31314
+ hasDegradedEvidence: evidence.some((e) => e.degraded)
31315
+ }
31316
+ };
31317
+ }
31318
+ function registerCandidate(entry) {
31319
+ const withIndex = { ...entry, index: entry.index ?? store2().length + 1 };
31320
+ store2().push(withIndex);
31321
+ return withIndex;
31322
+ }
31323
+ function candidateInstructions(index, cap3 = KRAKEN_CANDIDATE_CAP) {
31324
+ return CANDIDATE_INSTRUCTIONS.replaceAll("{index}", String(index)).replaceAll("{cap}", String(cap3));
31325
+ }
31326
+ var KRAKEN_CANDIDATE_CAP, REPORT_OPEN, REPORT_CLOSE, CANDIDATE_INSTRUCTIONS;
31327
+ var init_candidateRegistry = __esm({
31328
+ "src/cli/kraken/candidateRegistry.ts"() {
31329
+ "use strict";
31330
+ KRAKEN_CANDIDATE_CAP = 3;
31331
+ REPORT_OPEN = "<candidate-report>";
31332
+ REPORT_CLOSE = "</candidate-report>";
31333
+ CANDIDATE_INSTRUCTIONS = [
31334
+ "You are CANDIDATE #{index} of at most {cap}: one independent hypothesis",
31335
+ "about the task, researched in parallel with other candidates.",
31336
+ "DIVERSITY: focus on ONE hypothesis and pursue it honestly \u2014 do not try to",
31337
+ "cover every angle. A narrower, well-evidenced hypothesis beats a broad guess.",
31338
+ "OBSERVATION INTEGRITY: mark degraded or inconclusive observations explicitly",
31339
+ "(timeout, zero files walked, unavailable backend) \u2014 degraded is NOT proof",
31340
+ "of absence.",
31341
+ "END your final message with EXACTLY this block (valid JSON, no prose after):",
31342
+ "<candidate-report>",
31343
+ "{",
31344
+ ' "hypothesis": "one-sentence hypothesis you investigated",',
31345
+ ' "evidence": [',
31346
+ ' { "claim": "what you found", "basis": "tool + file:line or command", "degraded": false }',
31347
+ " ],",
31348
+ ' "risks": ["open questions / weaknesses"]',
31349
+ "}",
31350
+ "</candidate-report>"
31351
+ ].join("\n");
31352
+ }
31353
+ });
31354
+
31355
+ // src/cli/kraken/verifyReport.ts
31356
+ function normalize(text) {
31357
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
31358
+ }
31359
+ function extractVerifyReportBlocks(raw) {
31360
+ const blocks = [];
31361
+ let cursor = 0;
31362
+ for (; ; ) {
31363
+ const open = raw.indexOf(OPEN, cursor);
31364
+ if (open === -1) break;
31365
+ const close = raw.indexOf(CLOSE, open + OPEN.length);
31366
+ if (close === -1) break;
31367
+ const block = { check: "", status: "" };
31368
+ for (const line of raw.slice(open + OPEN.length, close).split(/\r?\n/)) {
31369
+ const m = /^(check|status|note)\s*:\s*(.*)$/i.exec(line.trim());
31370
+ if (!m) continue;
31371
+ const value = m[2].trim();
31372
+ const key = m[1].toLowerCase();
31373
+ if (key === "check" && !block.check) block.check = value;
31374
+ else if (key === "status" && !block.status) block.status = value;
31375
+ else if (key === "note" && block.note === void 0) block.note = value;
31376
+ }
31377
+ if (block.check) blocks.push(block);
31378
+ cursor = close + CLOSE.length;
31379
+ }
31380
+ return blocks;
31381
+ }
31382
+ function parseVerifyReport(raw, requiredChecks) {
31383
+ const byCriterion = /* @__PURE__ */ new Map();
31384
+ for (const block of extractVerifyReportBlocks(raw)) {
31385
+ byCriterion.set(normalize(block.check), block);
31386
+ }
31387
+ const keys = [...byCriterion.keys()];
31388
+ return requiredChecks.map((check2) => {
31389
+ const norm = normalize(check2);
31390
+ let block = byCriterion.get(norm) ?? null;
31391
+ if (!block) {
31392
+ for (const key of keys) {
31393
+ if (key.length >= 8 && (key.includes(norm) || norm.includes(key))) {
31394
+ block = byCriterion.get(key) ?? null;
31395
+ break;
31396
+ }
31397
+ }
31398
+ }
31399
+ if (!block) {
31400
+ return {
31401
+ check: check2,
31402
+ status: "unknown",
31403
+ note: "no verify-report block for this check"
31404
+ };
31405
+ }
31406
+ const status = VALID_STATUSES.has(block.status) ? block.status : "unknown";
31407
+ return { check: check2, status, ...block.note ? { note: block.note } : {} };
31408
+ });
31409
+ }
31410
+ function allUnknownCheckResults(requiredChecks, reason) {
31411
+ return requiredChecks.map((check2) => ({
31412
+ check: check2,
31413
+ status: "unknown",
31414
+ note: reason
31415
+ }));
31416
+ }
31417
+ var OPEN, CLOSE, VALID_STATUSES;
31418
+ var init_verifyReport = __esm({
31419
+ "src/cli/kraken/verifyReport.ts"() {
31420
+ "use strict";
31421
+ OPEN = "<verify-report>";
31422
+ CLOSE = "</verify-report>";
31423
+ VALID_STATUSES = /* @__PURE__ */ new Set(["pass", "fail", "unknown"]);
31424
+ }
31425
+ });
31426
+
31427
+ // src/cli/kraken/completionGate.ts
31428
+ function normalize2(text) {
31429
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
31430
+ }
31431
+ function classifyKrakenChecks(requiredChecks, results) {
31432
+ const byCriterion = /* @__PURE__ */ new Map();
31433
+ for (const result of results ?? []) {
31434
+ byCriterion.set(normalize2(result.check), result);
31435
+ }
31436
+ const keys = [...byCriterion.keys()];
31437
+ const out = { passed: [], failed: [], unknown: [] };
31438
+ for (const check2 of requiredChecks) {
31439
+ const norm = normalize2(check2);
31440
+ let result = byCriterion.get(norm) ?? null;
31441
+ if (!result) {
31442
+ for (const key of keys) {
31443
+ if (key.length >= 8 && (key.includes(norm) || norm.includes(key))) {
31444
+ result = byCriterion.get(key) ?? null;
31445
+ break;
31446
+ }
31447
+ }
31448
+ }
31449
+ if (!result || result.status === "unknown") out.unknown.push(check2);
31450
+ else if (result.status === "pass") out.passed.push(check2);
31451
+ else out.failed.push(check2);
31452
+ }
31453
+ return out;
31454
+ }
31455
+ function evaluateKrakenCompletionGate(mode) {
31456
+ try {
31457
+ if (mode !== "build") return OPEN_GATE;
31458
+ const checks = krakenRequiredChecks();
31459
+ if (checks.length === 0) return OPEN_GATE;
31460
+ const classification = classifyKrakenChecks(checks, getKrakenCheckResults());
31461
+ return {
31462
+ blocked: classification.failed.length > 0 || classification.unknown.length > 0,
31463
+ selectionUsed: true,
31464
+ total: checks.length,
31465
+ passed: classification.passed.length,
31466
+ failedChecks: classification.failed,
31467
+ unknownChecks: classification.unknown
31468
+ };
31469
+ } catch {
31470
+ return OPEN_GATE;
31471
+ }
31472
+ }
31473
+ function buildKrakenRepairPrompt(gate) {
31474
+ const lines = [
31475
+ `The BUILD turn is ending, but the required checks from kraken_select are not all satisfied (passed ${gate.passed}/${gate.total}).`,
31476
+ ""
31477
+ ];
31478
+ if (gate.failedChecks.length > 0) {
31479
+ lines.push("FAILED checks (evidence contradicts them):");
31480
+ for (const check2 of gate.failedChecks) lines.push(`- ${check2}`);
31481
+ lines.push("");
31482
+ }
31483
+ if (gate.unknownChecks.length > 0) {
31484
+ lines.push(
31485
+ "UNKNOWN checks (never conclusively verified \u2014 a degraded or missing observation is NOT proof):"
31486
+ );
31487
+ for (const check2 of gate.unknownChecks) lines.push(`- ${check2}`);
31488
+ lines.push("");
31489
+ }
31490
+ lines.push(
31491
+ "Recover this turn:",
31492
+ "1. The approach selection is settled \u2014 do NOT call kraken_select again.",
31493
+ "2. Fix each failing check directly with focused edits.",
31494
+ "3. Make each unknown check conclusively verifiable (run the real command, read the real output).",
31495
+ "4. Spawn a `task verify` tentacle whose Acceptance lists ALL required checks; its conclusion must contain one <verify-report> block per check with an explicit status.",
31496
+ "5. Only end the turn when every check reports status: pass \u2014 an unverified assumption is not a pass."
31497
+ );
31498
+ return lines.join("\n");
31499
+ }
31500
+ var OPEN_GATE;
31501
+ var init_completionGate = __esm({
31502
+ "src/cli/kraken/completionGate.ts"() {
31503
+ "use strict";
31504
+ init_candidateRegistry();
31505
+ OPEN_GATE = {
31506
+ blocked: false,
31507
+ selectionUsed: false,
31508
+ total: 0,
31509
+ passed: 0,
31510
+ failedChecks: [],
31511
+ unknownChecks: []
31512
+ };
31513
+ }
31514
+ });
31515
+
31516
+ // src/cli/kraken/metrics.ts
31517
+ function store3() {
31518
+ const g = globalThis;
31519
+ g.__zelariKrakenTurnMetrics ??= {
31520
+ candidateTokens: 0,
31521
+ selectionRecorded: false,
31522
+ selectionFallback: false,
31523
+ repairTriggered: false,
31524
+ repairSucceeded: false
31525
+ };
31526
+ return g.__zelariKrakenTurnMetrics;
31527
+ }
31528
+ function resetKrakenTurnMetrics() {
31529
+ const g = globalThis;
31530
+ g.__zelariKrakenTurnMetrics = void 0;
31531
+ }
31532
+ function recordCandidateTokens(totalTokens) {
31533
+ if (!Number.isFinite(totalTokens) || totalTokens <= 0) return;
31534
+ store3().candidateTokens += Math.round(totalTokens);
31535
+ }
31536
+ function recordSelectionOutcome(outcome) {
31537
+ const s = store3();
31538
+ s.selectionRecorded = true;
31539
+ s.selectionLatencyMs = Math.max(0, Math.round(outcome.latencyMs));
31540
+ if (typeof outcome.tokens === "number" && Number.isFinite(outcome.tokens) && outcome.tokens > 0) {
31541
+ s.selectionTokens = Math.round(outcome.tokens);
31542
+ }
31543
+ s.selectionFallback = outcome.degraded;
31544
+ s.selectionFallbackReason = outcome.fallbackReason;
31545
+ }
31546
+ function markRepairTriggered() {
31547
+ store3().repairTriggered = true;
31548
+ }
31549
+ function markRepairSucceeded() {
31550
+ if (store3().repairTriggered) store3().repairSucceeded = true;
31551
+ }
31552
+ function collectKrakenTurnMetrics() {
31553
+ const s = store3();
31554
+ const verdict = getKrakenSelection();
31555
+ const candidates = krakenCandidates();
31556
+ if (!verdict && candidates.length === 0 && !s.selectionRecorded && !s.repairTriggered) {
31557
+ return null;
31558
+ }
31559
+ const classification = classifyKrakenChecks(krakenRequiredChecks(), getKrakenCheckResults());
31560
+ return {
31561
+ selectionUsed: verdict !== null,
31562
+ candidateCount: candidates.length,
31563
+ candidateTokens: s.candidateTokens,
31564
+ ...s.selectionTokens !== void 0 ? { selectionTokens: s.selectionTokens } : {},
31565
+ ...s.selectionLatencyMs !== void 0 ? { selectionLatencyMs: s.selectionLatencyMs } : {},
31566
+ selectionFallback: s.selectionFallback,
31567
+ ...s.selectionFallbackReason !== void 0 ? { selectionFallbackReason: s.selectionFallbackReason } : {},
31568
+ needsMoreEvidence: verdict?.status === "needs_more_evidence",
31569
+ verificationPass: classification.passed.length,
31570
+ verificationFail: classification.failed.length,
31571
+ verificationUnknown: classification.unknown.length,
31572
+ repairTriggered: s.repairTriggered,
31573
+ repairSucceeded: s.repairSucceeded
31574
+ };
31575
+ }
31576
+ var init_metrics2 = __esm({
31577
+ "src/cli/kraken/metrics.ts"() {
31578
+ "use strict";
31579
+ init_candidateRegistry();
31580
+ init_completionGate();
31581
+ }
31582
+ });
31583
+
31158
31584
  // src/cli/tools/taskTool.ts
31159
31585
  function resetTaskSpawnCount() {
31160
31586
  const g = globalThis;
@@ -31184,6 +31610,21 @@ function buildTaskUserPrompt(args) {
31184
31610
  }
31185
31611
  return parts.join("\n");
31186
31612
  }
31613
+ function withKrakenRequiredChecks(agent, acceptance) {
31614
+ if (agent !== "verify") return acceptance;
31615
+ const required2 = krakenRequiredChecks();
31616
+ if (required2.length === 0) return acceptance;
31617
+ const seen = new Set((acceptance ?? []).map((a) => a.trim().toLowerCase()));
31618
+ const merged = [...acceptance ?? []];
31619
+ for (const check2 of required2) {
31620
+ const key = check2.trim().toLowerCase();
31621
+ if (!seen.has(key)) {
31622
+ seen.add(key);
31623
+ merged.push(check2);
31624
+ }
31625
+ }
31626
+ return merged;
31627
+ }
31187
31628
  function systemPromptForAgent(agent) {
31188
31629
  if (agent === "general") return GENERAL_PROMPT;
31189
31630
  if (agent === "verify") return VERIFY_PROMPT;
@@ -31209,6 +31650,7 @@ async function runSubAgent(harness, opts = {}) {
31209
31650
  let current = "";
31210
31651
  let lastCompleted = "";
31211
31652
  let error51;
31653
+ let usage;
31212
31654
  if (signal?.aborted) return { result: "", aborted: true };
31213
31655
  for await (const ev of harness.run()) {
31214
31656
  if (signal?.aborted) {
@@ -31227,6 +31669,16 @@ async function runSubAgent(harness, opts = {}) {
31227
31669
  break;
31228
31670
  case "message_end":
31229
31671
  if (current.trim()) lastCompleted = current;
31672
+ if (ev.usage) {
31673
+ usage = usage ? {
31674
+ promptTokens: usage.promptTokens + ev.usage.promptTokens,
31675
+ completionTokens: usage.completionTokens + ev.usage.completionTokens,
31676
+ totalTokens: usage.totalTokens + ev.usage.totalTokens,
31677
+ ...(usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0) > 0 ? {
31678
+ cachedPromptTokens: (usage.cachedPromptTokens ?? 0) + (ev.usage.cachedPromptTokens ?? 0)
31679
+ } : {}
31680
+ } : ev.usage;
31681
+ }
31230
31682
  current = "";
31231
31683
  break;
31232
31684
  case "error":
@@ -31237,7 +31689,7 @@ async function runSubAgent(harness, opts = {}) {
31237
31689
  }
31238
31690
  }
31239
31691
  const result = (lastCompleted || current).trim();
31240
- return { result, ...error51 ? { error: error51 } : {} };
31692
+ return { result, ...error51 ? { error: error51 } : {}, ...usage ? { usage } : {} };
31241
31693
  }
31242
31694
  async function runTentacle(opts) {
31243
31695
  const { deps, args, agent, thoroughness, parentCwd, sessionId: sessionId2 } = opts;
@@ -31312,7 +31764,7 @@ async function runTentacle(opts) {
31312
31764
  const userContent = buildTaskUserPrompt({
31313
31765
  prompt: args.prompt,
31314
31766
  scope: args.scope,
31315
- acceptance: args.acceptance
31767
+ acceptance: withKrakenRequiredChecks(agent, args.acceptance)
31316
31768
  });
31317
31769
  const maxToolCalls = maxToolCallsForThoroughness(thoroughness, agent);
31318
31770
  const runCwd = sub.cwd || effectiveCwd;
@@ -31347,7 +31799,7 @@ async function runTentacle(opts) {
31347
31799
  error: `task: failed to start sub-agent \u2014 ${err instanceof Error ? err.message : String(err)}`
31348
31800
  };
31349
31801
  }
31350
- const { result, error: error51, aborted: aborted2 } = await runSubAgent(harness, {
31802
+ const { result, error: error51, aborted: aborted2, usage } = await runSubAgent(harness, {
31351
31803
  ...opts.signal ? { signal: opts.signal } : {}
31352
31804
  });
31353
31805
  const durationMs = Date.now() - started;
@@ -31448,19 +31900,50 @@ ${verifyHintForGeneral(args.acceptance)}`;
31448
31900
  model: sub.model,
31449
31901
  result,
31450
31902
  footer,
31903
+ ...usage ? { usage } : {},
31451
31904
  worktreePath: worktree?.path ?? null,
31452
31905
  worktreeHandle: worktree
31453
31906
  };
31454
31907
  }
31455
- function createTaskTool(deps) {
31908
+ function createTaskTool(deps, policy = {}) {
31909
+ const allowedAgents = policy.allowedAgents ?? ["explore", "general", "verify"];
31910
+ const restricted = policy.allowedAgents !== void 0 && !(policy.allowedAgents.includes("explore") && policy.allowedAgents.includes("general") && policy.allowedAgents.includes("verify"));
31911
+ const inputSchema2 = restricted ? TaskArgsSchema.extend({
31912
+ agent: external_exports.enum(allowedAgents).optional().describe(
31913
+ `Sub-agent type. In this mode ONLY ${allowedAgents.join("|")} is allowed (plan-safe read-only tentacles).`
31914
+ )
31915
+ }) : TaskArgsSchema;
31456
31916
  return {
31457
31917
  name: "task",
31458
- description: "Delegate a focused sub-task to an isolated sub-agent with its own context; returns only a concise conclusion (keeps parent context lean).\n- agent=explore (default): read-only research/search\n- agent=general: can edit files for one bounded unit of work\n- agent=verify: read + bash to run tests/checks\nProvide a fully self-contained `prompt` (sub-agent cannot see this conversation). Optional scope[] + acceptance[] contracts. After general, follow up with verify.",
31918
+ description: "Delegate a focused sub-task to an isolated sub-agent with its own context; returns only a concise conclusion (keeps parent context lean).\n- agent=explore (default): read-only research/search\n- agent=general: can edit files for one bounded unit of work\n- agent=verify: read + bash to run tests/checks\nProvide a fully self-contained `prompt` (sub-agent cannot see this conversation). Optional scope[] + acceptance[] contracts. After general, follow up with verify." + (restricted ? `
31919
+ RESTRICTED in this mode: only agent=${allowedAgents.join("|")} is allowed.` : ""),
31459
31920
  permissions: ["read", "network", "write", "execute"],
31460
31921
  timeoutMs: 3e5,
31461
- inputSchema: TaskArgsSchema,
31922
+ inputSchema: inputSchema2,
31462
31923
  execute: async (args, ctx) => {
31463
31924
  const agent = args.agent ?? "explore";
31925
+ let candidateSlot = 0;
31926
+ if (!allowedAgents.includes(agent)) {
31927
+ return typedErr(
31928
+ `task: agent=${agent} is not allowed in this mode. Allowed: ${allowedAgents.join(", ")} (plan-safe read-only tentacles). Re-issue with an allowed agent kind.`
31929
+ );
31930
+ }
31931
+ const isCandidate = args.purpose === "candidate";
31932
+ if (isCandidate) {
31933
+ if (!isKrakenSelectionEnabled()) {
31934
+ return typedErr(
31935
+ "task: purpose=candidate requires ZELARI_KRAKEN_SELECTION=1 (alpha feature). Spawn a plain explore tentacle instead."
31936
+ );
31937
+ }
31938
+ if (agent !== "explore") {
31939
+ return typedErr(
31940
+ "task: purpose=candidate forces agent=explore (candidates are read-only in v1 \u2014 zero candidate implementations, ADR-0020)."
31941
+ );
31942
+ }
31943
+ const slot = reserveCandidateSlot();
31944
+ if ("error" in slot) return typedErr(slot.error);
31945
+ candidateSlot = slot.index;
31946
+ }
31464
31947
  const thoroughness = args.thoroughness ?? "medium";
31465
31948
  const sessionId2 = ctx.sessionId || "default";
31466
31949
  const parentCwd = ctx.cwd || process.cwd();
@@ -31483,8 +31966,51 @@ function createTaskTool(deps) {
31483
31966
  agent,
31484
31967
  thoroughness,
31485
31968
  parentCwd,
31486
- sessionId: sessionId2
31969
+ sessionId: sessionId2,
31970
+ // Fase 1 (ADR-0020): propagate the parent turn's cancellation signal
31971
+ // so cancel/timeout unwinds the tentacle instead of letting it run on.
31972
+ ...ctx.signal ? { signal: ctx.signal } : {},
31973
+ ...isCandidate ? {
31974
+ systemPromptOverride: systemPromptForAgent("explore") + "\n\n" + candidateInstructions(candidateSlot)
31975
+ } : {}
31487
31976
  });
31977
+ if (isCandidate) {
31978
+ recordCandidateTokens(res.ok ? res.usage?.totalTokens ?? 0 : 0);
31979
+ if (res.ok) {
31980
+ const parsed = parseCandidateReport(res.result);
31981
+ registerCandidate(
31982
+ parsed.ok ? {
31983
+ status: "ok",
31984
+ index: candidateSlot,
31985
+ description: args.description,
31986
+ report: parsed.report,
31987
+ raw: res.result
31988
+ } : {
31989
+ status: "malformed",
31990
+ index: candidateSlot,
31991
+ description: args.description,
31992
+ error: parsed.error,
31993
+ raw: res.result
31994
+ }
31995
+ );
31996
+ } else {
31997
+ registerCandidate({
31998
+ status: "malformed",
31999
+ index: candidateSlot,
32000
+ description: args.description,
32001
+ error: res.error,
32002
+ raw: ""
32003
+ });
32004
+ }
32005
+ }
32006
+ if (agent === "verify") {
32007
+ const required2 = krakenRequiredChecks();
32008
+ if (required2.length > 0) {
32009
+ setKrakenCheckResults(
32010
+ res.ok ? parseVerifyReport(res.result, required2) : allUnknownCheckResults(required2, `verify tentacle failed: ${res.error}`)
32011
+ );
32012
+ }
32013
+ }
31488
32014
  if (!res.ok) return typedErr(res.error);
31489
32015
  return typedOk({
31490
32016
  result: `[sub-agent:${res.agent}/${res.thoroughness} model=${res.model}]
@@ -31494,7 +32020,7 @@ ${res.result}${res.footer}`,
31494
32020
  }
31495
32021
  };
31496
32022
  }
31497
- var EXPLORE_PROMPT, GENERAL_PROMPT, VERIFY_PROMPT, TaskArgsSchema;
32023
+ var EXPLORE_PROMPT, GENERAL_PROMPT, VERIFY_PROMPT, TaskArgsSchema, TaskPurposeSchema, TaskArgsWithPurposeSchema;
31498
32024
  var init_taskTool = __esm({
31499
32025
  "src/cli/tools/taskTool.ts"() {
31500
32026
  "use strict";
@@ -31503,6 +32029,9 @@ var init_taskTool = __esm({
31503
32029
  init_krakenRadio();
31504
32030
  init_krakenWorktree();
31505
32031
  init_krakenLive();
32032
+ init_candidateRegistry();
32033
+ init_verifyReport();
32034
+ init_metrics2();
31506
32035
  EXPLORE_PROMPT = [
31507
32036
  "You are a focused EXPLORE tentacle of Kraken (parent super-agent).",
31508
32037
  "READ-ONLY tools only (read, list, grep, fetch). No edits, no shell.",
@@ -31528,7 +32057,16 @@ var init_taskTool = __esm({
31528
32057
  "You may read files and run test/build commands via bash. Prefer",
31529
32058
  "targeted checks over full suite when possible.",
31530
32059
  "Report: pass/fail, commands run, key output, and gaps vs Acceptance criteria.",
31531
- "If Acceptance criteria are listed, check each one explicitly."
32060
+ "If Acceptance criteria are listed, check each one explicitly.",
32061
+ "End your final message with ONE <verify-report> block per acceptance",
32062
+ "criterion (required checks included), in this exact shape:",
32063
+ "<verify-report>",
32064
+ "check: <criterion text as given>",
32065
+ "status: pass | fail | unknown",
32066
+ "note: <one line of evidence (command + outcome)>",
32067
+ "</verify-report>",
32068
+ "Use status=unknown when you could NOT determine the outcome (degraded",
32069
+ "tool, timeout, inconclusive evidence) \u2014 never guess pass."
31532
32070
  ].join("\n");
31533
32071
  TaskArgsSchema = external_exports.object({
31534
32072
  description: external_exports.string().min(1).describe("A 3-6 word label for the sub-task (for logs/UI)."),
@@ -31546,6 +32084,336 @@ var init_taskTool = __esm({
31546
32084
  "Optional acceptance checklist (contract). Appended to the prompt as Acceptance criteria."
31547
32085
  )
31548
32086
  });
32087
+ TaskPurposeSchema = external_exports.enum(["candidate"]).optional().describe(
32088
+ "Mark this explore tentacle as one CANDIDATE hypothesis (alpha: requires ZELARI_KRAKEN_SELECTION=1). Forces agent=explore, structured report."
32089
+ );
32090
+ TaskArgsWithPurposeSchema = TaskArgsSchema.extend({
32091
+ purpose: TaskPurposeSchema
32092
+ });
32093
+ }
32094
+ });
32095
+
32096
+ // src/cli/kraken/verifier.ts
32097
+ function resolveKrakenVerifier(parent, env = process.env, explicit) {
32098
+ if (explicit?.provider?.trim() && explicit?.model?.trim()) {
32099
+ return { provider: explicit.provider.trim(), model: explicit.model.trim() };
32100
+ }
32101
+ const envProvider = env.ZELARI_KRAKEN_SELECT_PROVIDER?.trim();
32102
+ const envModel = env.ZELARI_KRAKEN_SELECT_MODEL?.trim();
32103
+ if (envProvider && envModel) return { provider: envProvider, model: envModel };
32104
+ if (envModel) return { provider: parent.provider, model: envModel };
32105
+ return { provider: parent.provider, model: parent.model };
32106
+ }
32107
+ function renderCandidate(entry) {
32108
+ if (entry.status === "malformed") {
32109
+ return [
32110
+ `## Candidate #${entry.index} \u2014 UNUSABLE (malformed report: ${entry.error})`,
32111
+ "This candidate cannot win; it is listed for completeness."
32112
+ ].join("\n");
32113
+ }
32114
+ const lines = [`## Candidate #${entry.index} \u2014 ${entry.report.hypothesis || "(no hypothesis stated)"}`];
32115
+ if (entry.report.evidence.length === 0) {
32116
+ lines.push("Evidence: NONE (unsupported hypothesis)");
32117
+ } else {
32118
+ lines.push("Evidence:");
32119
+ for (const e of entry.report.evidence) {
32120
+ const tag = e.degraded ? "DEGRADED \u2014 inconclusive, NOT proof of absence" : "OK";
32121
+ lines.push(`- [${tag}] ${e.claim}${e.basis ? ` (basis: ${e.basis})` : ""}`);
32122
+ }
32123
+ }
32124
+ if (entry.report.risks.length > 0) {
32125
+ lines.push("Risks:");
32126
+ for (const r of entry.report.risks) lines.push(`- ${r}`);
32127
+ }
32128
+ return lines.join("\n");
32129
+ }
32130
+ function buildSelectionPrompt(task, candidates) {
32131
+ return [
32132
+ "TASK",
32133
+ task || "(the current user task \u2014 judge against the candidates below)",
32134
+ "",
32135
+ "CANDIDATES (read-only explorer reports):",
32136
+ ...candidates.map(renderCandidate),
32137
+ "",
32138
+ "Decide which candidate is best supported by the evidence above."
32139
+ ].join("\n");
32140
+ }
32141
+ function needsMoreEvidence(rationale, fallbackReason) {
32142
+ return {
32143
+ status: "needs_more_evidence",
32144
+ winnerIndex: null,
32145
+ rationale,
32146
+ requiredChecks: [],
32147
+ degraded: true,
32148
+ ...fallbackReason ? { fallbackReason } : {},
32149
+ verifier: null,
32150
+ judgedBy: "deterministic"
32151
+ };
32152
+ }
32153
+ function parseSelectionVerdict(raw, candidates) {
32154
+ const open = raw.lastIndexOf(VERDICT_OPEN);
32155
+ const close = raw.lastIndexOf(VERDICT_CLOSE);
32156
+ if (open === -1 || close === -1 || close < open) {
32157
+ return { ok: false, error: "missing verdict block" };
32158
+ }
32159
+ const body = raw.slice(open + VERDICT_OPEN.length, close).trim();
32160
+ let parsed;
32161
+ try {
32162
+ parsed = JSON.parse(body);
32163
+ } catch (err) {
32164
+ return { ok: false, error: `invalid JSON (${err instanceof Error ? err.message : String(err)})` };
32165
+ }
32166
+ if (typeof parsed !== "object" || parsed === null) {
32167
+ return { ok: false, error: "verdict is not a JSON object" };
32168
+ }
32169
+ const obj = parsed;
32170
+ const status = obj.status;
32171
+ if (status !== "selected" && status !== "needs_more_evidence") {
32172
+ return { ok: false, error: `invalid status (${String(status)})` };
32173
+ }
32174
+ const rationale = typeof obj.rationale === "string" ? obj.rationale.trim() : "";
32175
+ const requiredChecks = (Array.isArray(obj.requiredChecks) ? obj.requiredChecks : []).filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()).slice(0, 5);
32176
+ if (status === "needs_more_evidence") {
32177
+ return {
32178
+ ok: true,
32179
+ verdict: {
32180
+ status,
32181
+ winnerIndex: null,
32182
+ rationale: rationale || "Candidates are not sufficiently differentiated by evidence.",
32183
+ requiredChecks,
32184
+ degraded: false,
32185
+ verifier: null,
32186
+ judgedBy: "llm"
32187
+ }
32188
+ };
32189
+ }
32190
+ const winner = obj.winnerIndex;
32191
+ if (typeof winner !== "number" || !Number.isInteger(winner)) {
32192
+ return { ok: false, error: `selected without integer winnerIndex (${String(winner)})` };
32193
+ }
32194
+ const entry = candidates.find((c) => c.index === winner);
32195
+ if (!entry || entry.status !== "ok") {
32196
+ return { ok: false, error: `winnerIndex ${winner} does not point at a usable candidate` };
32197
+ }
32198
+ return {
32199
+ ok: true,
32200
+ verdict: {
32201
+ status: "selected",
32202
+ winnerIndex: winner,
32203
+ rationale: rationale || "Selected by evidence comparison.",
32204
+ requiredChecks,
32205
+ degraded: false,
32206
+ verifier: null,
32207
+ judgedBy: "llm"
32208
+ }
32209
+ };
32210
+ }
32211
+ async function runKrakenSelection(opts) {
32212
+ const okEntries = opts.candidates.filter(
32213
+ (c) => c.status === "ok"
32214
+ );
32215
+ if (okEntries.length === 0) {
32216
+ return needsMoreEvidence(
32217
+ "No usable candidate reports this turn (all malformed). Proceed with your own judgment.",
32218
+ "no usable candidates"
32219
+ );
32220
+ }
32221
+ if (okEntries.length === 1) {
32222
+ return {
32223
+ status: "selected",
32224
+ winnerIndex: okEntries[0].index,
32225
+ rationale: "Single usable candidate this turn \u2014 selected without a comparison call.",
32226
+ requiredChecks: [],
32227
+ degraded: false,
32228
+ verifier: null,
32229
+ judgedBy: "deterministic"
32230
+ };
32231
+ }
32232
+ const system = VERIFIER_SYSTEM_PROMPT;
32233
+ const user = buildSelectionPrompt(opts.task, opts.candidates);
32234
+ let raw;
32235
+ try {
32236
+ raw = await opts.callModel({ system, user, identity: opts.identity });
32237
+ } catch (err) {
32238
+ return needsMoreEvidence(
32239
+ `Verifier call failed (${err instanceof Error ? err.message : String(err)}). Proceed with your own judgment.`,
32240
+ "verifier call failed"
32241
+ );
32242
+ }
32243
+ const parsed = parseSelectionVerdict(raw, opts.candidates);
32244
+ if (!parsed.ok) {
32245
+ return needsMoreEvidence(
32246
+ `Verifier response unusable (${parsed.error}). Proceed with your own judgment.`,
32247
+ `malformed verifier response: ${parsed.error}`
32248
+ );
32249
+ }
32250
+ return { ...parsed.verdict, verifier: opts.identity };
32251
+ }
32252
+ var VERIFIER_SYSTEM_PROMPT, VERDICT_OPEN, VERDICT_CLOSE;
32253
+ var init_verifier = __esm({
32254
+ "src/cli/kraken/verifier.ts"() {
32255
+ "use strict";
32256
+ VERIFIER_SYSTEM_PROMPT = [
32257
+ "You are the Kraken selection verifier. You compare independent research",
32258
+ "reports (candidates) about ONE task and decide which hypothesis is best",
32259
+ "supported by OBSERVED EVIDENCE. You do not explore anything yourself \u2014",
32260
+ "you judge what the candidates actually observed.",
32261
+ "",
32262
+ "RULES:",
32263
+ "- Evidence decides. A specific observation (file:line, command output,",
32264
+ " test result) outweighs any amount of confident wording.",
32265
+ "- An eloquent candidate with no observations must NOT beat a plainly",
32266
+ " worded candidate whose claims are grounded in evidence.",
32267
+ "- Degraded observations (timeouts, empty searches, unavailable backends)",
32268
+ " are NOT proof of absence \u2014 never treat them as confirmation that",
32269
+ " something does not exist or cannot happen.",
32270
+ "- A candidate CONTRADICTED by a concrete observation loses to one",
32271
+ " consistent with it. Weigh listed risks.",
32272
+ "- If the leading candidates are equally (un)supported, answer",
32273
+ " needs_more_evidence \u2014 do not guess.",
32274
+ "- requiredChecks: 0-5 concrete, runnable checks the implementation must",
32275
+ " pass to prove the winning hypothesis (test names, commands, behaviors).",
32276
+ "",
32277
+ "Answer with EXACTLY this block (valid JSON, no prose before/after):",
32278
+ "<selection-verdict>",
32279
+ "{",
32280
+ ' "status": "selected" | "needs_more_evidence",',
32281
+ ' "winnerIndex": <1-based index of the winning candidate, or null>,',
32282
+ ' "rationale": "2-4 sentences grounded in the evidence",',
32283
+ ' "requiredChecks": ["concrete check", "..."]',
32284
+ "}",
32285
+ "</selection-verdict>"
32286
+ ].join("\n");
32287
+ VERDICT_OPEN = "<selection-verdict>";
32288
+ VERDICT_CLOSE = "</selection-verdict>";
32289
+ }
32290
+ });
32291
+
32292
+ // src/cli/tools/krakenSelectTool.ts
32293
+ async function collectProviderText(stream, params) {
32294
+ let text = "";
32295
+ let usage;
32296
+ for await (const delta of stream(params)) {
32297
+ if (delta.kind === "text") text += delta.delta;
32298
+ else if (delta.kind === "usage") usage = delta.usage;
32299
+ else if (delta.kind === "error") throw new Error(delta.message);
32300
+ else if (delta.kind === "finish") break;
32301
+ }
32302
+ return { text, ...usage ? { usage } : {} };
32303
+ }
32304
+ function renderVerdict(verdict, candidateCount) {
32305
+ const header = verdict.status === "selected" && verdict.winnerIndex !== null ? `kraken_select: SELECTED candidate #${verdict.winnerIndex}` : "kraken_select: NEEDS MORE EVIDENCE \u2014 no candidate clearly wins";
32306
+ const lines = [header, `Rationale: ${verdict.rationale}`];
32307
+ if (verdict.requiredChecks.length > 0) {
32308
+ lines.push("Required checks (must pass before clean completion):");
32309
+ verdict.requiredChecks.forEach((c, i) => lines.push(` ${i + 1}. ${c}`));
32310
+ }
32311
+ if (verdict.degraded && verdict.fallbackReason) {
32312
+ lines.push(
32313
+ `NOTE (degraded): ${verdict.fallbackReason}. Proceed with your own judgment \u2014 the verifier could not decide.`
32314
+ );
32315
+ }
32316
+ lines.push(`Candidates compared: ${candidateCount}.`);
32317
+ return lines.join("\n");
32318
+ }
32319
+ function createKrakenSelectTool(deps) {
32320
+ const timeoutMs = deps.timeoutMs ?? 12e4;
32321
+ return {
32322
+ name: "kraken_select",
32323
+ description: "Compare the candidate research reports spawned this turn (task purpose=candidate) and select the hypothesis best supported by OBSERVED EVIDENCE. A dedicated verifier call (default: the current model) judges grounded vs unsupported claims; degraded observations are never treated as proof of absence. Returns the selected candidate, a rationale, and requiredChecks the implementation must pass. Call it ONCE per turn, after candidate tentacles finished and BEFORE implementing. If it reports needs_more_evidence, either spawn ONE more differentiated candidate or proceed with your own judgment.",
32324
+ permissions: ["read", "network"],
32325
+ timeoutMs: 3e5,
32326
+ inputSchema: KrakenSelectArgsSchema,
32327
+ execute: async (input, _ctx) => {
32328
+ try {
32329
+ const candidates = krakenCandidates();
32330
+ if (candidates.length === 0) {
32331
+ return typedOk({
32332
+ result: "kraken_select: no candidates registered this turn \u2014 nothing to compare. Proceed directly (single-path execution)."
32333
+ });
32334
+ }
32335
+ const parent = await deps.loadParentIdentity();
32336
+ if (!parent) {
32337
+ const verdict2 = {
32338
+ status: "needs_more_evidence",
32339
+ winnerIndex: null,
32340
+ rationale: "Verifier unavailable (no provider identity for this run). Proceed with your own judgment.",
32341
+ requiredChecks: [],
32342
+ degraded: true,
32343
+ fallbackReason: "parent identity unavailable",
32344
+ verifier: null,
32345
+ judgedBy: "deterministic"
32346
+ };
32347
+ setKrakenSelection(verdict2);
32348
+ return typedOk({ result: renderVerdict(verdict2, candidates.length) });
32349
+ }
32350
+ const identity = resolveKrakenVerifier(
32351
+ parent,
32352
+ deps.env ?? process.env,
32353
+ deps.loadVerifierOverride?.()
32354
+ );
32355
+ let judgingStartedAt = Date.now();
32356
+ let judgingUsage;
32357
+ let judgingLatencyMs = 0;
32358
+ const verdict = await runKrakenSelection({
32359
+ task: input.task ?? "",
32360
+ candidates,
32361
+ identity,
32362
+ callModel: async ({ system, user, identity: id }) => {
32363
+ const stream = await deps.loadStream(id.provider);
32364
+ if (!stream) {
32365
+ throw new Error(`no provider config for verifier "${id.provider}"`);
32366
+ }
32367
+ judgingStartedAt = Date.now();
32368
+ try {
32369
+ const { text: raw, usage } = await collectProviderText(stream, {
32370
+ messages: [
32371
+ { role: "system", content: system },
32372
+ { role: "user", content: user }
32373
+ ],
32374
+ model: id.model,
32375
+ provider: id.provider,
32376
+ tools: [],
32377
+ signal: AbortSignal.timeout(timeoutMs)
32378
+ });
32379
+ judgingUsage = usage;
32380
+ if (raw.trim().length === 0) throw new Error("empty verifier response");
32381
+ return raw;
32382
+ } finally {
32383
+ judgingLatencyMs = Date.now() - judgingStartedAt;
32384
+ }
32385
+ }
32386
+ });
32387
+ recordSelectionOutcome({
32388
+ latencyMs: judgingLatencyMs,
32389
+ ...judgingUsage ? { tokens: judgingUsage.totalTokens } : {},
32390
+ degraded: verdict.degraded,
32391
+ fallbackReason: verdict.fallbackReason
32392
+ });
32393
+ setKrakenSelection(verdict);
32394
+ return typedOk({ result: renderVerdict(verdict, candidates.length) });
32395
+ } catch (err) {
32396
+ return typedErr(
32397
+ `kraken_select: internal error \u2014 ${err instanceof Error ? err.message : String(err)}`
32398
+ );
32399
+ }
32400
+ }
32401
+ };
32402
+ }
32403
+ var KrakenSelectArgsSchema;
32404
+ var init_krakenSelectTool = __esm({
32405
+ "src/cli/tools/krakenSelectTool.ts"() {
32406
+ "use strict";
32407
+ init_zod();
32408
+ init_metrics2();
32409
+ init_toolTypes();
32410
+ init_verifier();
32411
+ init_candidateRegistry();
32412
+ KrakenSelectArgsSchema = external_exports.object({
32413
+ task: external_exports.string().optional().describe(
32414
+ "The user task being solved, in one or two sentences. Used to judge which candidate hypothesis best serves it. Omit to judge against the candidates alone."
32415
+ )
32416
+ });
31549
32417
  }
31550
32418
  });
31551
32419
 
@@ -32270,13 +33138,13 @@ async function withPlanStore(projectRoot, fn) {
32270
33138
  return out;
32271
33139
  });
32272
33140
  }
32273
- function nextPlanTaskId(store4) {
32274
- const maxExisting = store4.tasks.reduce((max, t) => {
33141
+ function nextPlanTaskId(store6) {
33142
+ const maxExisting = store6.tasks.reduce((max, t) => {
32275
33143
  const m = /^t(\d+)$/.exec(t.id);
32276
33144
  return m ? Math.max(max, parseInt(m[1], 10)) : max;
32277
33145
  }, 0);
32278
- store4.counter = Math.max(store4.counter, maxExisting) + 1;
32279
- return `t${store4.counter}`;
33146
+ store6.counter = Math.max(store6.counter, maxExisting) + 1;
33147
+ return `t${store6.counter}`;
32280
33148
  }
32281
33149
  function writePlanTaskArtifact(rootDir, task) {
32282
33150
  const path56 = join15(rootDir, "plan-tasks", `${task.id}.md`);
@@ -32456,14 +33324,14 @@ function createPlanTaskTools(opts) {
32456
33324
  inputSchema: CreateSchema,
32457
33325
  execute: async (input) => {
32458
33326
  try {
32459
- const res = await withPlanStore(projectRoot, (store4) => {
32460
- if (store4.tasks.length >= PLAN_MAX_TASKS) {
33327
+ const res = await withPlanStore(projectRoot, (store6) => {
33328
+ if (store6.tasks.length >= PLAN_MAX_TASKS) {
32461
33329
  return typedErr(
32462
- `PLAN_TOO_MANY_TASKS: plan.json already holds ${store4.tasks.length} tasks (max ${PLAN_MAX_TASKS}).`
33330
+ `PLAN_TOO_MANY_TASKS: plan.json already holds ${store6.tasks.length} tasks (max ${PLAN_MAX_TASKS}).`
32463
33331
  );
32464
33332
  }
32465
33333
  const now = (/* @__PURE__ */ new Date()).toISOString();
32466
- const id = nextPlanTaskId(store4);
33334
+ const id = nextPlanTaskId(store6);
32467
33335
  const task = {
32468
33336
  id,
32469
33337
  title: input.title.trim().slice(0, PLAN_TITLE_MAX),
@@ -32477,8 +33345,8 @@ function createPlanTaskTools(opts) {
32477
33345
  createdAt: now,
32478
33346
  updatedAt: now
32479
33347
  };
32480
- store4.tasks.push(task);
32481
- writePlanTaskArtifact(store4.rootDir, task);
33348
+ store6.tasks.push(task);
33349
+ writePlanTaskArtifact(store6.rootDir, task);
32482
33350
  return typedOk({ id, task });
32483
33351
  });
32484
33352
  if (res.ok) {
@@ -32502,8 +33370,8 @@ function createPlanTaskTools(opts) {
32502
33370
  inputSchema: UpdateSchema,
32503
33371
  execute: async (input) => {
32504
33372
  try {
32505
- const res = await withPlanStore(projectRoot, (store4) => {
32506
- const task = store4.tasks.find((t) => t.id === input.id);
33373
+ const res = await withPlanStore(projectRoot, (store6) => {
33374
+ const task = store6.tasks.find((t) => t.id === input.id);
32507
33375
  if (!task) {
32508
33376
  return typedErr(
32509
33377
  `PLAN_TASK_NOT_FOUND: no task with id "${input.id}" in .zelari/plan.json (call task_list for current ids).`
@@ -32526,7 +33394,7 @@ ${input.appendNote}` : input.appendNote;
32526
33394
  task.notes = merged.slice(-PLAN_NOTES_MAX);
32527
33395
  }
32528
33396
  task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
32529
- writePlanTaskArtifact(store4.rootDir, task);
33397
+ writePlanTaskArtifact(store6.rootDir, task);
32530
33398
  return typedOk({ task });
32531
33399
  });
32532
33400
  if (res.ok) {
@@ -32551,21 +33419,21 @@ ${input.appendNote}` : input.appendNote;
32551
33419
  execute: async (input) => {
32552
33420
  try {
32553
33421
  let allPayloads = [];
32554
- const res = await withPlanStore(projectRoot, (store4) => {
32555
- allPayloads = store4.tasks.map(toTaskPayload);
32556
- const filtered = store4.tasks.filter(
33422
+ const res = await withPlanStore(projectRoot, (store6) => {
33423
+ allPayloads = store6.tasks.map(toTaskPayload);
33424
+ const filtered = store6.tasks.filter(
32557
33425
  (t) => (input.status === void 0 || t.status === input.status) && (input.phaseId === void 0 || t.phaseId === input.phaseId)
32558
33426
  );
32559
- const done = store4.tasks.filter(
33427
+ const done = store6.tasks.filter(
32560
33428
  (t) => t.status === "completed" || t.status === "cancelled"
32561
33429
  ).length;
32562
33430
  const formatted = filtered.length === 0 ? "(no matching workspace tasks)" : filtered.map(taskSummaryLine).join("\n");
32563
33431
  return typedOk({
32564
33432
  tasks: filtered,
32565
- total: store4.tasks.length,
33433
+ total: store6.tasks.length,
32566
33434
  done,
32567
33435
  formatted: `${formatted}
32568
- (done/total: ${done}/${store4.tasks.length})`
33436
+ (done/total: ${done}/${store6.tasks.length})`
32569
33437
  });
32570
33438
  });
32571
33439
  if (res.ok) {
@@ -35695,7 +36563,7 @@ import path31 from "node:path";
35695
36563
  function trustStorePath() {
35696
36564
  return _overrideStorePath ?? path31.join(homedir8(), ".zelari-code", "trust.json");
35697
36565
  }
35698
- function normalize(p3) {
36566
+ function normalize3(p3) {
35699
36567
  const resolved = path31.resolve(p3);
35700
36568
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
35701
36569
  }
@@ -35709,11 +36577,11 @@ function readStore3() {
35709
36577
  return DEFAULT_STORE;
35710
36578
  }
35711
36579
  }
35712
- function writeStore3(store4) {
36580
+ function writeStore3(store6) {
35713
36581
  const p3 = trustStorePath();
35714
36582
  try {
35715
36583
  mkdirSync14(path31.dirname(p3), { recursive: true });
35716
- writeFileSync16(p3, JSON.stringify(store4, null, 2), "utf8");
36584
+ writeFileSync16(p3, JSON.stringify(store6, null, 2), "utf8");
35717
36585
  } catch (err) {
35718
36586
  throw new Error(
35719
36587
  `failed to persist trust store ${p3}: ${err instanceof Error ? err.message : String(err)}`
@@ -35732,26 +36600,26 @@ function isFolderTrusted(folderPath) {
35732
36600
  const env = envTrustedFolder();
35733
36601
  if (env === "all") return true;
35734
36602
  if (env === "none") return false;
35735
- if (env) return normalize(env) === normalize(folderPath);
35736
- const target = normalize(folderPath);
35737
- return readStore3().folders.some((f) => normalize(f.path) === target);
36603
+ if (env) return normalize3(env) === normalize3(folderPath);
36604
+ const target = normalize3(folderPath);
36605
+ return readStore3().folders.some((f) => normalize3(f.path) === target);
35738
36606
  }
35739
36607
  function trustFolder(folderPath) {
35740
- const store4 = readStore3();
36608
+ const store6 = readStore3();
35741
36609
  const normalized = path31.resolve(folderPath);
35742
- if (!store4.folders.some((f) => normalize(f.path) === normalize(normalized))) {
35743
- store4.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
35744
- writeStore3(store4);
36610
+ if (!store6.folders.some((f) => normalize3(f.path) === normalize3(normalized))) {
36611
+ store6.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
36612
+ writeStore3(store6);
35745
36613
  }
35746
36614
  return { ok: true, path: normalized };
35747
36615
  }
35748
36616
  function untrustFolder(folderPath) {
35749
- const store4 = readStore3();
35750
- const target = normalize(folderPath);
35751
- const before = store4.folders.length;
35752
- store4.folders = store4.folders.filter((f) => normalize(f.path) !== target);
35753
- if (store4.folders.length === before) return { ok: true, removed: false };
35754
- writeStore3(store4);
36617
+ const store6 = readStore3();
36618
+ const target = normalize3(folderPath);
36619
+ const before = store6.folders.length;
36620
+ store6.folders = store6.folders.filter((f) => normalize3(f.path) !== target);
36621
+ if (store6.folders.length === before) return { ok: true, removed: false };
36622
+ writeStore3(store6);
35755
36623
  return { ok: true, removed: true };
35756
36624
  }
35757
36625
  function listTrustedFolders() {
@@ -35906,19 +36774,19 @@ function applyTruncation(result, toolName) {
35906
36774
  function evictOldest() {
35907
36775
  let oldestKey = null;
35908
36776
  let oldestTs = Infinity;
35909
- for (const [key, entry] of store2) {
36777
+ for (const [key, entry] of store4) {
35910
36778
  if (entry.ts < oldestTs) {
35911
36779
  oldestTs = entry.ts;
35912
36780
  oldestKey = key;
35913
36781
  }
35914
36782
  }
35915
- if (oldestKey) store2.delete(oldestKey);
36783
+ if (oldestKey) store4.delete(oldestKey);
35916
36784
  }
35917
36785
  function cacheGet(key, now) {
35918
- const entry = store2.get(key);
36786
+ const entry = store4.get(key);
35919
36787
  if (!entry) return null;
35920
36788
  if (entry.expiresAt !== void 0 && entry.expiresAt <= now) {
35921
- store2.delete(key);
36789
+ store4.delete(key);
35922
36790
  return null;
35923
36791
  }
35924
36792
  return cloneResult(entry.result) ?? entry.result;
@@ -35928,8 +36796,8 @@ function cachePut(key, result, now, ttlMs) {
35928
36796
  if (resultBytes(result) > TOOL_CACHE_MAX_BYTES) return;
35929
36797
  const cloned = cloneResult(result);
35930
36798
  if (!cloned) return;
35931
- if (store2.size >= TOOL_CACHE_MAX_ENTRIES && !store2.has(key)) evictOldest();
35932
- store2.set(key, {
36799
+ if (store4.size >= TOOL_CACHE_MAX_ENTRIES && !store4.has(key)) evictOldest();
36800
+ store4.set(key, {
35933
36801
  result: cloned,
35934
36802
  ts: now,
35935
36803
  ...ttlMs !== void 0 ? { expiresAt: now + ttlMs } : {}
@@ -35973,7 +36841,7 @@ function withResultCache(tool, options = {}) {
35973
36841
  }
35974
36842
  };
35975
36843
  }
35976
- var TOOL_CACHE_MAX_ENTRIES, TOOL_CACHE_MAX_BYTES, TOOL_CACHE_DEFAULT_TTL_MS, store2;
36844
+ var TOOL_CACHE_MAX_ENTRIES, TOOL_CACHE_MAX_BYTES, TOOL_CACHE_DEFAULT_TTL_MS, store4;
35977
36845
  var init_toolResultCache = __esm({
35978
36846
  "src/cli/toolResultCache.ts"() {
35979
36847
  "use strict";
@@ -35981,7 +36849,7 @@ var init_toolResultCache = __esm({
35981
36849
  TOOL_CACHE_MAX_ENTRIES = 200;
35982
36850
  TOOL_CACHE_MAX_BYTES = 256 * 1024;
35983
36851
  TOOL_CACHE_DEFAULT_TTL_MS = 5 * 60 * 1e3;
35984
- store2 = /* @__PURE__ */ new Map();
36852
+ store4 = /* @__PURE__ */ new Map();
35985
36853
  }
35986
36854
  });
35987
36855
 
@@ -36252,11 +37120,20 @@ function createBuiltinToolRegistry(options = {}) {
36252
37120
  });
36253
37121
  }
36254
37122
  }
36255
- const enableTask = options.enableTask !== false && !readOnly && !verifyMode && profile === "full" && !options.planMode;
37123
+ const enableTask = options.enableTask !== false && options.readOnly !== true && !verifyMode && profile === "full" && (!options.planMode || options.planExploreTask !== false);
36256
37124
  if (enableTask) {
36257
- const taskTool = createTaskTool({
36258
- createSubAgentContext: createKrakenSubAgentContextFactory({ root, audit, sessionId: sessionId2 })
36259
- });
37125
+ const taskTool = createTaskTool(
37126
+ {
37127
+ createSubAgentContext: createKrakenSubAgentContextFactory({
37128
+ root,
37129
+ audit,
37130
+ sessionId: sessionId2,
37131
+ ...options.subAgentProvider ? { provider: options.subAgentProvider } : {},
37132
+ ...options.subAgentModel ? { model: options.subAgentModel } : {}
37133
+ })
37134
+ },
37135
+ options.planMode === true ? { allowedAgents: ["explore"] } : void 0
37136
+ );
36260
37137
  registry4.register(withPerm(taskTool));
36261
37138
  tools.push({
36262
37139
  name: taskTool.name,
@@ -36264,6 +37141,31 @@ function createBuiltinToolRegistry(options = {}) {
36264
37141
  permissions: taskTool.permissions ?? []
36265
37142
  });
36266
37143
  }
37144
+ if (options.krakenSelect === true && enableTask) {
37145
+ const selectTool = createKrakenSelectTool({
37146
+ loadParentIdentity: async () => {
37147
+ const cfg = options.subAgentProvider ? await providerConfigFor(options.subAgentProvider) : await providerFromEnv();
37148
+ if (!cfg) return null;
37149
+ return {
37150
+ provider: cfg.providerId,
37151
+ model: options.subAgentModel || cfg.model
37152
+ };
37153
+ },
37154
+ loadStream: async (provider) => {
37155
+ const cfg = await providerConfigFor(provider);
37156
+ return cfg ? buildProviderStream(cfg) : null;
37157
+ },
37158
+ // Fase 9 (ADR-0020): persisted verifier override (provider.json
37159
+ // `krakenVerifier`). undefined = inherit the EXACT parent model.
37160
+ loadVerifierOverride: () => getKrakenVerifierOverride()
37161
+ });
37162
+ registry4.register(withPerm(selectTool));
37163
+ tools.push({
37164
+ name: selectTool.name,
37165
+ description: selectTool.description,
37166
+ permissions: selectTool.permissions ?? []
37167
+ });
37168
+ }
36267
37169
  if (process.env.ZELARI_LSP !== "0" && options.lspProvider !== null) {
36268
37170
  const lspTools = options.lspProvider ? createLspTools(options.lspProvider, root) : createLspTools(getSharedLspManager(root), root);
36269
37171
  for (const t of lspTools) {
@@ -36546,6 +37448,7 @@ var init_toolRegistry = __esm({
36546
37448
  init_auditLogger();
36547
37449
  init_engine();
36548
37450
  init_taskTool();
37451
+ init_krakenSelectTool();
36549
37452
  init_askUser();
36550
37453
  init_skillTool();
36551
37454
  init_todoTools();
@@ -36562,6 +37465,7 @@ var init_toolRegistry = __esm({
36562
37465
  init_worldModel();
36563
37466
  init_openai_compatible();
36564
37467
  init_resolveStream();
37468
+ init_providerConfig();
36565
37469
  init_toolPermissions();
36566
37470
  init_lifecycleHooks();
36567
37471
  init_toolResultCache();
@@ -36626,10 +37530,10 @@ function isStateEnabled(env = process.env) {
36626
37530
  }
36627
37531
  async function getStateStore(projectRoot, env = process.env) {
36628
37532
  if (!isStateEnabled(env)) return new NoopDurableStateStore();
36629
- const store4 = new FileDurableStateStore();
37533
+ const store6 = new FileDurableStateStore();
36630
37534
  try {
36631
- await store4.init(projectRoot);
36632
- return store4;
37535
+ await store6.init(projectRoot);
37536
+ return store6;
36633
37537
  } catch {
36634
37538
  return new NoopDurableStateStore();
36635
37539
  }
@@ -37278,24 +38182,24 @@ var init_historyCompaction = __esm({
37278
38182
 
37279
38183
  // src/cli/budget/requestSnapshotStore.ts
37280
38184
  function recordRequestSnapshot(sessionId2, snapshot) {
37281
- store3.set(sessionId2, { snapshot });
38185
+ store5.set(sessionId2, { snapshot });
37282
38186
  }
37283
38187
  function recordRequestUsage(sessionId2, usage) {
37284
- const entry = store3.get(sessionId2);
38188
+ const entry = store5.get(sessionId2);
37285
38189
  if (!entry) return;
37286
38190
  entry.usage = usage;
37287
38191
  }
37288
38192
  function getRequestSnapshotWithUsage(sessionId2) {
37289
- return store3.get(sessionId2) ?? null;
38193
+ return store5.get(sessionId2) ?? null;
37290
38194
  }
37291
38195
  function clearAllRequestSnapshots() {
37292
- store3.clear();
38196
+ store5.clear();
37293
38197
  }
37294
- var store3;
38198
+ var store5;
37295
38199
  var init_requestSnapshotStore = __esm({
37296
38200
  "src/cli/budget/requestSnapshotStore.ts"() {
37297
38201
  "use strict";
37298
- store3 = /* @__PURE__ */ new Map();
38202
+ store5 = /* @__PURE__ */ new Map();
37299
38203
  }
37300
38204
  });
37301
38205
 
@@ -38442,8 +39346,8 @@ async function loadDurableContext(projectRoot, opts) {
38442
39346
  return cache2.text;
38443
39347
  }
38444
39348
  try {
38445
- const store4 = await getStateStore(projectRoot, env);
38446
- const text = await store4.materializeContext(void 0, opts?.maxChars);
39349
+ const store6 = await getStateStore(projectRoot, env);
39350
+ const text = await store6.materializeContext(void 0, opts?.maxChars);
38447
39351
  cache2 = { text: text || "", at: now, projectRoot };
38448
39352
  return cache2.text;
38449
39353
  } catch {
@@ -41251,7 +42155,7 @@ __export(commitHelpers_exports, {
41251
42155
  });
41252
42156
  async function tryStateCommit(args) {
41253
42157
  try {
41254
- const store4 = args.store ?? await getStateStore(args.projectRoot, args.env);
42158
+ const store6 = args.store ?? await getStateStore(args.projectRoot, args.env);
41255
42159
  let workspaceCheckpointId = args.workspaceCheckpointId;
41256
42160
  if (!workspaceCheckpointId && args.withCheckpoint && (args.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
41257
42161
  const cp = await createCheckpoint(
@@ -41260,7 +42164,7 @@ async function tryStateCommit(args) {
41260
42164
  );
41261
42165
  if (cp.ok) workspaceCheckpointId = cp.value.id;
41262
42166
  }
41263
- const meta3 = await store4.commit({
42167
+ const meta3 = await store6.commit({
41264
42168
  mode: args.mode,
41265
42169
  label: args.label,
41266
42170
  layer: args.layer,
@@ -46010,6 +46914,9 @@ function parseSetConfigFlags(argv) {
46010
46914
  let endpoint;
46011
46915
  let thinking;
46012
46916
  let endpointClear = false;
46917
+ let verifierProvider;
46918
+ let verifierModel;
46919
+ let verifierClear = false;
46013
46920
  for (let i = 0; i < argv.length; i++) {
46014
46921
  const arg = argv[i];
46015
46922
  if (arg === "--provider") {
@@ -46026,12 +46933,20 @@ function parseSetConfigFlags(argv) {
46026
46933
  i++;
46027
46934
  } else if (arg === "--endpoint-clear") {
46028
46935
  endpointClear = true;
46936
+ } else if (arg === "--verifier-provider") {
46937
+ verifierProvider = argv[i + 1];
46938
+ i++;
46939
+ } else if (arg === "--verifier-model") {
46940
+ verifierModel = argv[i + 1];
46941
+ i++;
46942
+ } else if (arg === "--verifier-clear") {
46943
+ verifierClear = true;
46029
46944
  }
46030
46945
  }
46031
- if (!provider && !model && !endpoint && !endpointClear && !thinking) {
46946
+ if (!provider && !model && !endpoint && !endpointClear && !thinking && !verifierProvider && !verifierModel && !verifierClear) {
46032
46947
  return {
46033
46948
  request: null,
46034
- error: "--set-config requires --provider, --model, --endpoint, --thinking, and/or --endpoint-clear"
46949
+ error: "--set-config requires --provider, --model, --endpoint, --thinking, --verifier-provider + --verifier-model, --verifier-clear, and/or --endpoint-clear"
46035
46950
  };
46036
46951
  }
46037
46952
  if (provider !== void 0 && provider.trim().length === 0) {
@@ -46043,6 +46958,18 @@ function parseSetConfigFlags(argv) {
46043
46958
  if (endpoint !== void 0 && endpoint.trim().length === 0) {
46044
46959
  return { request: null, error: "--endpoint cannot be empty" };
46045
46960
  }
46961
+ if (verifierClear && (verifierProvider || verifierModel)) {
46962
+ return { request: null, error: "--verifier-clear conflicts with --verifier-provider/--verifier-model" };
46963
+ }
46964
+ if (verifierProvider !== void 0 && verifierModel === void 0 || verifierProvider === void 0 && verifierModel !== void 0) {
46965
+ return { request: null, error: "--verifier-provider and --verifier-model must be used together" };
46966
+ }
46967
+ if (verifierProvider !== void 0 && verifierProvider.trim().length === 0) {
46968
+ return { request: null, error: "--verifier-provider cannot be empty" };
46969
+ }
46970
+ if (verifierModel !== void 0 && verifierModel.trim().length === 0) {
46971
+ return { request: null, error: "--verifier-model cannot be empty" };
46972
+ }
46046
46973
  if (endpoint && endpointClear) {
46047
46974
  return { request: null, error: "--endpoint and --endpoint-clear conflict" };
46048
46975
  }
@@ -46055,7 +46982,10 @@ function parseSetConfigFlags(argv) {
46055
46982
  model: model?.trim(),
46056
46983
  endpoint: endpoint?.trim(),
46057
46984
  endpointClear: endpointClear || void 0,
46058
- thinking: thinking?.trim().toLowerCase()
46985
+ thinking: thinking?.trim().toLowerCase(),
46986
+ verifierProvider: verifierProvider?.trim(),
46987
+ verifierModel: verifierModel?.trim(),
46988
+ verifierClear: verifierClear || void 0
46059
46989
  }
46060
46990
  };
46061
46991
  }
@@ -46144,6 +47074,7 @@ function buildDesktopConfigSnapshot() {
46144
47074
  activeProviderId: config2.activeProviderId,
46145
47075
  modelByProvider: { ...config2.modelByProvider },
46146
47076
  providers,
47077
+ krakenVerifier: config2.krakenVerifier ?? null,
46147
47078
  cliVersion: getCurrentVersion(),
46148
47079
  configPaths: {
46149
47080
  provider: getProviderConfigPath(),
@@ -46182,11 +47113,25 @@ function applySetConfig(req) {
46182
47113
  if (req.thinking) {
46183
47114
  setThinkingForProvider(targetProvider, parseThinkingSpec(req.thinking));
46184
47115
  }
47116
+ if (req.verifierClear) {
47117
+ clearKrakenVerifier();
47118
+ }
47119
+ if (req.verifierProvider && req.verifierModel) {
47120
+ const existsV = PROVIDERS.some((p3) => p3.id === req.verifierProvider);
47121
+ if (!existsV) {
47122
+ return {
47123
+ ok: false,
47124
+ error: `unknown verifier provider '${req.verifierProvider}'. Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`
47125
+ };
47126
+ }
47127
+ setKrakenVerifier(req.verifierProvider, req.verifierModel);
47128
+ }
46185
47129
  const after = getProviderConfig();
47130
+ const vf = after.krakenVerifier;
46186
47131
  const ep = getCustomEndpoint(after.activeProviderId);
46187
47132
  return {
46188
47133
  ok: true,
46189
- message: `activeProvider=${after.activeProviderId} model=${after.modelByProvider[after.activeProviderId]}` + (ep ? ` endpoint=${ep}` : "")
47134
+ message: `activeProvider=${after.activeProviderId} model=${after.modelByProvider[after.activeProviderId]}` + (ep ? ` endpoint=${ep}` : "") + (vf ? ` verifier=${vf.provider}/${vf.model}` : " verifier=inherit")
46190
47135
  };
46191
47136
  } catch (err) {
46192
47137
  return {
@@ -51290,7 +52235,180 @@ init_shellResolver();
51290
52235
  init_keyStore();
51291
52236
  init_providerConfig();
51292
52237
  init_toolRegistry();
52238
+
52239
+ // src/cli/kraken/turnRuntime.ts
52240
+ init_events2();
52241
+ var WRITE_TOOLS3 = /* @__PURE__ */ new Set([
52242
+ "write_file",
52243
+ "edit_file",
52244
+ "apply_diff"
52245
+ ]);
52246
+ var KrakenTurnRuntime = class {
52247
+ mode;
52248
+ sessionId;
52249
+ onProgress;
52250
+ now;
52251
+ loadCheckTotal;
52252
+ loadChecksPassed;
52253
+ phase = "understanding";
52254
+ phaseEnteredAt;
52255
+ tentacles = 0;
52256
+ exploreTentacles = 0;
52257
+ verifyTentacles = 0;
52258
+ writes = 0;
52259
+ inFlightTentacles = 0;
52260
+ /** Required checks registered via kraken_select (Fase 6). */
52261
+ checkTotal;
52262
+ /** Required checks explicitly PASSED (Fase 7; undefined until first report). */
52263
+ checksPassed;
52264
+ /** toolCallId → toolName (end events omit the name). */
52265
+ pendingTools = /* @__PURE__ */ new Map();
52266
+ /** toolCallIds of VERIFY tentacles (Fase 7 refresh trigger). */
52267
+ pendingVerifyIds = /* @__PURE__ */ new Set();
52268
+ started = false;
52269
+ constructor(opts) {
52270
+ this.mode = opts.mode;
52271
+ this.sessionId = opts.sessionId;
52272
+ this.onProgress = opts.onProgress;
52273
+ this.now = opts.now ?? Date.now;
52274
+ this.loadCheckTotal = opts.loadCheckTotal;
52275
+ this.loadChecksPassed = opts.loadChecksPassed;
52276
+ this.phaseEnteredAt = this.now();
52277
+ }
52278
+ /** Start the turn: emits the initial `understanding` event. */
52279
+ beginTurn() {
52280
+ this.started = true;
52281
+ this.emit();
52282
+ }
52283
+ /**
52284
+ * Reset the phase machine for a recovery pass of the SAME turn (headless
52285
+ * BUILD write-retry). Counters are kept — they describe the whole turn.
52286
+ *
52287
+ * Fase 8 (ADR-0020): `beginPass(true)` marks an automatic REPAIR pass
52288
+ * (completion gate) and projects `repairing` instead of restarting at
52289
+ * `understanding`, so the UI shows the fix loop rather than a fresh turn.
52290
+ */
52291
+ beginPass(repair = false) {
52292
+ this.transition(repair ? "repairing" : "understanding");
52293
+ }
52294
+ /**
52295
+ * Terminal transition. `completed` is only projected on a clean finish —
52296
+ * cancellations and errors are already carried by the `agent_end` event.
52297
+ */
52298
+ finish(reason) {
52299
+ if (reason === "completed") this.transition("completed");
52300
+ }
52301
+ /** Feed one parent-turn BrainEvent. Never throws. */
52302
+ observe(event) {
52303
+ try {
52304
+ if (event.type === "tool_execution_start") {
52305
+ if (event.toolCallId && event.toolName) {
52306
+ this.pendingTools.set(event.toolCallId, event.toolName);
52307
+ }
52308
+ if (event.toolName === "kraken_select") {
52309
+ this.transition("selecting");
52310
+ return;
52311
+ }
52312
+ if (event.toolName === "task") {
52313
+ this.tentacles++;
52314
+ this.inFlightTentacles++;
52315
+ const agent = typeof event.args?.agent === "string" ? event.args.agent : "explore";
52316
+ if (agent === "verify") {
52317
+ this.verifyTentacles++;
52318
+ if (event.toolCallId) this.pendingVerifyIds.add(event.toolCallId);
52319
+ } else this.exploreTentacles++;
52320
+ this.transition(agent === "verify" ? "verifying" : "exploring");
52321
+ }
52322
+ return;
52323
+ }
52324
+ if (event.type === "tool_execution_end") {
52325
+ const name = this.pendingTools.get(event.toolCallId);
52326
+ this.pendingTools.delete(event.toolCallId);
52327
+ if (name === "task") {
52328
+ const wasVerify = event.toolCallId ? this.pendingVerifyIds.delete(event.toolCallId) : false;
52329
+ this.inFlightTentacles = Math.max(0, this.inFlightTentacles - 1);
52330
+ let checksChanged = false;
52331
+ if (wasVerify && this.loadChecksPassed) {
52332
+ const passed = this.loadChecksPassed();
52333
+ if (passed !== void 0 && passed !== this.checksPassed) {
52334
+ this.checksPassed = passed;
52335
+ checksChanged = true;
52336
+ }
52337
+ }
52338
+ if (this.inFlightTentacles === 0) {
52339
+ if (this.mode === "plan") this.transition("planning");
52340
+ else if (this.writes > 0) this.transition("implementing");
52341
+ else if (checksChanged) this.emit();
52342
+ } else if (checksChanged) {
52343
+ this.emit();
52344
+ }
52345
+ return;
52346
+ }
52347
+ if (name === "kraken_select") {
52348
+ const total = this.loadCheckTotal?.() ?? 0;
52349
+ if (total > 0 && total !== this.checkTotal) {
52350
+ this.checkTotal = total;
52351
+ this.emit();
52352
+ }
52353
+ return;
52354
+ }
52355
+ if (name && WRITE_TOOLS3.has(name) && !event.isError) {
52356
+ this.writes++;
52357
+ if (this.mode === "build") this.transition("implementing");
52358
+ }
52359
+ return;
52360
+ }
52361
+ } catch {
52362
+ }
52363
+ }
52364
+ /** Current progress payload (counters + phase). */
52365
+ snapshot() {
52366
+ return {
52367
+ phase: this.phase,
52368
+ mode: this.mode,
52369
+ tentacles: this.tentacles,
52370
+ exploreTentacles: this.exploreTentacles,
52371
+ verifyTentacles: this.verifyTentacles,
52372
+ writes: this.writes,
52373
+ phaseEnteredAt: this.phaseEnteredAt,
52374
+ ...this.checkTotal !== void 0 ? { checkTotal: this.checkTotal } : {},
52375
+ ...this.checksPassed !== void 0 ? { checksPassed: this.checksPassed } : {}
52376
+ };
52377
+ }
52378
+ transition(next) {
52379
+ if (this.phase === next) return;
52380
+ this.phase = next;
52381
+ this.phaseEnteredAt = this.now();
52382
+ this.emit();
52383
+ }
52384
+ emit() {
52385
+ if (!this.started) return;
52386
+ try {
52387
+ this.onProgress(
52388
+ createBrainEvent("kraken_progress", this.sessionId, {
52389
+ progress: this.snapshot()
52390
+ })
52391
+ );
52392
+ } catch {
52393
+ }
52394
+ }
52395
+ };
52396
+
52397
+ // src/cli/hooks/useChatTurn.ts
51293
52398
  init_taskTool();
52399
+ init_candidateRegistry();
52400
+ init_metrics2();
52401
+
52402
+ // src/cli/kraken/selectionPlaybook.ts
52403
+ init_skills2();
52404
+ init_candidateRegistry();
52405
+ function krakenSelectionPlaybook(include) {
52406
+ if (!include || !isKrakenSelectionEnabled()) return [];
52407
+ return [KRAKEN_SELECTION_PLAYBOOK_MODULE];
52408
+ }
52409
+
52410
+ // src/cli/hooks/useChatTurn.ts
52411
+ init_completionGate();
51294
52412
 
51295
52413
  // src/cli/hooks/permissionPicker.ts
51296
52414
  init_toolPermissions();
@@ -51645,6 +52763,8 @@ function useChatTurn(params) {
51645
52763
  const dispatchPrompt = useCallback2(
51646
52764
  async (userText, opts) => {
51647
52765
  resetTaskSpawnCount();
52766
+ resetKrakenCandidates();
52767
+ resetKrakenTurnMetrics();
51648
52768
  let envConfig;
51649
52769
  let harness;
51650
52770
  let historySeedLen = 0;
@@ -51728,10 +52848,30 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
51728
52848
  }) : void 0;
51729
52849
  const { registry: toolRegistry } = createBuiltinToolRegistry({
51730
52850
  planMode: workPhase === "plan",
52851
+ // Fase 1 (ADR-0020): anchor tentacles to THIS turn's resolved
52852
+ // provider/model so the TUI selection governs sub-agents too.
52853
+ ...envConfig ? {
52854
+ subAgentProvider: envConfig.providerId,
52855
+ subAgentModel: envConfig.model,
52856
+ // Fase 4 (ADR-0020): kraken_select rides the same alpha
52857
+ // flag as candidate spawning (default off = unchanged).
52858
+ krakenSelect: isKrakenSelectionEnabled()
52859
+ } : {},
51731
52860
  onAskUser,
51732
52861
  onPermissionAsk,
51733
52862
  permissionPolicy: defaultPermissionPolicy()
51734
52863
  });
52864
+ const progressRuntime = new KrakenTurnRuntime({
52865
+ mode: workPhase === "plan" ? "plan" : "build",
52866
+ sessionId: sessionId2,
52867
+ loadCheckTotal: () => krakenRequiredChecks().length,
52868
+ loadChecksPassed: () => krakenChecksPassed(),
52869
+ onProgress: (ev) => {
52870
+ if (writerRef.current) void writerRef.current.append(ev);
52871
+ if (sessionId2) ingestLiveEvent(sessionId2, ev);
52872
+ }
52873
+ });
52874
+ progressRuntime.beginTurn();
51735
52875
  const baseProviderStream = localCliProvider ?? buildProviderStream(envConfig);
51736
52876
  let providerStream;
51737
52877
  if (localCliProvider) {
@@ -51922,6 +53062,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
51922
53062
  customPromptModules: [
51923
53063
  KRAKEN_IDENTITY_MODULE,
51924
53064
  KRAKEN_LEAD_PLAYBOOK_MODULE,
53065
+ ...krakenSelectionPlaybook(true),
51925
53066
  languageModule
51926
53067
  ],
51927
53068
  agentSkillConfigs: []
@@ -51990,6 +53131,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
51990
53131
  harnessRef.current = harness2;
51991
53132
  setQueueCount(harness2.queueLength);
51992
53133
  let assistantContent = "";
53134
+ let krakenRepairEnqueued = false;
51993
53135
  let streamContent = "";
51994
53136
  const streamScrub = createStreamScrubber(16);
51995
53137
  const toolNameById = /* @__PURE__ */ new Map();
@@ -51998,6 +53140,29 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
51998
53140
  let realUsage = null;
51999
53141
  try {
52000
53142
  for await (const event of harness2.run()) {
53143
+ progressRuntime.observe(event);
53144
+ if (event.type === "agent_end") {
53145
+ let krakenSuppressFinish = false;
53146
+ if (event.reason === "completed" && !krakenRepairEnqueued && isKrakenSelectionEnabled() && workPhase === "build") {
53147
+ const krakenGate = evaluateKrakenCompletionGate("build");
53148
+ if (krakenGate.blocked) {
53149
+ krakenRepairEnqueued = true;
53150
+ markRepairTriggered();
53151
+ harness2.enqueue(buildKrakenRepairPrompt(krakenGate));
53152
+ appendSystem(
53153
+ setMessages,
53154
+ `[kraken] required checks unresolved (${krakenGate.passed}/${krakenGate.total} passed) \u2014 automatic repair pass`,
53155
+ Date.now()
53156
+ );
53157
+ progressRuntime.beginPass(true);
53158
+ krakenSuppressFinish = true;
53159
+ }
53160
+ }
53161
+ if (event.reason === "completed" && krakenRepairEnqueued && !evaluateKrakenCompletionGate("build").blocked) {
53162
+ markRepairSucceeded();
53163
+ }
53164
+ if (!krakenSuppressFinish) progressRuntime.finish(event.reason);
53165
+ }
52001
53166
  if (event.type === "message_end") {
52002
53167
  if (event.usage) realUsage = event.usage;
52003
53168
  if (event.usage) {
@@ -52170,6 +53335,14 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
52170
53335
  }
52171
53336
  }
52172
53337
  }
53338
+ const turnMetrics = collectKrakenTurnMetrics();
53339
+ if (turnMetrics) {
53340
+ const metricsEvent = createBrainEvent("kraken_metrics", sessionId2, {
53341
+ metrics: turnMetrics
53342
+ });
53343
+ if (writerRef.current) void writerRef.current.append(metricsEvent);
53344
+ if (sessionId2) ingestLiveEvent(sessionId2, metricsEvent);
53345
+ }
52173
53346
  turnSucceeded = true;
52174
53347
  } finally {
52175
53348
  flushStreaming();
@@ -54167,12 +55340,12 @@ init_fileStateStore();
54167
55340
  async function restoreDurableState(opts) {
54168
55341
  const restoreTree = opts.restoreTree !== false;
54169
55342
  try {
54170
- const store4 = opts.store ?? await getStateStore(opts.projectRoot);
55343
+ const store6 = opts.store ?? await getStateStore(opts.projectRoot);
54171
55344
  let meta3;
54172
55345
  if (opts.commitId) {
54173
- meta3 = await store4.setHead(opts.commitId);
55346
+ meta3 = await store6.setHead(opts.commitId);
54174
55347
  } else {
54175
- meta3 = await store4.head();
55348
+ meta3 = await store6.head();
54176
55349
  if (!meta3) {
54177
55350
  return {
54178
55351
  ok: false,
@@ -54225,8 +55398,8 @@ function ago2(ms) {
54225
55398
  return `${Math.round(s / 3600)}h ago`;
54226
55399
  }
54227
55400
  async function handleStateStatus(ctx) {
54228
- const store4 = await getStateStore(ctx.cwd);
54229
- const head = await store4.head();
55401
+ const store6 = await getStateStore(ctx.cwd);
55402
+ const head = await store6.head();
54230
55403
  if (!head) {
54231
55404
  appendSystem(
54232
55405
  ctx.setMessages,
@@ -54234,9 +55407,9 @@ async function handleStateStatus(ctx) {
54234
55407
  );
54235
55408
  return;
54236
55409
  }
54237
- const discoveries = await store4.loadDiscoveries(head.id);
55410
+ const discoveries = await store6.loadDiscoveries(head.id);
54238
55411
  const reusable = discoveries.filter((d) => d.reusable).length;
54239
- const recent = await store4.list(8);
55412
+ const recent = await store6.list(8);
54240
55413
  const lines = recent.map((c, i) => {
54241
55414
  const ver2 = c.verification.ran ? c.verification.ok ? "ok" : "fail" : "n/a";
54242
55415
  return ` ${i === 0 ? "\u2192" : " "} ${c.id} ${ago2(c.createdAt)} ${c.label} ver=${ver2}` + (c.layer ? ` [${c.layer}]` : "") + (c.stablePromptHash ? ` hash=${c.stablePromptHash.slice(0, 8)}` : "");
@@ -54255,9 +55428,9 @@ async function handleStateStatus(ctx) {
54255
55428
  );
54256
55429
  }
54257
55430
  async function handleStateCommit(ctx, label) {
54258
- const store4 = await getStateStore(ctx.cwd);
55431
+ const store6 = await getStateStore(ctx.cwd);
54259
55432
  try {
54260
- const meta3 = await store4.commit({
55433
+ const meta3 = await store6.commit({
54261
55434
  mode: "agent",
54262
55435
  label: label?.trim() || "manual state commit",
54263
55436
  layer: "manual",
@@ -54284,8 +55457,8 @@ async function handleStateCommit(ctx, label) {
54284
55457
  }
54285
55458
  }
54286
55459
  async function handleStateShow(ctx, id) {
54287
- const store4 = await getStateStore(ctx.cwd);
54288
- const meta3 = id ? await store4.get(id) : await store4.head();
55460
+ const store6 = await getStateStore(ctx.cwd);
55461
+ const meta3 = id ? await store6.get(id) : await store6.head();
54289
55462
  if (!meta3) {
54290
55463
  appendSystem(
54291
55464
  ctx.setMessages,
@@ -54293,7 +55466,7 @@ async function handleStateShow(ctx, id) {
54293
55466
  );
54294
55467
  return;
54295
55468
  }
54296
- const text = await store4.materializeContext(meta3.id, 6e3);
55469
+ const text = await store6.materializeContext(meta3.id, 6e3);
54297
55470
  appendSystem(ctx.setMessages, `[state] show ${meta3.id}
54298
55471
  ${text}`);
54299
55472
  }
@@ -55803,14 +56976,14 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
55803
56976
  }
55804
56977
  function handleCouncilFeedback(ctx, memberId, score, note) {
55805
56978
  try {
55806
- const store4 = new FeedbackStore();
55807
- const entry = store4.record({
56979
+ const store6 = new FeedbackStore();
56980
+ const entry = store6.record({
55808
56981
  memberId,
55809
56982
  score,
55810
56983
  ...note ? { note } : {},
55811
56984
  ...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
55812
56985
  });
55813
- const stats = store4.getStats(memberId);
56986
+ const stats = store6.getStats(memberId);
55814
56987
  appendSystem(
55815
56988
  ctx.setMessages,
55816
56989
  `[council-feedback] ${memberId} rated ${entry.score}/5 \u2014 running avg ${stats.avg.toFixed(2)} over ${stats.count} rating(s).`
@@ -57502,9 +58675,13 @@ function emitEvent(event) {
57502
58675
  // src/cli/runHeadless.ts
57503
58676
  init_harness();
57504
58677
  init_dist();
58678
+ init_events2();
57505
58679
  init_conversationContext();
57506
58680
  init_council();
57507
58681
  init_toolRegistry();
58682
+ init_candidateRegistry();
58683
+ init_metrics2();
58684
+ init_completionGate();
57508
58685
  init_claudeProvider();
57509
58686
  init_skills2();
57510
58687
  init_envNumber();
@@ -57861,8 +59038,18 @@ async function registerHeadlessMcp(toolRegistry, opts) {
57861
59038
  }
57862
59039
  async function runHeadlessSingle(opts, provider, model, providerStream) {
57863
59040
  const sessionId2 = crypto.randomUUID();
59041
+ resetKrakenCandidates();
59042
+ resetKrakenTurnMetrics();
57864
59043
  const { registry: toolRegistry } = createBuiltinToolRegistry({
57865
59044
  planMode: planModeFromOpts(opts),
59045
+ // Fase 1 (ADR-0020): anchor tentacles to the provider/model THIS run
59046
+ // resolved (--provider/--model opts or Desktop's selector), mirroring
59047
+ // what the kraken-graph path already does for its executor.
59048
+ subAgentProvider: provider,
59049
+ subAgentModel: model,
59050
+ // Fase 4 (ADR-0020): kraken_select on the parent registry for kraken
59051
+ // runs with the alpha selection flag on (default off = unchanged).
59052
+ krakenSelect: opts.mode === "kraken" && isKrakenSelectionEnabled(),
57866
59053
  // ADR-0018 3b: upgrade plan-task domain events to first-class NDJSON
57867
59054
  // BrainEvents. Rust envelopes every stdout line with runId/conversationId,
57868
59055
  // so task events ride the same multiplexed channel as the rest.
@@ -57971,6 +59158,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
57971
59158
  customPromptModules: [
57972
59159
  KRAKEN_IDENTITY_MODULE,
57973
59160
  KRAKEN_LEAD_PLAYBOOK_MODULE,
59161
+ ...krakenSelectionPlaybook(opts.mode === "kraken"),
57974
59162
  {
57975
59163
  type: "language-policy",
57976
59164
  title: "Response Language",
@@ -58035,6 +59223,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
58035
59223
  const scrub = createStreamScrubber2();
58036
59224
  try {
58037
59225
  for await (const event of harness.run()) {
59226
+ progressRuntime.observe(event);
58038
59227
  if (event.type === "message_start") {
58039
59228
  scrub.reset();
58040
59229
  }
@@ -58112,6 +59301,16 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
58112
59301
  messages: harness.getMessages()
58113
59302
  };
58114
59303
  }
59304
+ const progressRuntime = new KrakenTurnRuntime({
59305
+ mode: planModeFromOpts(opts) ? "plan" : "build",
59306
+ sessionId: sessionId2,
59307
+ loadCheckTotal: () => krakenRequiredChecks().length,
59308
+ loadChecksPassed: () => krakenChecksPassed(),
59309
+ onProgress: (ev) => {
59310
+ if (opts.output === "json") emitEvent(ev);
59311
+ }
59312
+ });
59313
+ progressRuntime.beginTurn();
58115
59314
  const initialMessages = [
58116
59315
  ...systemMessages,
58117
59316
  ...historySeed,
@@ -58148,6 +59347,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
58148
59347
  ...retryMessages,
58149
59348
  { role: "user", content: retryPrompt }
58150
59349
  ];
59350
+ progressRuntime.beginPass();
58151
59351
  const retry = await runSinglePass(withSystem, `${sessionId2}-write-retry`);
58152
59352
  pass = {
58153
59353
  ...retry,
@@ -58156,6 +59356,42 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
58156
59356
  emittedWrites: pass.emittedWrites + retry.emittedWrites
58157
59357
  };
58158
59358
  }
59359
+ if (pass.finalReason === "completed" && pass.exitCode === 0 && opts.mode === "kraken" && isKrakenSelectionEnabled() && !planModeFromOpts(opts)) {
59360
+ const gate = evaluateKrakenCompletionGate("build");
59361
+ if (gate.blocked) {
59362
+ const repairPrompt = buildKrakenRepairPrompt(gate);
59363
+ if (opts.output === "json") {
59364
+ emitEvent({
59365
+ type: "log",
59366
+ message: `[headless] Kraken BUILD: ${gate.failedChecks.length} failed / ${gate.unknownChecks.length} unknown required checks \u2014 forcing repair pass`
59367
+ });
59368
+ } else {
59369
+ process.stderr.write(
59370
+ "[zelari-code --headless] Kraken BUILD: required checks unresolved \u2014 forcing repair pass\n"
59371
+ );
59372
+ }
59373
+ const withSystem = [
59374
+ ...systemMessages,
59375
+ ...pass.messages.filter((m) => m.role !== "system"),
59376
+ { role: "user", content: repairPrompt }
59377
+ ];
59378
+ progressRuntime.beginPass(true);
59379
+ markRepairTriggered();
59380
+ const repair = await runSinglePass(withSystem, `${sessionId2}-check-repair`);
59381
+ pass = {
59382
+ ...repair,
59383
+ textBuffer: [...pass.textBuffer, ...repair.textBuffer],
59384
+ successfulWrites: pass.successfulWrites + repair.successfulWrites,
59385
+ emittedWrites: pass.emittedWrites + repair.emittedWrites
59386
+ };
59387
+ if (!evaluateKrakenCompletionGate("build").blocked) markRepairSucceeded();
59388
+ }
59389
+ }
59390
+ progressRuntime.finish(pass.finalReason);
59391
+ const turnMetrics = collectKrakenTurnMetrics();
59392
+ if (turnMetrics && opts.output === "json") {
59393
+ emitEvent(createBrainEvent("kraken_metrics", sessionId2, { metrics: turnMetrics }));
59394
+ }
58159
59395
  if (opts.output === "plain" && pass.textBuffer.length > 0) {
58160
59396
  process.stdout.write(pass.textBuffer.join(""));
58161
59397
  }