zelari-code 2.33.1 → 2.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cli/councilDispatcher.js +1 -0
  2. package/dist/cli/councilDispatcher.js.map +1 -1
  3. package/dist/cli/headless/liveTurnAbort.js +67 -0
  4. package/dist/cli/headless/liveTurnAbort.js.map +1 -0
  5. package/dist/cli/headless/runOneTurn.js +5 -1
  6. package/dist/cli/headless/runOneTurn.js.map +1 -1
  7. package/dist/cli/headless.js.map +1 -1
  8. package/dist/cli/hooks/useChatTurn.js +1 -0
  9. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  10. package/dist/cli/main.bundled.js +1777 -1228
  11. package/dist/cli/main.bundled.js.map +4 -4
  12. package/dist/cli/runHeadless.js +55 -7
  13. package/dist/cli/runHeadless.js.map +1 -1
  14. package/dist/cli/serve/askUserBridge.js +59 -0
  15. package/dist/cli/serve/askUserBridge.js.map +1 -0
  16. package/dist/cli/serve/harnessServer.js +14 -2
  17. package/dist/cli/serve/harnessServer.js.map +1 -1
  18. package/dist/cli/serve/permissionBridge.js +64 -8
  19. package/dist/cli/serve/permissionBridge.js.map +1 -1
  20. package/dist/cli/serve/sessionControl.js +5 -3
  21. package/dist/cli/serve/sessionControl.js.map +1 -1
  22. package/dist/cli/toolRegistry.js +32 -17
  23. package/dist/cli/toolRegistry.js.map +1 -1
  24. package/dist/cli/tools/krakenModel.js +18 -0
  25. package/dist/cli/tools/krakenModel.js.map +1 -1
  26. package/dist/cli/tools/taskTool.js +114 -9
  27. package/dist/cli/tools/taskTool.js.map +1 -1
  28. package/dist/cli/utils/doctor.js +24 -3
  29. package/dist/cli/utils/doctor.js.map +1 -1
  30. package/dist/cli/utils/fixPath.js +18 -14
  31. package/dist/cli/utils/fixPath.js.map +1 -1
  32. package/dist/cli/utils/streamScrub.js +5 -3
  33. package/dist/cli/utils/streamScrub.js.map +1 -1
  34. package/dist/cli/zelariMission.js +14 -0
  35. package/dist/cli/zelariMission.js.map +1 -1
  36. package/package.json +2 -2
@@ -28372,6 +28372,136 @@ var init_textLoopDetect = __esm({
28372
28372
  }
28373
28373
  });
28374
28374
 
28375
+ // packages/core/dist/agents/council/outputCleaning.js
28376
+ function extractBalancedJsonObject(s) {
28377
+ const start = s.indexOf("{");
28378
+ if (start < 0)
28379
+ return null;
28380
+ let depth = 0;
28381
+ let inString = false;
28382
+ let escape = false;
28383
+ for (let i = start; i < s.length; i++) {
28384
+ const ch = s[i];
28385
+ if (inString) {
28386
+ if (escape) {
28387
+ escape = false;
28388
+ continue;
28389
+ }
28390
+ if (ch === "\\") {
28391
+ escape = true;
28392
+ continue;
28393
+ }
28394
+ if (ch === '"')
28395
+ inString = false;
28396
+ continue;
28397
+ }
28398
+ if (ch === '"') {
28399
+ inString = true;
28400
+ continue;
28401
+ }
28402
+ if (ch === "{")
28403
+ depth++;
28404
+ else if (ch === "}") {
28405
+ depth--;
28406
+ if (depth === 0)
28407
+ return s.slice(start, i + 1);
28408
+ }
28409
+ }
28410
+ return null;
28411
+ }
28412
+ function parseClarificationRequest(text) {
28413
+ const start = text.indexOf(QUESTION_MARKER);
28414
+ if (start < 0)
28415
+ return null;
28416
+ const rest = text.slice(start + QUESTION_MARKER.length);
28417
+ const end = rest.indexOf(QUESTION_END_MARKER);
28418
+ const block = end >= 0 ? rest.slice(0, end) : rest;
28419
+ const cleaned = block.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
28420
+ const jsonText = extractBalancedJsonObject(cleaned) ?? (() => {
28421
+ const objStart = cleaned.indexOf("{");
28422
+ const objEnd = cleaned.lastIndexOf("}");
28423
+ return objStart >= 0 && objEnd > objStart ? cleaned.slice(objStart, objEnd + 1) : cleaned;
28424
+ })();
28425
+ try {
28426
+ const parsed = JSON.parse(jsonText);
28427
+ if (typeof parsed.question !== "string" || !parsed.question.trim())
28428
+ return null;
28429
+ return {
28430
+ question: parsed.question.trim(),
28431
+ choices: Array.isArray(parsed.choices) ? parsed.choices.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : void 0,
28432
+ context: typeof parsed.context === "string" ? parsed.context.trim() : void 0
28433
+ };
28434
+ } catch {
28435
+ return null;
28436
+ }
28437
+ }
28438
+ function hasInteractiveClarification(text) {
28439
+ const c = parseClarificationRequest(text);
28440
+ return !!(c && c.choices && c.choices.length >= 2);
28441
+ }
28442
+ function stripQuestionBlocks(text) {
28443
+ let out = "";
28444
+ let rest = text;
28445
+ while (true) {
28446
+ const start = rest.indexOf(QUESTION_MARKER);
28447
+ if (start < 0) {
28448
+ out += rest;
28449
+ break;
28450
+ }
28451
+ out += rest.slice(0, start);
28452
+ const afterMarker = rest.slice(start + QUESTION_MARKER.length);
28453
+ const trimmed = afterMarker.replace(/^\s+/, "");
28454
+ if (!trimmed.startsWith("{")) {
28455
+ out += QUESTION_MARKER;
28456
+ rest = afterMarker;
28457
+ continue;
28458
+ }
28459
+ const endIdx = afterMarker.indexOf(QUESTION_END_MARKER);
28460
+ if (endIdx >= 0) {
28461
+ rest = afterMarker.slice(endIdx + QUESTION_END_MARKER.length);
28462
+ continue;
28463
+ }
28464
+ const json3 = extractBalancedJsonObject(trimmed);
28465
+ if (json3) {
28466
+ const jsonAt = afterMarker.indexOf(json3);
28467
+ rest = afterMarker.slice(jsonAt + json3.length);
28468
+ continue;
28469
+ }
28470
+ break;
28471
+ }
28472
+ return out.replace(/\n{3,}/g, "\n\n").trim();
28473
+ }
28474
+ function parseThinking(text) {
28475
+ const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
28476
+ if (complete)
28477
+ return complete[1].trim();
28478
+ const open2 = text.match(/<think(?:ing)?>([\s\S]*)$/i);
28479
+ return open2 ? open2[1].trim() : "";
28480
+ }
28481
+ function cleanAgentContent(text, opts = {}) {
28482
+ const stripQuestion = opts.stripQuestion !== false;
28483
+ const stripThink = opts.stripThink !== false;
28484
+ let out = text;
28485
+ if (stripThink) {
28486
+ out = out.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "");
28487
+ }
28488
+ out = out.replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/gi, "").replace(/<\/?minimax:tool_call>/gi, "").replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, "").replace(/<\/?tool_call>/gi, "").replace(/<function_call>[\s\S]*?<\/function_call>/gi, "").replace(/<\/?function_call>/gi, "").replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "").replace(/<\/invoke>/gi, "").replace(/<parameter\b[^>]*>[\s\S]*?<\/parameter>/gi, "").replace(/<\/parameter>/gi, "").replace(/\]\s*<\]\s*minimax\s*\[>\s*\[?<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "").replace(/<minimax:tool_call>[\s\S]*$/gi, "").replace(/<tool_call>[\s\S]*$/gi, "").replace(/<function_call>[\s\S]*$/gi, "").replace(/<invoke\b[^>]*>[\s\S]*$/gi, "").replace(/\]\s*<\]\s*minimax\s*\[>[\s\S]*$/gi, "").replace(/^\s*\]\s*<\]\s*minimax\s*\[>.*$/gim, "").replace(/^\s*<\/?(?:tool_call|function_call|invoke|parameter|minimax:tool_call)\b[^>]*>\s*$/gim, "");
28489
+ if (stripQuestion) {
28490
+ out = stripQuestionBlocks(out);
28491
+ }
28492
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
28493
+ return scrubProprietaryLeak(out);
28494
+ }
28495
+ var QUESTION_MARKER, QUESTION_END_MARKER;
28496
+ var init_outputCleaning = __esm({
28497
+ "packages/core/dist/agents/council/outputCleaning.js"() {
28498
+ "use strict";
28499
+ init_secrecyPolicy();
28500
+ QUESTION_MARKER = "---QUESTION---";
28501
+ QUESTION_END_MARKER = "---END---";
28502
+ }
28503
+ });
28504
+
28375
28505
  // packages/core/dist/core/AgentHarness.js
28376
28506
  function gateAdvice(hardLimit) {
28377
28507
  return hardLimit ? "Finalize now with the evidence already collected; no further tool calls will run this turn." : "Prioritize verification/repair actions (test, typecheck, build, read failures) or finalize honestly.";
@@ -28668,6 +28798,7 @@ var init_AgentHarness = __esm({
28668
28798
  init_requestSnapshot();
28669
28799
  init_textLoopDetect();
28670
28800
  init_ObserverBus();
28801
+ init_outputCleaning();
28671
28802
  init_textLoopDetect();
28672
28803
  TOOL_CALL_TRUNCATED_RECOVERY_MARKER = "[harness] Previous tool call was truncated";
28673
28804
  TOOL_CALL_TRUNCATED_RECOVERY_USER = `${TOOL_CALL_TRUNCATED_RECOVERY_MARKER} by the provider before completion (finish_reason=tool_calls but no complete tool_call arrived). Retry with a shorter payload or split the work into smaller tool calls.`;
@@ -29562,7 +29693,7 @@ ${cached2}`
29562
29693
  }
29563
29694
  pendingNativeTools.length = 0;
29564
29695
  }
29565
- const clarificationPause = /---QUESTION---/.test(turnText) && /"choices"\s*:\s*\[/.test(turnText);
29696
+ const clarificationPause = hasInteractiveClarification(turnText);
29566
29697
  if (clarificationPause) {
29567
29698
  finishRef.value = "stop";
29568
29699
  finishRef.clarificationRequested = true;
@@ -30994,31 +31125,6 @@ You are the sole implementer this run. Advisors already analyzed \u2014 implemen
30994
31125
  }
30995
31126
  });
30996
31127
 
30997
- // packages/core/dist/council/modeBanners.js
30998
- function councilModeBanner(runMode, opts) {
30999
- if (runMode === "design-phase")
31000
- return DESIGN_PHASE_MODE_BANNER;
31001
- if (opts?.isImplementer === true)
31002
- return IMPLEMENTATION_IMPLEMENTER_BANNER;
31003
- if (opts?.isImplementer === false)
31004
- return IMPLEMENTATION_ADVISOR_BANNER;
31005
- return IMPLEMENTATION_MODE_BANNER;
31006
- }
31007
- var DESIGN_PHASE_MODE_BANNER, IMPLEMENTATION_MODE_BANNER, IMPLEMENTATION_IMPLEMENTER_BANNER, IMPLEMENTATION_ADVISOR_BANNER;
31008
- var init_modeBanners = __esm({
31009
- "packages/core/dist/council/modeBanners.js"() {
31010
- "use strict";
31011
- DESIGN_PHASE_MODE_BANNER = `COUNCIL RUN MODE: design-phase.
31012
- Workspace tool emissions described in your role prompt are MANDATORY in this run \u2014 prose alone does not count as a deliverable. Persist artifacts via the workspace tools listed in your AVAILABLE TOOLS section.`;
31013
- IMPLEMENTATION_MODE_BANNER = `COUNCIL RUN MODE: implementation.
31014
- Prefer write_file, edit_file, and bash for code changes. Design-phase mandatory workspace blocks in your role prompt are INACTIVE \u2014 .zelari/docs and draft plans are hypotheses, not product law. Ground work in the real source tree. Only call workspace tools when they add durable project value.`;
31015
- IMPLEMENTATION_IMPLEMENTER_BANNER = `COUNCIL RUN MODE: implementation \u2014 you are the IMPLEMENTER.
31016
- You are the only member that writes code this run. Implement the solution with write_file / edit_file and verify with bash. Reconcile specialists' analysis and Minosse's critique into working, verified changes. Design vault (.zelari/docs, draft plans) is HYPOTHESIS only \u2014 product truth is the source tree. Design-phase mandatory workspace blocks are INACTIVE.`;
31017
- IMPLEMENTATION_ADVISOR_BANNER = `COUNCIL RUN MODE: implementation \u2014 you are an ADVISOR.
31018
- Do NOT write or edit project files (not via write_file/edit_file, and not via bash) \u2014 Lucifero is the sole implementer. Inspect the real codebase (read_file, grep_content, list_files). Treat prior design prose and .zelari/docs as hypotheses. Flag contradictions with package.json / the tree. Design-phase mandatory workspace blocks are INACTIVE.`;
31019
- }
31020
- });
31021
-
31022
31128
  // packages/core/dist/council/runMode.js
31023
31129
  function resolveCouncilRunMode(input) {
31024
31130
  if (input.forceMode) {
@@ -31880,54 +31986,6 @@ var init_microGate = __esm({
31880
31986
  }
31881
31987
  });
31882
31988
 
31883
- // packages/core/dist/council/verification/completion.js
31884
- function isVerifyToolCheckSkipped() {
31885
- return process.env.ZELARI_VERIFY_SKIP_TOOL === "1";
31886
- }
31887
- function checkImplementationCompletion(emittedToolNames) {
31888
- if (isVerifyToolCheckSkipped()) {
31889
- return { ok: true, missing: [] };
31890
- }
31891
- let lastWriteIdx = -1;
31892
- for (let i = 0; i < emittedToolNames.length; i++) {
31893
- if (WRITE_TOOLS.has(emittedToolNames[i])) {
31894
- lastWriteIdx = i;
31895
- }
31896
- }
31897
- if (lastWriteIdx === -1) {
31898
- return { ok: true, missing: [] };
31899
- }
31900
- const afterWrite = emittedToolNames.slice(lastWriteIdx + 1);
31901
- if (afterWrite.some((t) => VERIFY_TOOLS.has(t))) {
31902
- return { ok: true, missing: [] };
31903
- }
31904
- return {
31905
- ok: false,
31906
- missing: ["grep_content"],
31907
- reason: "No grep_content or bash after last write_file/edit_file"
31908
- };
31909
- }
31910
- function resolveVerifyRetryTool(executableTools) {
31911
- if (!executableTools)
31912
- return "grep_content";
31913
- if (executableTools.has("grep_content"))
31914
- return "grep_content";
31915
- if (executableTools.has("bash"))
31916
- return "bash";
31917
- return null;
31918
- }
31919
- function buildImplementationVerifyRetryPrompt(toolName) {
31920
- return `You wrote files but did not run ${toolName} or bash to verify them. Call ${toolName} NOW on your changed HTML (search @keyframes, transition, classList.add). No prose.`;
31921
- }
31922
- var WRITE_TOOLS, VERIFY_TOOLS;
31923
- var init_completion = __esm({
31924
- "packages/core/dist/council/verification/completion.js"() {
31925
- "use strict";
31926
- WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
31927
- VERIFY_TOOLS = /* @__PURE__ */ new Set(["grep_content", "bash"]);
31928
- }
31929
- });
31930
-
31931
31989
  // packages/core/dist/council/scope/extractTaskScope.js
31932
31990
  function normalizePath(p3) {
31933
31991
  return p3.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -32178,13 +32236,13 @@ ${hints.map((h) => `- ${h}`).join("\n")}
32178
32236
  ` : "") + `Use read_file then edit_file on the listed files. No verification table \u2014 tool calls only.`;
32179
32237
  }
32180
32238
  function countEmittedWriteTools(emittedToolNames) {
32181
- return emittedToolNames.filter((t) => WRITE_TOOLS2.has(t)).length;
32239
+ return emittedToolNames.filter((t) => WRITE_TOOLS.has(t)).length;
32182
32240
  }
32183
- var WRITE_TOOLS2, IMPLEMENTATION_WRITE_REQUIREMENTS;
32241
+ var WRITE_TOOLS, IMPLEMENTATION_WRITE_REQUIREMENTS;
32184
32242
  var init_implementationDelivery = __esm({
32185
32243
  "packages/core/dist/council/verification/implementationDelivery.js"() {
32186
32244
  "use strict";
32187
- WRITE_TOOLS2 = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
32245
+ WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
32188
32246
  IMPLEMENTATION_WRITE_REQUIREMENTS = [
32189
32247
  { name: "write_file", min: 1 },
32190
32248
  { name: "edit_file", min: 1 }
@@ -32192,6 +32250,191 @@ var init_implementationDelivery = __esm({
32192
32250
  }
32193
32251
  });
32194
32252
 
32253
+ // packages/core/dist/agents/council/types.js
32254
+ var NON_RETRY_AGENTS;
32255
+ var init_types7 = __esm({
32256
+ "packages/core/dist/agents/council/types.js"() {
32257
+ "use strict";
32258
+ NON_RETRY_AGENTS = /* @__PURE__ */ new Set([]);
32259
+ }
32260
+ });
32261
+
32262
+ // packages/core/dist/agents/council/cancel.js
32263
+ function isCouncilCancelled(signal) {
32264
+ return signal?.aborted === true;
32265
+ }
32266
+ function bindHarnessAbort(harness, signal) {
32267
+ if (!signal)
32268
+ return () => void 0;
32269
+ if (signal.aborted) {
32270
+ harness.cancel();
32271
+ return () => void 0;
32272
+ }
32273
+ const onAbort = () => {
32274
+ harness.cancel();
32275
+ };
32276
+ signal.addEventListener("abort", onAbort);
32277
+ return () => {
32278
+ signal.removeEventListener("abort", onAbort);
32279
+ };
32280
+ }
32281
+ async function* runHarnessWithAbort(harness, signal) {
32282
+ const unbind = bindHarnessAbort(harness, signal);
32283
+ try {
32284
+ yield* harness.run();
32285
+ } finally {
32286
+ unbind();
32287
+ }
32288
+ }
32289
+ var init_cancel = __esm({
32290
+ "packages/core/dist/agents/council/cancel.js"() {
32291
+ "use strict";
32292
+ }
32293
+ });
32294
+
32295
+ // packages/core/dist/council/modeBanners.js
32296
+ function councilModeBanner(runMode, opts) {
32297
+ if (runMode === "design-phase")
32298
+ return DESIGN_PHASE_MODE_BANNER;
32299
+ if (opts?.isImplementer === true)
32300
+ return IMPLEMENTATION_IMPLEMENTER_BANNER;
32301
+ if (opts?.isImplementer === false)
32302
+ return IMPLEMENTATION_ADVISOR_BANNER;
32303
+ return IMPLEMENTATION_MODE_BANNER;
32304
+ }
32305
+ var DESIGN_PHASE_MODE_BANNER, IMPLEMENTATION_MODE_BANNER, IMPLEMENTATION_IMPLEMENTER_BANNER, IMPLEMENTATION_ADVISOR_BANNER;
32306
+ var init_modeBanners = __esm({
32307
+ "packages/core/dist/council/modeBanners.js"() {
32308
+ "use strict";
32309
+ DESIGN_PHASE_MODE_BANNER = `COUNCIL RUN MODE: design-phase.
32310
+ Workspace tool emissions described in your role prompt are MANDATORY in this run \u2014 prose alone does not count as a deliverable. Persist artifacts via the workspace tools listed in your AVAILABLE TOOLS section.`;
32311
+ IMPLEMENTATION_MODE_BANNER = `COUNCIL RUN MODE: implementation.
32312
+ Prefer write_file, edit_file, and bash for code changes. Design-phase mandatory workspace blocks in your role prompt are INACTIVE \u2014 .zelari/docs and draft plans are hypotheses, not product law. Ground work in the real source tree. Only call workspace tools when they add durable project value.`;
32313
+ IMPLEMENTATION_IMPLEMENTER_BANNER = `COUNCIL RUN MODE: implementation \u2014 you are the IMPLEMENTER.
32314
+ You are the only member that writes code this run. Implement the solution with write_file / edit_file and verify with bash. Reconcile specialists' analysis and Minosse's critique into working, verified changes. Design vault (.zelari/docs, draft plans) is HYPOTHESIS only \u2014 product truth is the source tree. Design-phase mandatory workspace blocks are INACTIVE.`;
32315
+ IMPLEMENTATION_ADVISOR_BANNER = `COUNCIL RUN MODE: implementation \u2014 you are an ADVISOR.
32316
+ Do NOT write or edit project files (not via write_file/edit_file, and not via bash) \u2014 Lucifero is the sole implementer. Inspect the real codebase (read_file, grep_content, list_files). Treat prior design prose and .zelari/docs as hypotheses. Flag contradictions with package.json / the tree. Design-phase mandatory workspace blocks are INACTIVE.`;
32317
+ }
32318
+ });
32319
+
32320
+ // packages/core/dist/agents/council/memberMessages.js
32321
+ function restrictImplementationWrites(toolNames, opts) {
32322
+ if (opts.runMode !== "implementation" || opts.isImplementer)
32323
+ return toolNames;
32324
+ return toolNames.filter((t) => !MUTATING_PROJECT_TOOLS.includes(t));
32325
+ }
32326
+ function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation", languageModule) {
32327
+ const allToolNames = computeAgentTools(agent, aiConfig);
32328
+ const toolNames = executableTools ? allToolNames.filter((n) => executableTools.has(n)) : allToolNames;
32329
+ const mergedAiConfig = languageModule ? {
32330
+ enabledSkills: aiConfig?.enabledSkills ?? [],
32331
+ enabledTools: aiConfig?.enabledTools ?? [],
32332
+ agentSkillConfigs: aiConfig?.agentSkillConfigs ?? [],
32333
+ customSkills: aiConfig?.customSkills,
32334
+ customPromptModules: [
32335
+ ...aiConfig?.customPromptModules ?? [],
32336
+ languageModule
32337
+ ]
32338
+ } : aiConfig;
32339
+ const modeAwareAgent = {
32340
+ ...agent,
32341
+ systemPrompt: resolveRoleSystemPrompt(agent, runMode)
32342
+ };
32343
+ const split = buildSystemPromptSplit(modeAwareAgent, {
32344
+ tools: getAllTools(),
32345
+ toolNames,
32346
+ aiConfig: mergedAiConfig,
32347
+ workspaceContext,
32348
+ ragContext,
32349
+ mode: "council",
32350
+ includeWorkspaceInPrompt: true
32351
+ });
32352
+ const messages = [
32353
+ { role: "system", content: split.stable }
32354
+ ];
32355
+ if (split.volatile.trim()) {
32356
+ messages.push({ role: "system", content: split.volatile });
32357
+ }
32358
+ messages.push({ role: "system", content: councilModeBanner(runMode, { isImplementer: agent.id === "lucifer" }) }, { role: "system", content: "IMPORTANT: Before making any tool calls or expensive operations, check if the information already exists in the shared context from previous agents. Avoid redundant work." });
32359
+ if (priorOutputs.length > 0) {
32360
+ const MAX_PRIOR_CHARS = 2800;
32361
+ const summary = priorOutputs.map((o) => {
32362
+ const body = o.content.length > MAX_PRIOR_CHARS ? `${o.content.slice(0, MAX_PRIOR_CHARS)}
32363
+ \u2026 [truncated ${o.content.length}\u2192${MAX_PRIOR_CHARS} chars; treat as hypothesis]` : o.content;
32364
+ return `[${o.name} - ${o.role}]: ${body}`;
32365
+ }).join("\n\n");
32366
+ messages.push({
32367
+ role: "user",
32368
+ content: `Previous council members have said (hypotheses \u2014 prefer product files on disk if they conflict):
32369
+ ${summary}
32370
+
32371
+ Original user request: ${userMessage}`
32372
+ });
32373
+ } else {
32374
+ messages.push({ role: "user", content: userMessage });
32375
+ }
32376
+ return messages;
32377
+ }
32378
+ var MUTATING_PROJECT_TOOLS;
32379
+ var init_memberMessages = __esm({
32380
+ "packages/core/dist/agents/council/memberMessages.js"() {
32381
+ "use strict";
32382
+ init_roles();
32383
+ init_systemPromptBuilder();
32384
+ init_tools();
32385
+ init_modeBanners();
32386
+ MUTATING_PROJECT_TOOLS = ["write_file", "edit_file"];
32387
+ }
32388
+ });
32389
+
32390
+ // packages/core/dist/council/verification/completion.js
32391
+ function isVerifyToolCheckSkipped() {
32392
+ return process.env.ZELARI_VERIFY_SKIP_TOOL === "1";
32393
+ }
32394
+ function checkImplementationCompletion(emittedToolNames) {
32395
+ if (isVerifyToolCheckSkipped()) {
32396
+ return { ok: true, missing: [] };
32397
+ }
32398
+ let lastWriteIdx = -1;
32399
+ for (let i = 0; i < emittedToolNames.length; i++) {
32400
+ if (WRITE_TOOLS2.has(emittedToolNames[i])) {
32401
+ lastWriteIdx = i;
32402
+ }
32403
+ }
32404
+ if (lastWriteIdx === -1) {
32405
+ return { ok: true, missing: [] };
32406
+ }
32407
+ const afterWrite = emittedToolNames.slice(lastWriteIdx + 1);
32408
+ if (afterWrite.some((t) => VERIFY_TOOLS.has(t))) {
32409
+ return { ok: true, missing: [] };
32410
+ }
32411
+ return {
32412
+ ok: false,
32413
+ missing: ["grep_content"],
32414
+ reason: "No grep_content or bash after last write_file/edit_file"
32415
+ };
32416
+ }
32417
+ function resolveVerifyRetryTool(executableTools) {
32418
+ if (!executableTools)
32419
+ return "grep_content";
32420
+ if (executableTools.has("grep_content"))
32421
+ return "grep_content";
32422
+ if (executableTools.has("bash"))
32423
+ return "bash";
32424
+ return null;
32425
+ }
32426
+ function buildImplementationVerifyRetryPrompt(toolName) {
32427
+ return `You wrote files but did not run ${toolName} or bash to verify them. Call ${toolName} NOW on your changed HTML (search @keyframes, transition, classList.add). No prose.`;
32428
+ }
32429
+ var WRITE_TOOLS2, VERIFY_TOOLS;
32430
+ var init_completion = __esm({
32431
+ "packages/core/dist/council/verification/completion.js"() {
32432
+ "use strict";
32433
+ WRITE_TOOLS2 = /* @__PURE__ */ new Set(["write_file", "edit_file"]);
32434
+ VERIFY_TOOLS = /* @__PURE__ */ new Set(["grep_content", "bash"]);
32435
+ }
32436
+ });
32437
+
32195
32438
  // packages/core/dist/council/verification/inlineJsAutofix.js
32196
32439
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
32197
32440
  import { join as join7 } from "node:path";
@@ -32272,153 +32515,518 @@ var init_inlineJsAutofix = __esm({
32272
32515
  }
32273
32516
  });
32274
32517
 
32275
- // packages/core/dist/agents/councilApi.js
32276
- import { existsSync as existsSync12 } from "node:fs";
32277
- import { join as join8 } from "node:path";
32278
- function extractBalancedJsonObject(s) {
32279
- const start = s.indexOf("{");
32280
- if (start < 0)
32281
- return null;
32282
- let depth = 0;
32283
- let inString = false;
32284
- let escape = false;
32285
- for (let i = start; i < s.length; i++) {
32286
- const ch = s[i];
32287
- if (inString) {
32288
- if (escape) {
32289
- escape = false;
32290
- continue;
32291
- }
32292
- if (ch === "\\") {
32293
- escape = true;
32294
- continue;
32295
- }
32296
- if (ch === '"')
32297
- inString = false;
32298
- continue;
32518
+ // packages/core/dist/agents/council/toolEmission.js
32519
+ function checkMemberToolEmissions(_memberId, emittedToolNames, requirements) {
32520
+ if (requirements.length === 0) {
32521
+ return { ok: true, missing: [] };
32522
+ }
32523
+ const counts = /* @__PURE__ */ new Map();
32524
+ for (const name of emittedToolNames) {
32525
+ counts.set(name, (counts.get(name) ?? 0) + 1);
32526
+ }
32527
+ const missing = [];
32528
+ for (const req of requirements) {
32529
+ const got = counts.get(req.name) ?? 0;
32530
+ if (got < req.min) {
32531
+ missing.push(`${req.name} (got ${got}, need >= ${req.min})`);
32299
32532
  }
32300
- if (ch === '"') {
32301
- inString = true;
32302
- continue;
32533
+ }
32534
+ return { ok: missing.length === 0, missing };
32535
+ }
32536
+ function checkMemberToolEmissionSets(memberId, emittedToolNames, sets) {
32537
+ if (sets.length === 0) {
32538
+ return { ok: true, missing: [] };
32539
+ }
32540
+ const results = sets.map((set2) => checkMemberToolEmissions(memberId, emittedToolNames, set2));
32541
+ if (results.some((r) => r.ok)) {
32542
+ return { ok: true, missing: [] };
32543
+ }
32544
+ return results[0];
32545
+ }
32546
+ function enforceDesignPhaseToolEmissions(memberId, emittedToolNames) {
32547
+ const sets = DESIGN_PHASE_REQUIREMENT_SETS[memberId];
32548
+ if (!sets || sets.length === 0) {
32549
+ return { ok: true, missing: [] };
32550
+ }
32551
+ const result = checkMemberToolEmissionSets(memberId, emittedToolNames, sets);
32552
+ if (!result.ok) {
32553
+ console.warn(`[council] member "${memberId}" did not emit required tools: ${result.missing.join(", ")}. The downstream .zelari/ deliverable may be incomplete. (A forced retry turn scoped to the missing tools follows; the deterministic complete-design fallback covers any remaining gap.)`);
32554
+ }
32555
+ return result;
32556
+ }
32557
+ var DESIGN_PHASE_REQUIREMENT_SETS, DESIGN_PHASE_REQUIREMENTS;
32558
+ var init_toolEmission = __esm({
32559
+ "packages/core/dist/agents/council/toolEmission.js"() {
32560
+ "use strict";
32561
+ DESIGN_PHASE_REQUIREMENT_SETS = {
32562
+ nettun: [
32563
+ [{ name: "createPlan", min: 1 }],
32564
+ [
32565
+ { name: "createPhase", min: 3 },
32566
+ { name: "createTask", min: 6 },
32567
+ { name: "createMilestone", min: 1 }
32568
+ ]
32569
+ ],
32570
+ geryon: [
32571
+ [{ name: "createDocument", min: 3 }]
32572
+ ],
32573
+ pluton: [
32574
+ [{ name: "createDocument", min: 1 }]
32575
+ ],
32576
+ minos: [
32577
+ [{ name: "createDocument", min: 1 }]
32578
+ ],
32579
+ lucifer: [
32580
+ [{ name: "createDocument", min: 1 }]
32581
+ ]
32582
+ };
32583
+ DESIGN_PHASE_REQUIREMENTS = Object.fromEntries(Object.entries(DESIGN_PHASE_REQUIREMENT_SETS).map(([id3, sets]) => [id3, sets[0]]));
32584
+ }
32585
+ });
32586
+
32587
+ // packages/core/dist/agents/council/retryTurn.js
32588
+ function shouldRetryMember(missingToolNames, attemptsSoFar) {
32589
+ if (missingToolNames.length === 0)
32590
+ return false;
32591
+ if (attemptsSoFar >= MAX_RETRY_PER_MEMBER)
32592
+ return false;
32593
+ return true;
32594
+ }
32595
+ function buildRetryPrompt(missingToolNames) {
32596
+ const names = missingToolNames.join(", ");
32597
+ return `You did not emit the required workspace tools: ${names}. Call ${names} NOW with concrete arguments. No prose. No search.`;
32598
+ }
32599
+ async function* runRetryTurnForMember(args) {
32600
+ if (isCouncilCancelled(args.signal))
32601
+ return [];
32602
+ const executableMissing = args.executableTools ? args.missingToolNames.filter((n) => args.executableTools.has(n)) : args.missingToolNames;
32603
+ if (executableMissing.length === 0) {
32604
+ return [];
32605
+ }
32606
+ const retryToolNames = executableMissing;
32607
+ const retryToolSpecs = getProviderTools(retryToolNames).map((t) => ({
32608
+ name: t.function.name,
32609
+ description: t.function.description,
32610
+ parameters: t.function.parameters
32611
+ }));
32612
+ const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? "implementation", args.languageModule);
32613
+ const retryMessages = [
32614
+ ...baseMessages,
32615
+ {
32616
+ role: "user",
32617
+ content: args.retryPrompt ?? buildRetryPrompt(executableMissing)
32303
32618
  }
32304
- if (ch === "{")
32305
- depth++;
32306
- else if (ch === "}") {
32307
- depth--;
32308
- if (depth === 0)
32309
- return s.slice(start, i + 1);
32619
+ ];
32620
+ const maxToolCalls = args.minPerTool !== void 0 ? Object.entries(args.minPerTool).filter(([name]) => executableMissing.includes(name)).reduce((sum, [, min]) => sum + min, 0) : retryToolNames.length;
32621
+ const retryHarness = new AgentHarness({
32622
+ model: args.effectiveModel,
32623
+ provider: args.effectiveProvider,
32624
+ sessionId: args.sessionId,
32625
+ messages: retryMessages,
32626
+ tools: retryToolSpecs,
32627
+ eventBus: args.eventBus,
32628
+ toolRegistry: args.toolRegistry,
32629
+ // Budget the retry so the model can satisfy every requirement in
32630
+ // ONE tool_calls turn. For createTask min:12 this needs 12 calls.
32631
+ maxToolCallsPerTurn: maxToolCalls,
32632
+ memberId: args.agent.id,
32633
+ memberName: args.agent.name,
32634
+ providerStream: (params) => args.providerStream(params)
32635
+ });
32636
+ const retryEmitted = [];
32637
+ for await (const event of runHarnessWithAbort(retryHarness, args.signal)) {
32638
+ if (event.type === "tool_execution_start") {
32639
+ retryEmitted.push(event.toolName);
32310
32640
  }
32641
+ yield event;
32311
32642
  }
32312
- return null;
32643
+ return retryEmitted;
32313
32644
  }
32314
- function parseClarificationRequest(text) {
32315
- const start = text.indexOf(QUESTION_MARKER);
32316
- if (start < 0)
32317
- return null;
32318
- const rest = text.slice(start + QUESTION_MARKER.length);
32319
- const end = rest.indexOf(QUESTION_END_MARKER);
32320
- const block = end >= 0 ? rest.slice(0, end) : rest;
32321
- const cleaned = block.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
32322
- const jsonText = extractBalancedJsonObject(cleaned) ?? (() => {
32323
- const objStart = cleaned.indexOf("{");
32324
- const objEnd = cleaned.lastIndexOf("}");
32325
- return objStart >= 0 && objEnd > objStart ? cleaned.slice(objStart, objEnd + 1) : cleaned;
32326
- })();
32645
+ async function* applyRetryIfMissing(args) {
32646
+ if (isCouncilCancelled(args.config.signal))
32647
+ return;
32648
+ if (args.check.ok)
32649
+ return;
32650
+ const missingToolNames = args.check.missing.map((m) => m.split(" ")[0]);
32651
+ if (!shouldRetryMember(missingToolNames, 0))
32652
+ return;
32653
+ console.warn(`[council] ${args.agent.id} retrying missing tools: ${missingToolNames.join(", ")}`);
32654
+ const minPerTool = {};
32655
+ if (args.requirements) {
32656
+ for (const req of args.requirements) {
32657
+ if (missingToolNames.includes(req.name)) {
32658
+ minPerTool[req.name] = req.min;
32659
+ }
32660
+ }
32661
+ }
32327
32662
  try {
32328
- const parsed = JSON.parse(jsonText);
32329
- if (typeof parsed.question !== "string" || !parsed.question.trim())
32330
- return null;
32331
- return {
32332
- question: parsed.question.trim(),
32333
- choices: Array.isArray(parsed.choices) ? parsed.choices.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : void 0,
32334
- context: typeof parsed.context === "string" ? parsed.context.trim() : void 0
32335
- };
32336
- } catch {
32337
- return null;
32663
+ const retryGenerator = runRetryTurnForMember({
32664
+ agent: args.agent,
32665
+ missingToolNames,
32666
+ minPerTool,
32667
+ executableTools: args.executableNames,
32668
+ userMessage: args.userMessage,
32669
+ ragContext: args.config.ragContext,
32670
+ workspaceContext: args.config.workspaceContext,
32671
+ priorOutputs: args.agentOutputs,
32672
+ aiConfig: args.config.aiConfig,
32673
+ sessionId: args.sessionId,
32674
+ effectiveModel: args.effectiveModel,
32675
+ effectiveProvider: args.effectiveProvider,
32676
+ eventBus: args.config.eventBus,
32677
+ toolRegistry: args.config.tools,
32678
+ providerStream: args.config.providerStream,
32679
+ runMode: args.config.runMode,
32680
+ languageModule: args.languageModule,
32681
+ signal: args.config.signal
32682
+ });
32683
+ for await (const event of retryGenerator) {
32684
+ if (event.type === "tool_execution_start") {
32685
+ args.onToolCall();
32686
+ args.emittedToolNames.push(event.toolName);
32687
+ }
32688
+ yield event;
32689
+ }
32690
+ } catch (retryErr) {
32691
+ console.error(`[council] ${args.agent.id} retry failed:`, retryErr);
32338
32692
  }
32693
+ enforceDesignPhaseToolEmissions(args.agent.id, args.emittedToolNames);
32339
32694
  }
32340
- function hasInteractiveClarification(text) {
32341
- const c = parseClarificationRequest(text);
32342
- return !!(c && c.choices && c.choices.length >= 2);
32695
+ var MAX_RETRY_PER_MEMBER;
32696
+ var init_retryTurn = __esm({
32697
+ "packages/core/dist/agents/council/retryTurn.js"() {
32698
+ "use strict";
32699
+ init_AgentHarness();
32700
+ init_cancel();
32701
+ init_toolSchemas();
32702
+ init_memberMessages();
32703
+ init_toolEmission();
32704
+ MAX_RETRY_PER_MEMBER = 1;
32705
+ }
32706
+ });
32707
+
32708
+ // packages/core/dist/agents/council/chairmanDelivery.js
32709
+ async function* applyCompletionRetry(args) {
32710
+ if (isCouncilCancelled(args.config.signal))
32711
+ return;
32712
+ const check2 = checkImplementationCompletion(args.emittedToolNames);
32713
+ if (check2.ok)
32714
+ return;
32715
+ const retryTool = resolveVerifyRetryTool(args.executableNames);
32716
+ if (!retryTool) {
32717
+ console.warn("[council] implementation verify retry skipped \u2014 no grep_content/bash in registry");
32718
+ return;
32719
+ }
32720
+ if (!shouldRetryMember([retryTool], 0))
32721
+ return;
32722
+ console.warn(`[council] ${args.agent.id} retrying missing verify tool: ${retryTool}`);
32723
+ try {
32724
+ const retryGenerator = runRetryTurnForMember({
32725
+ agent: args.agent,
32726
+ missingToolNames: [retryTool],
32727
+ executableTools: args.executableNames,
32728
+ userMessage: args.userMessage,
32729
+ ragContext: args.config.ragContext,
32730
+ workspaceContext: args.config.workspaceContext,
32731
+ priorOutputs: args.agentOutputs,
32732
+ aiConfig: args.config.aiConfig,
32733
+ sessionId: args.sessionId,
32734
+ effectiveModel: args.effectiveModel,
32735
+ effectiveProvider: args.effectiveProvider,
32736
+ eventBus: args.config.eventBus,
32737
+ toolRegistry: args.config.tools,
32738
+ providerStream: args.config.providerStream,
32739
+ runMode: args.config.runMode,
32740
+ retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
32741
+ languageModule: args.languageModule,
32742
+ signal: args.config.signal
32743
+ });
32744
+ for await (const event of retryGenerator) {
32745
+ if (event.type === "tool_execution_start") {
32746
+ args.onToolCall();
32747
+ args.emittedToolNames.push(event.toolName);
32748
+ }
32749
+ yield event;
32750
+ }
32751
+ } catch (retryErr) {
32752
+ console.error(`[council] ${args.agent.id} verify retry failed:`, retryErr);
32753
+ }
32754
+ const after = checkImplementationCompletion(args.emittedToolNames);
32755
+ if (!after.ok) {
32756
+ console.warn(`[council] ${args.agent.id} still missing verify after retry: ${after.reason}`);
32757
+ }
32343
32758
  }
32344
- function parseThinking(text) {
32345
- const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
32346
- if (complete)
32347
- return complete[1].trim();
32348
- const open2 = text.match(/<think(?:ing)?>([\s\S]*)$/i);
32349
- return open2 ? open2[1].trim() : "";
32759
+ function buildMotionFixPrompt(violations) {
32760
+ const byFile = /* @__PURE__ */ new Map();
32761
+ for (const v of violations) {
32762
+ const file2 = v.file || "index.html";
32763
+ const loc = v.line ? `${file2}:L${v.line}` : file2;
32764
+ const list = byFile.get(file2) ?? [];
32765
+ list.push(` - ${loc}: ${v.message}`);
32766
+ byFile.set(file2, list);
32767
+ }
32768
+ const blocks = Array.from(byFile.values()).map((lines) => lines.join("\n")).join("\n");
32769
+ return `Deterministic verification found ${violations.length} motion violation(s) in the file(s) you just edited. Fix ONLY these \u2014 do not add features, do not rewrite sections, do not touch anything else:
32770
+ ${blocks}
32771
+
32772
+ Rules: animate ONLY transform and opacity. Replace any box-shadow / background / background-position / filter / color / border-color / width / height / grid-template-rows used in @keyframes or transitions with transform/opacity equivalents (e.g. render a glow via a pseudo-element that scales and fades). For every classList.add('x') in the script, add a matching '.x' CSS rule. Use read_file to see the exact lines, then edit_file. When the listed items are fixed, stop \u2014 no summary.`;
32350
32773
  }
32351
- function cleanAgentContent(text, opts = {}) {
32352
- const stripQuestion = opts.stripQuestion !== false;
32353
- const stripThink = opts.stripThink !== false;
32354
- let out = text;
32355
- if (stripThink) {
32356
- out = out.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "");
32774
+ async function* applyImplementationWriteRetry(args) {
32775
+ if (isCouncilCancelled(args.config.signal))
32776
+ return;
32777
+ if (args.check.ok)
32778
+ return;
32779
+ if (!shouldRetryMember(["write_file"], 0))
32780
+ return;
32781
+ const statusMsg = `[council] ${args.chairman.id} implementation write retry: ${args.check.missing.join(", ")}`;
32782
+ args.onCouncilStatus?.(statusMsg);
32783
+ console.warn(statusMsg);
32784
+ try {
32785
+ const retryGenerator = runRetryTurnForMember({
32786
+ agent: args.chairman,
32787
+ missingToolNames: ["read_file", "write_file", "edit_file"],
32788
+ minPerTool: { read_file: 3, edit_file: 8, write_file: 1 },
32789
+ executableTools: args.executableNames,
32790
+ userMessage: args.userMessage,
32791
+ ragContext: args.config.ragContext,
32792
+ workspaceContext: args.config.workspaceContext,
32793
+ priorOutputs: args.agentOutputs,
32794
+ aiConfig: args.config.aiConfig,
32795
+ sessionId: args.sessionId,
32796
+ effectiveModel: args.effectiveModel,
32797
+ effectiveProvider: args.effectiveProvider,
32798
+ eventBus: args.config.eventBus,
32799
+ toolRegistry: args.config.tools,
32800
+ providerStream: args.config.providerStream,
32801
+ runMode: "implementation",
32802
+ retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
32803
+ languageModule: args.languageModule,
32804
+ signal: args.config.signal
32805
+ });
32806
+ for await (const event of retryGenerator) {
32807
+ if (event.type === "tool_execution_start")
32808
+ args.onToolCall?.();
32809
+ if (event.type === "tool_execution_end" && !event.isError && typeof event.result === "string") {
32810
+ try {
32811
+ const parsed = JSON.parse(event.result);
32812
+ if ((parsed.bytesWritten ?? 0) > 0 || (parsed.occurrencesReplaced ?? 0) > 0) {
32813
+ args.onSuccessfulWrite?.();
32814
+ }
32815
+ } catch {
32816
+ if (event.result.includes("bytesWritten") || event.result.includes("occurrencesReplaced")) {
32817
+ args.onSuccessfulWrite?.();
32818
+ }
32819
+ }
32820
+ }
32821
+ yield event;
32822
+ }
32823
+ } catch (retryErr) {
32824
+ console.error(`[council] ${args.chairman.id} implementation write retry failed:`, retryErr);
32357
32825
  }
32358
- out = out.replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/gi, "").replace(/<\/?minimax:tool_call>/gi, "").replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, "").replace(/<\/?tool_call>/gi, "").replace(/<function_call>[\s\S]*?<\/function_call>/gi, "").replace(/<\/?function_call>/gi, "").replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "").replace(/<\/invoke>/gi, "").replace(/<parameter\b[^>]*>[\s\S]*?<\/parameter>/gi, "").replace(/<\/parameter>/gi, "").replace(/\]\s*<\]\s*minimax\s*\[>\s*\[?<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "").replace(/<minimax:tool_call>[\s\S]*$/gi, "").replace(/<tool_call>[\s\S]*$/gi, "").replace(/<function_call>[\s\S]*$/gi, "").replace(/<invoke\b[^>]*>[\s\S]*$/gi, "").replace(/\]\s*<\]\s*minimax\s*\[>[\s\S]*$/gi, "").replace(/^\s*\]\s*<\]\s*minimax\s*\[>.*$/gim, "").replace(/^\s*<\/?(?:tool_call|function_call|invoke|parameter|minimax:tool_call)\b[^>]*>\s*$/gim, "");
32359
- if (stripQuestion) {
32360
- out = out.replace(/---QUESTION---[\s\S]*?---END---/g, "").replace(/---QUESTION---[\s\S]*$/g, "");
32826
+ }
32827
+ async function* runChairmanDeliveryLoop(args) {
32828
+ const maxAttempts = args.maxAttempts ?? MAX_DELIVERY_ATTEMPTS;
32829
+ const zelariRoot = `${args.projectRoot}/.zelari`;
32830
+ let attempt = 0;
32831
+ while (attempt < maxAttempts) {
32832
+ if (isCouncilCancelled(args.config.signal))
32833
+ return false;
32834
+ const report = runImplementationVerification({
32835
+ projectRoot: args.projectRoot,
32836
+ zelariRoot
32837
+ });
32838
+ const blocking = filterDeliveryBlockingFails(report.results);
32839
+ if (blocking.length === 0)
32840
+ return true;
32841
+ if (blocking.some((b) => b.id === "inline-js.budget")) {
32842
+ const jsFix = applyInlineJsAutofix(args.projectRoot, report);
32843
+ if (jsFix.applied) {
32844
+ args.onCouncilStatus?.(`[council] ${args.chairman.id} inline-js autofix: ${jsFix.fixes.join("; ")}`);
32845
+ const afterJs = runImplementationVerification({
32846
+ projectRoot: args.projectRoot,
32847
+ zelariRoot
32848
+ });
32849
+ if (filterDeliveryBlockingFails(afterJs.results).length === 0)
32850
+ return true;
32851
+ }
32852
+ }
32853
+ attempt++;
32854
+ const statusMsg = `[council] ${args.chairman.id} delivery pass ${attempt}/${maxAttempts}: ${blocking.map((b) => b.id).join(", ")}`;
32855
+ args.onCouncilStatus?.(statusMsg);
32856
+ console.warn(statusMsg);
32857
+ try {
32858
+ const fixGenerator = runRetryTurnForMember({
32859
+ agent: args.chairman,
32860
+ missingToolNames: ["read_file", "edit_file"],
32861
+ minPerTool: { read_file: 3, edit_file: 10 },
32862
+ executableTools: args.executableNames,
32863
+ userMessage: args.userMessage,
32864
+ ragContext: args.config.ragContext,
32865
+ workspaceContext: args.config.workspaceContext,
32866
+ priorOutputs: args.agentOutputs,
32867
+ aiConfig: args.config.aiConfig,
32868
+ sessionId: args.sessionId,
32869
+ effectiveModel: args.effectiveModel,
32870
+ effectiveProvider: args.effectiveProvider,
32871
+ eventBus: args.config.eventBus,
32872
+ toolRegistry: args.config.tools,
32873
+ providerStream: args.config.providerStream,
32874
+ runMode: "implementation",
32875
+ retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
32876
+ languageModule: args.languageModule,
32877
+ signal: args.config.signal
32878
+ });
32879
+ for await (const event of fixGenerator) {
32880
+ if (event.type === "tool_execution_start")
32881
+ args.onToolCall?.();
32882
+ yield event;
32883
+ }
32884
+ } catch (deliveryErr) {
32885
+ console.error(`[council] ${args.chairman.id} delivery pass ${attempt} failed:`, deliveryErr);
32886
+ break;
32887
+ }
32888
+ for (const rel2 of args.changedFiles) {
32889
+ for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath: rel2, zelariRoot })) {
32890
+ args.changedFiles.add(w.file ?? rel2);
32891
+ }
32892
+ }
32893
+ }
32894
+ const finalReport = runImplementationVerification({
32895
+ projectRoot: args.projectRoot,
32896
+ zelariRoot
32897
+ });
32898
+ return filterDeliveryBlockingFails(finalReport.results).length === 0;
32899
+ }
32900
+ var MAX_DELIVERY_ATTEMPTS;
32901
+ var init_chairmanDelivery = __esm({
32902
+ "packages/core/dist/agents/council/chairmanDelivery.js"() {
32903
+ "use strict";
32904
+ init_microGate();
32905
+ init_completion();
32906
+ init_implementationDelivery();
32907
+ init_runChecks();
32908
+ init_inlineJsAutofix();
32909
+ init_retryTurn();
32910
+ init_cancel();
32911
+ MAX_DELIVERY_ATTEMPTS = 2;
32912
+ }
32913
+ });
32914
+
32915
+ // packages/core/dist/agents/council/chairmanFixLoop.js
32916
+ async function* replayChairmanTextTools(args) {
32917
+ const tools = parseTextToolCalls(args.synthesisText);
32918
+ if (tools.length === 0)
32919
+ return 0;
32920
+ let applied = 0;
32921
+ for (const tt of tools) {
32922
+ if (tt.name !== "edit_file" && tt.name !== "write_file")
32923
+ continue;
32924
+ const normalized = normalizeTextToolArgs(tt.name, tt.args);
32925
+ const toolCallId = `replay-${crypto.randomUUID().slice(0, 8)}`;
32926
+ yield createBrainEvent("tool_execution_start", args.sessionId, {
32927
+ toolCallId,
32928
+ toolName: tt.name,
32929
+ args: normalized,
32930
+ ...args.memberId ? { memberId: args.memberId } : {}
32931
+ });
32932
+ const startMs = Date.now();
32933
+ let resultStr = "";
32934
+ let isError = false;
32935
+ try {
32936
+ const result = await args.toolRegistry.invoke(tt.name, normalized, {
32937
+ cwd: args.projectRoot,
32938
+ sessionId: args.sessionId
32939
+ });
32940
+ if (result.ok) {
32941
+ const val = result.value;
32942
+ if (tt.name === "edit_file" && val.occurrencesReplaced === 0) {
32943
+ resultStr = `edit_file: no match for oldString (replay)`;
32944
+ isError = true;
32945
+ } else {
32946
+ resultStr = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
32947
+ applied += 1;
32948
+ }
32949
+ } else {
32950
+ resultStr = result.error;
32951
+ isError = true;
32952
+ }
32953
+ } catch (err) {
32954
+ resultStr = err instanceof Error ? err.message : String(err);
32955
+ isError = true;
32956
+ }
32957
+ yield createBrainEvent("tool_execution_end", args.sessionId, {
32958
+ toolCallId,
32959
+ result: resultStr,
32960
+ isError,
32961
+ durationMs: Date.now() - startMs
32962
+ });
32963
+ }
32964
+ return applied;
32965
+ }
32966
+ async function* runChairmanFixLoop(args) {
32967
+ const maxAttempts = args.maxAttempts ?? 3;
32968
+ const zelariRoot = `${args.projectRoot}/.zelari`;
32969
+ let current = Array.from(args.violations.values());
32970
+ let attempt = 0;
32971
+ while (current.length > 0 && attempt < maxAttempts) {
32972
+ if (isCouncilCancelled(args.config.signal))
32973
+ return;
32974
+ attempt++;
32975
+ try {
32976
+ const fixGenerator = runRetryTurnForMember({
32977
+ agent: args.chairman,
32978
+ missingToolNames: ["read_file", "edit_file"],
32979
+ minPerTool: { read_file: 2, edit_file: 8 },
32980
+ executableTools: args.executableNames,
32981
+ userMessage: args.userMessage,
32982
+ ragContext: args.config.ragContext,
32983
+ workspaceContext: args.config.workspaceContext,
32984
+ priorOutputs: args.agentOutputs,
32985
+ aiConfig: args.config.aiConfig,
32986
+ sessionId: args.sessionId,
32987
+ effectiveModel: args.effectiveModel,
32988
+ effectiveProvider: args.effectiveProvider,
32989
+ eventBus: args.config.eventBus,
32990
+ toolRegistry: args.config.tools,
32991
+ providerStream: args.config.providerStream,
32992
+ runMode: "implementation",
32993
+ retryPrompt: buildMotionFixPrompt(current),
32994
+ languageModule: args.languageModule,
32995
+ signal: args.config.signal
32996
+ });
32997
+ for await (const event of fixGenerator) {
32998
+ if (event.type === "tool_execution_start")
32999
+ args.onToolCall?.();
33000
+ yield event;
33001
+ }
33002
+ } catch (fixErr) {
33003
+ console.error(`[council] chairman fix pass ${attempt} failed:`, fixErr);
33004
+ break;
33005
+ }
33006
+ const rescanned = /* @__PURE__ */ new Map();
33007
+ for (const relPath of args.changedFiles) {
33008
+ for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath, zelariRoot })) {
33009
+ rescanned.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
33010
+ }
33011
+ }
33012
+ current = Array.from(rescanned.values());
32361
33013
  }
32362
- out = out.replace(/\n{3,}/g, "\n\n").trim();
32363
- return scrubProprietaryLeak(out);
32364
- }
32365
- function restrictImplementationWrites(toolNames, opts) {
32366
- if (opts.runMode !== "implementation" || opts.isImplementer)
32367
- return toolNames;
32368
- return toolNames.filter((t) => !MUTATING_PROJECT_TOOLS.includes(t));
32369
33014
  }
32370
- function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation", languageModule) {
32371
- const allToolNames = computeAgentTools(agent, aiConfig);
32372
- const toolNames = executableTools ? allToolNames.filter((n) => executableTools.has(n)) : allToolNames;
32373
- const mergedAiConfig = languageModule ? {
32374
- enabledSkills: aiConfig?.enabledSkills ?? [],
32375
- enabledTools: aiConfig?.enabledTools ?? [],
32376
- agentSkillConfigs: aiConfig?.agentSkillConfigs ?? [],
32377
- customSkills: aiConfig?.customSkills,
32378
- customPromptModules: [
32379
- ...aiConfig?.customPromptModules ?? [],
32380
- languageModule
32381
- ]
32382
- } : aiConfig;
32383
- const modeAwareAgent = {
32384
- ...agent,
32385
- systemPrompt: resolveRoleSystemPrompt(agent, runMode)
32386
- };
32387
- const split = buildSystemPromptSplit(modeAwareAgent, {
32388
- tools: getAllTools(),
32389
- toolNames,
32390
- aiConfig: mergedAiConfig,
32391
- workspaceContext,
32392
- ragContext,
32393
- mode: "council",
32394
- includeWorkspaceInPrompt: true
32395
- });
32396
- const messages = [
32397
- { role: "system", content: split.stable }
32398
- ];
32399
- if (split.volatile.trim()) {
32400
- messages.push({ role: "system", content: split.volatile });
33015
+ var init_chairmanFixLoop = __esm({
33016
+ "packages/core/dist/agents/council/chairmanFixLoop.js"() {
33017
+ "use strict";
33018
+ init_AgentHarness();
33019
+ init_events();
33020
+ init_microGate();
33021
+ init_chairmanDelivery();
33022
+ init_retryTurn();
33023
+ init_cancel();
32401
33024
  }
32402
- messages.push({ role: "system", content: councilModeBanner(runMode, { isImplementer: agent.id === "lucifer" }) }, { role: "system", content: "IMPORTANT: Before making any tool calls or expensive operations, check if the information already exists in the shared context from previous agents. Avoid redundant work." });
32403
- if (priorOutputs.length > 0) {
32404
- const MAX_PRIOR_CHARS = 2800;
32405
- const summary = priorOutputs.map((o) => {
32406
- const body = o.content.length > MAX_PRIOR_CHARS ? `${o.content.slice(0, MAX_PRIOR_CHARS)}
32407
- \u2026 [truncated ${o.content.length}\u2192${MAX_PRIOR_CHARS} chars; treat as hypothesis]` : o.content;
32408
- return `[${o.name} - ${o.role}]: ${body}`;
32409
- }).join("\n\n");
32410
- messages.push({
32411
- role: "user",
32412
- content: `Previous council members have said (hypotheses \u2014 prefer product files on disk if they conflict):
32413
- ${summary}
33025
+ });
32414
33026
 
32415
- Original user request: ${userMessage}`
32416
- });
32417
- } else {
32418
- messages.push({ role: "user", content: userMessage });
32419
- }
32420
- return messages;
32421
- }
33027
+ // packages/core/dist/agents/councilApi.js
33028
+ import { existsSync as existsSync12 } from "node:fs";
33029
+ import { join as join8 } from "node:path";
32422
33030
  async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32423
33031
  const baseAgents = getCouncilAgents(config2.councilSize);
32424
33032
  const agents = swapMembers(baseAgents, config2.memberSwap ?? {});
@@ -32454,6 +33062,17 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32454
33062
  model: config2.model,
32455
33063
  provider: config2.provider ?? "minimax"
32456
33064
  };
33065
+ if (isCouncilCancelled(config2.signal)) {
33066
+ yield {
33067
+ type: "agent_end",
33068
+ id: crypto.randomUUID(),
33069
+ ts: Date.now(),
33070
+ sessionId: sessionId2,
33071
+ reason: "cancelled",
33072
+ durationMs: 0
33073
+ };
33074
+ return;
33075
+ }
32457
33076
  const emitMemberCost = (input) => {
32458
33077
  const usage = input.usage;
32459
33078
  const prompt = usage?.promptTokens ?? 0;
@@ -32483,6 +33102,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32483
33102
  const oracle = agents.find((a) => a.id === "minos") ?? (config2.skipSpecialists ? getAgent("minos") : void 0);
32484
33103
  const chairman = agents.find((a) => a.id === "lucifer") ?? (config2.skipSpecialists ? getAgent("lucifer") : void 0);
32485
33104
  for (const agent of specialists) {
33105
+ if (isCouncilCancelled(config2.signal))
33106
+ break;
32486
33107
  if (completedIds.has(agent.id))
32487
33108
  continue;
32488
33109
  callbacks.onAgentStart?.(agent);
@@ -32528,7 +33149,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32528
33149
  const emittedToolNames = [];
32529
33150
  const memberStart = Date.now();
32530
33151
  try {
32531
- for await (const event of harness.run()) {
33152
+ for await (const event of runHarnessWithAbort(harness, config2.signal)) {
32532
33153
  yield event;
32533
33154
  if (event.type === "tool_execution_start") {
32534
33155
  toolCalls += 1;
@@ -32550,7 +33171,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32550
33171
  fullText = `Error: ${err instanceof Error ? err.message : "Unknown"}`;
32551
33172
  errored = true;
32552
33173
  }
32553
- if (isDesignPhase && !errored && !NON_RETRY_AGENTS.has(agent.id)) {
33174
+ if (isDesignPhase && !errored && !NON_RETRY_AGENTS.has(agent.id) && !isCouncilCancelled(config2.signal)) {
32554
33175
  const specialistCheck = enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
32555
33176
  yield* applyRetryIfMissing({
32556
33177
  agent,
@@ -32633,7 +33254,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32633
33254
  }
32634
33255
  }
32635
33256
  }
32636
- if (oracle && !completedIds.has(oracle.id)) {
33257
+ if (oracle && !completedIds.has(oracle.id) && !isCouncilCancelled(config2.signal)) {
32637
33258
  callbacks.onAgentStart?.(oracle);
32638
33259
  const override = config2.agentModels?.[oracle.id];
32639
33260
  const effectiveProvider = override?.providerId ?? config2.provider ?? "minimax";
@@ -32682,7 +33303,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32682
33303
  const emittedToolNames = [];
32683
33304
  const memberStart = Date.now();
32684
33305
  try {
32685
- for await (const event of harness.run()) {
33306
+ for await (const event of runHarnessWithAbort(harness, config2.signal)) {
32686
33307
  yield event;
32687
33308
  if (event.type === "tool_execution_start") {
32688
33309
  toolCalls += 1;
@@ -32704,7 +33325,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32704
33325
  fullText = `Review error: ${err instanceof Error ? err.message : "Unknown"}`;
32705
33326
  errored = true;
32706
33327
  }
32707
- if (isDesignPhase && !errored) {
33328
+ if (isDesignPhase && !errored && !isCouncilCancelled(config2.signal)) {
32708
33329
  const oracleCheck = enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
32709
33330
  yield* applyRetryIfMissing({
32710
33331
  agent: oracle,
@@ -32764,7 +33385,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32764
33385
  });
32765
33386
  agentOutputs.push({ name: oracle.name, role: oracle.role, content: cleaned });
32766
33387
  }
32767
- if (chairman && !completedIds.has(chairman.id)) {
33388
+ if (chairman && !completedIds.has(chairman.id) && !isCouncilCancelled(config2.signal)) {
32768
33389
  callbacks.onSynthesisStart?.();
32769
33390
  callbacks.onAgentStart?.(chairman);
32770
33391
  const override = config2.agentModels?.[chairman.id];
@@ -32800,702 +33421,264 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32800
33421
  });
32801
33422
  let fullText = "";
32802
33423
  let toolCalls = 0;
32803
- let usage = null;
32804
- let errored = false;
32805
- let lastErrorMessage = "";
32806
- const emittedToolNames = [];
32807
- const pendingChairmanWrites = /* @__PURE__ */ new Map();
32808
- let successfulWriteCount = 0;
32809
- const chairmanViolations = /* @__PURE__ */ new Map();
32810
- const changedTargetFiles = /* @__PURE__ */ new Set();
32811
- let chairmanProjectRoot = parseProjectRootFromWorkspaceContext(config2.workspaceContext ?? "");
32812
- const memberStart = Date.now();
32813
- try {
32814
- for await (const event of chairmanHarness.run()) {
32815
- yield event;
32816
- if (event.type === "tool_execution_start") {
32817
- toolCalls += 1;
32818
- emittedToolNames.push(event.toolName);
32819
- if (!isDesignPhase && (event.toolName === "write_file" || event.toolName === "edit_file") && typeof event.args.path === "string") {
32820
- pendingChairmanWrites.set(event.toolCallId, {
32821
- path: event.args.path,
32822
- toolName: event.toolName
32823
- });
32824
- }
32825
- }
32826
- if (!isDesignPhase && event.type === "tool_execution_end") {
32827
- const pending = pendingChairmanWrites.get(event.toolCallId);
32828
- if (pending) {
32829
- pendingChairmanWrites.delete(event.toolCallId);
32830
- if (!event.isError) {
32831
- let wrote = pending.toolName === "write_file";
32832
- if (pending.toolName === "edit_file" && typeof event.result === "string") {
32833
- try {
32834
- const parsed = JSON.parse(event.result);
32835
- wrote = (parsed.occurrencesReplaced ?? 0) > 0;
32836
- } catch {
32837
- wrote = false;
32838
- }
32839
- }
32840
- if (wrote) {
32841
- successfulWriteCount += 1;
32842
- const projectRoot = parseProjectRootFromWorkspaceContext(config2.workspaceContext);
32843
- if (projectRoot) {
32844
- chairmanProjectRoot = projectRoot;
32845
- changedTargetFiles.add(pending.path);
32846
- for (const w of runChairmanMicroGate({
32847
- projectRoot,
32848
- relPath: pending.path,
32849
- zelariRoot: `${projectRoot}/.zelari`
32850
- })) {
32851
- chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
32852
- }
32853
- }
32854
- }
32855
- }
32856
- }
32857
- }
32858
- if (event.type === "message_end" && event.usage) {
32859
- usage = event.usage;
32860
- }
32861
- if (event.type === "message_delta") {
32862
- fullText += event.delta;
32863
- callbacks.onSynthesisChunk?.(event.delta);
32864
- callbacks.onAgentChunk?.(chairman, event.delta);
32865
- }
32866
- if (event.type === "error") {
32867
- if (event.severity !== "cancelled" && event.code !== "text_tools_parse_failed") {
32868
- errored = true;
32869
- lastErrorMessage = event.message;
32870
- }
32871
- }
32872
- }
32873
- } catch (err) {
32874
- console.error(`[council] chairman "${chairman.id}" failed:`, err);
32875
- errored = true;
32876
- lastErrorMessage = err instanceof Error ? err.message : String(err);
32877
- }
32878
- if (isDesignPhase && !errored) {
32879
- const chairmanCheck = enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
32880
- yield* applyRetryIfMissing({
32881
- agent: chairman,
32882
- check: chairmanCheck,
32883
- requirements: DESIGN_PHASE_REQUIREMENTS[chairman.id],
32884
- emittedToolNames,
32885
- executableNames,
32886
- sessionId: sessionId2,
32887
- userMessage,
32888
- agentOutputs,
32889
- config: config2,
32890
- effectiveProvider,
32891
- effectiveModel,
32892
- onToolCall: () => {
32893
- toolCalls += 1;
32894
- },
32895
- languageModule: councilLanguageModule
32896
- });
32897
- } else if (isDesignPhase) {
32898
- enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
32899
- } else if (!errored) {
32900
- if (chairmanProjectRoot) {
32901
- const zelariRoot = `${chairmanProjectRoot}/.zelari`;
32902
- const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
32903
- for (const rel2 of spec.targets) {
32904
- if (!existsSync12(join8(chairmanProjectRoot, rel2)))
32905
- continue;
32906
- changedTargetFiles.add(rel2);
32907
- for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
32908
- chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
32909
- }
32910
- }
32911
- }
32912
- if (fullText.includes("---TOOLS---") && chairmanProjectRoot && config2.tools) {
32913
- const replayed = yield* replayChairmanTextTools({
32914
- synthesisText: fullText,
32915
- projectRoot: chairmanProjectRoot,
32916
- toolRegistry: config2.tools,
32917
- sessionId: sessionId2,
32918
- memberId: chairman.id
32919
- });
32920
- successfulWriteCount += replayed;
32921
- const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
32922
- const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
32923
- for (const rel2 of specReplay.targets) {
32924
- if (!existsSync12(join8(chairmanProjectRoot, rel2)))
32925
- continue;
32926
- changedTargetFiles.add(rel2);
32927
- for (const w of runChairmanMicroGate({
32928
- projectRoot: chairmanProjectRoot,
32929
- relPath: rel2,
32930
- zelariRoot: zelariRootReplay
32931
- })) {
32932
- chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
32933
- }
32934
- }
32935
- }
32936
- if (chairmanProjectRoot) {
32937
- const deliveryCheck = checkImplementationDelivery(successfulWriteCount, countEmittedWriteTools(emittedToolNames));
32938
- if (!deliveryCheck.ok) {
32939
- yield* applyImplementationWriteRetry({
32940
- chairman,
32941
- check: deliveryCheck,
32942
- sessionId: sessionId2,
32943
- userMessage,
32944
- agentOutputs,
32945
- config: config2,
32946
- effectiveProvider,
32947
- effectiveModel,
32948
- executableNames,
32949
- onToolCall: () => {
32950
- toolCalls += 1;
32951
- },
32952
- onSuccessfulWrite: () => {
32953
- successfulWriteCount += 1;
32954
- },
32955
- onCouncilStatus: callbacks.onCouncilStatus,
32956
- languageModule: councilLanguageModule
32957
- });
32958
- }
32959
- }
32960
- if (chairmanViolations.size > 0 && chairmanProjectRoot) {
32961
- yield* runChairmanFixLoop({
32962
- chairman,
32963
- violations: chairmanViolations,
32964
- changedFiles: changedTargetFiles,
32965
- projectRoot: chairmanProjectRoot,
32966
- executableNames,
32967
- sessionId: sessionId2,
32968
- userMessage,
32969
- agentOutputs,
32970
- config: config2,
32971
- effectiveProvider,
32972
- effectiveModel,
32973
- onToolCall: () => {
32974
- toolCalls += 1;
32975
- },
32976
- languageModule: councilLanguageModule
32977
- });
32978
- }
32979
- if (chairmanProjectRoot) {
32980
- yield* runChairmanDeliveryLoop({
32981
- chairman,
32982
- projectRoot: chairmanProjectRoot,
32983
- changedFiles: changedTargetFiles,
32984
- executableNames,
32985
- sessionId: sessionId2,
32986
- userMessage,
32987
- agentOutputs,
32988
- config: config2,
32989
- effectiveProvider,
32990
- effectiveModel,
32991
- onToolCall: () => {
32992
- toolCalls += 1;
32993
- },
32994
- onCouncilStatus: callbacks.onCouncilStatus,
32995
- languageModule: councilLanguageModule
32996
- });
32997
- }
32998
- }
32999
- const memberDuration = Date.now() - memberStart;
33000
- const finalSynthesis = errored && fullText.length === 0 ? `[Chairman synthesis failed: ${lastErrorMessage || "unknown error"}]` : fullText;
33001
- callbacks.onSynthesisDone?.(finalSynthesis, void 0, void 0);
33002
- emitMemberCost({
33003
- memberId: chairman.id,
33004
- name: chairman.name,
33005
- usage,
33006
- durationMs: memberDuration,
33007
- toolCalls,
33008
- errored
33009
- });
33010
- yield createBrainEvent("member_cost", sessionId2, {
33011
- cost: {
33012
- memberId: chairman.id,
33013
- name: chairman.name,
33014
- promptTokens: usage?.promptTokens ?? 0,
33015
- completionTokens: usage?.completionTokens ?? 0,
33016
- totalTokens: usage?.totalTokens ?? (usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0),
33017
- durationMs: memberDuration,
33018
- toolCalls,
33019
- errored
33020
- }
33021
- });
33022
- }
33023
- yield {
33024
- type: "agent_end",
33025
- id: crypto.randomUUID(),
33026
- ts: Date.now(),
33027
- sessionId: sessionId2,
33028
- reason: "completed",
33029
- durationMs: 0
33030
- };
33031
- }
33032
- async function* applyCompletionRetry(args) {
33033
- const check2 = checkImplementationCompletion(args.emittedToolNames);
33034
- if (check2.ok)
33035
- return;
33036
- const retryTool = resolveVerifyRetryTool(args.executableNames);
33037
- if (!retryTool) {
33038
- console.warn("[council] implementation verify retry skipped \u2014 no grep_content/bash in registry");
33039
- return;
33040
- }
33041
- if (!shouldRetryMember([retryTool], 0))
33042
- return;
33043
- console.warn(`[council] ${args.agent.id} retrying missing verify tool: ${retryTool}`);
33044
- try {
33045
- const retryGenerator = runRetryTurnForMember({
33046
- agent: args.agent,
33047
- missingToolNames: [retryTool],
33048
- executableTools: args.executableNames,
33049
- userMessage: args.userMessage,
33050
- ragContext: args.config.ragContext,
33051
- workspaceContext: args.config.workspaceContext,
33052
- priorOutputs: args.agentOutputs,
33053
- aiConfig: args.config.aiConfig,
33054
- sessionId: args.sessionId,
33055
- effectiveModel: args.effectiveModel,
33056
- effectiveProvider: args.effectiveProvider,
33057
- eventBus: args.config.eventBus,
33058
- toolRegistry: args.config.tools,
33059
- providerStream: args.config.providerStream,
33060
- runMode: args.config.runMode,
33061
- retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
33062
- languageModule: args.languageModule
33063
- });
33064
- for await (const event of retryGenerator) {
33065
- if (event.type === "tool_execution_start") {
33066
- args.onToolCall();
33067
- args.emittedToolNames.push(event.toolName);
33068
- }
33069
- yield event;
33070
- }
33071
- } catch (retryErr) {
33072
- console.error(`[council] ${args.agent.id} verify retry failed:`, retryErr);
33073
- }
33074
- const after = checkImplementationCompletion(args.emittedToolNames);
33075
- if (!after.ok) {
33076
- console.warn(`[council] ${args.agent.id} still missing verify after retry: ${after.reason}`);
33077
- }
33078
- }
33079
- function buildMotionFixPrompt(violations) {
33080
- const byFile = /* @__PURE__ */ new Map();
33081
- for (const v of violations) {
33082
- const file2 = v.file || "index.html";
33083
- const loc = v.line ? `${file2}:L${v.line}` : file2;
33084
- const list = byFile.get(file2) ?? [];
33085
- list.push(` - ${loc}: ${v.message}`);
33086
- byFile.set(file2, list);
33087
- }
33088
- const blocks = Array.from(byFile.values()).map((lines) => lines.join("\n")).join("\n");
33089
- return `Deterministic verification found ${violations.length} motion violation(s) in the file(s) you just edited. Fix ONLY these \u2014 do not add features, do not rewrite sections, do not touch anything else:
33090
- ${blocks}
33091
-
33092
- Rules: animate ONLY transform and opacity. Replace any box-shadow / background / background-position / filter / color / border-color / width / height / grid-template-rows used in @keyframes or transitions with transform/opacity equivalents (e.g. render a glow via a pseudo-element that scales and fades). For every classList.add('x') in the script, add a matching '.x' CSS rule. Use read_file to see the exact lines, then edit_file. When the listed items are fixed, stop \u2014 no summary.`;
33093
- }
33094
- async function* applyImplementationWriteRetry(args) {
33095
- if (args.check.ok)
33096
- return;
33097
- if (!shouldRetryMember(["write_file"], 0))
33098
- return;
33099
- const statusMsg = `[council] ${args.chairman.id} implementation write retry: ${args.check.missing.join(", ")}`;
33100
- args.onCouncilStatus?.(statusMsg);
33101
- console.warn(statusMsg);
33102
- try {
33103
- const retryGenerator = runRetryTurnForMember({
33104
- agent: args.chairman,
33105
- missingToolNames: ["read_file", "write_file", "edit_file"],
33106
- minPerTool: { read_file: 3, edit_file: 8, write_file: 1 },
33107
- executableTools: args.executableNames,
33108
- userMessage: args.userMessage,
33109
- ragContext: args.config.ragContext,
33110
- workspaceContext: args.config.workspaceContext,
33111
- priorOutputs: args.agentOutputs,
33112
- aiConfig: args.config.aiConfig,
33113
- sessionId: args.sessionId,
33114
- effectiveModel: args.effectiveModel,
33115
- effectiveProvider: args.effectiveProvider,
33116
- eventBus: args.config.eventBus,
33117
- toolRegistry: args.config.tools,
33118
- providerStream: args.config.providerStream,
33119
- runMode: "implementation",
33120
- retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
33121
- languageModule: args.languageModule
33122
- });
33123
- for await (const event of retryGenerator) {
33124
- if (event.type === "tool_execution_start")
33125
- args.onToolCall?.();
33126
- if (event.type === "tool_execution_end" && !event.isError && typeof event.result === "string") {
33127
- try {
33128
- const parsed = JSON.parse(event.result);
33129
- if ((parsed.bytesWritten ?? 0) > 0 || (parsed.occurrencesReplaced ?? 0) > 0) {
33130
- args.onSuccessfulWrite?.();
33131
- }
33132
- } catch {
33133
- if (event.result.includes("bytesWritten") || event.result.includes("occurrencesReplaced")) {
33134
- args.onSuccessfulWrite?.();
33135
- }
33136
- }
33137
- }
33138
- yield event;
33139
- }
33140
- } catch (retryErr) {
33141
- console.error(`[council] ${args.chairman.id} implementation write retry failed:`, retryErr);
33142
- }
33143
- }
33144
- async function* runChairmanDeliveryLoop(args) {
33145
- const maxAttempts = args.maxAttempts ?? MAX_DELIVERY_ATTEMPTS;
33146
- const zelariRoot = `${args.projectRoot}/.zelari`;
33147
- let attempt = 0;
33148
- while (attempt < maxAttempts) {
33149
- const report = runImplementationVerification({
33150
- projectRoot: args.projectRoot,
33151
- zelariRoot
33152
- });
33153
- const blocking = filterDeliveryBlockingFails(report.results);
33154
- if (blocking.length === 0)
33155
- return true;
33156
- if (blocking.some((b) => b.id === "inline-js.budget")) {
33157
- const jsFix = applyInlineJsAutofix(args.projectRoot, report);
33158
- if (jsFix.applied) {
33159
- args.onCouncilStatus?.(`[council] ${args.chairman.id} inline-js autofix: ${jsFix.fixes.join("; ")}`);
33160
- const afterJs = runImplementationVerification({
33161
- projectRoot: args.projectRoot,
33162
- zelariRoot
33163
- });
33164
- if (filterDeliveryBlockingFails(afterJs.results).length === 0)
33165
- return true;
33166
- }
33167
- }
33168
- attempt++;
33169
- const statusMsg = `[council] ${args.chairman.id} delivery pass ${attempt}/${maxAttempts}: ${blocking.map((b) => b.id).join(", ")}`;
33170
- args.onCouncilStatus?.(statusMsg);
33171
- console.warn(statusMsg);
33172
- try {
33173
- const fixGenerator = runRetryTurnForMember({
33174
- agent: args.chairman,
33175
- missingToolNames: ["read_file", "edit_file"],
33176
- minPerTool: { read_file: 3, edit_file: 10 },
33177
- executableTools: args.executableNames,
33178
- userMessage: args.userMessage,
33179
- ragContext: args.config.ragContext,
33180
- workspaceContext: args.config.workspaceContext,
33181
- priorOutputs: args.agentOutputs,
33182
- aiConfig: args.config.aiConfig,
33183
- sessionId: args.sessionId,
33184
- effectiveModel: args.effectiveModel,
33185
- effectiveProvider: args.effectiveProvider,
33186
- eventBus: args.config.eventBus,
33187
- toolRegistry: args.config.tools,
33188
- providerStream: args.config.providerStream,
33189
- runMode: "implementation",
33190
- retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
33191
- languageModule: args.languageModule
33192
- });
33193
- for await (const event of fixGenerator) {
33194
- if (event.type === "tool_execution_start")
33195
- args.onToolCall?.();
33196
- yield event;
33197
- }
33198
- } catch (deliveryErr) {
33199
- console.error(`[council] ${args.chairman.id} delivery pass ${attempt} failed:`, deliveryErr);
33200
- break;
33201
- }
33202
- for (const rel2 of args.changedFiles) {
33203
- for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath: rel2, zelariRoot })) {
33204
- args.changedFiles.add(w.file ?? rel2);
33205
- }
33206
- }
33207
- }
33208
- const finalReport = runImplementationVerification({
33209
- projectRoot: args.projectRoot,
33210
- zelariRoot
33211
- });
33212
- return filterDeliveryBlockingFails(finalReport.results).length === 0;
33213
- }
33214
- async function* replayChairmanTextTools(args) {
33215
- const tools = parseTextToolCalls(args.synthesisText);
33216
- if (tools.length === 0)
33217
- return 0;
33218
- let applied = 0;
33219
- for (const tt of tools) {
33220
- if (tt.name !== "edit_file" && tt.name !== "write_file")
33221
- continue;
33222
- const normalized = normalizeTextToolArgs(tt.name, tt.args);
33223
- const toolCallId = `replay-${crypto.randomUUID().slice(0, 8)}`;
33224
- yield createBrainEvent("tool_execution_start", args.sessionId, {
33225
- toolCallId,
33226
- toolName: tt.name,
33227
- args: normalized,
33228
- ...args.memberId ? { memberId: args.memberId } : {}
33229
- });
33230
- const startMs = Date.now();
33231
- let resultStr = "";
33232
- let isError = false;
33233
- try {
33234
- const result = await args.toolRegistry.invoke(tt.name, normalized, {
33235
- cwd: args.projectRoot,
33236
- sessionId: args.sessionId
33237
- });
33238
- if (result.ok) {
33239
- const val = result.value;
33240
- if (tt.name === "edit_file" && val.occurrencesReplaced === 0) {
33241
- resultStr = `edit_file: no match for oldString (replay)`;
33242
- isError = true;
33243
- } else {
33244
- resultStr = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
33245
- applied += 1;
33424
+ let usage = null;
33425
+ let errored = false;
33426
+ let lastErrorMessage = "";
33427
+ const emittedToolNames = [];
33428
+ const pendingChairmanWrites = /* @__PURE__ */ new Map();
33429
+ let successfulWriteCount = 0;
33430
+ const chairmanViolations = /* @__PURE__ */ new Map();
33431
+ const changedTargetFiles = /* @__PURE__ */ new Set();
33432
+ let chairmanProjectRoot = parseProjectRootFromWorkspaceContext(config2.workspaceContext ?? "");
33433
+ const memberStart = Date.now();
33434
+ try {
33435
+ for await (const event of runHarnessWithAbort(chairmanHarness, config2.signal)) {
33436
+ yield event;
33437
+ if (event.type === "tool_execution_start") {
33438
+ toolCalls += 1;
33439
+ emittedToolNames.push(event.toolName);
33440
+ if (!isDesignPhase && (event.toolName === "write_file" || event.toolName === "edit_file") && typeof event.args.path === "string") {
33441
+ pendingChairmanWrites.set(event.toolCallId, {
33442
+ path: event.args.path,
33443
+ toolName: event.toolName
33444
+ });
33445
+ }
33446
+ }
33447
+ if (!isDesignPhase && event.type === "tool_execution_end") {
33448
+ const pending = pendingChairmanWrites.get(event.toolCallId);
33449
+ if (pending) {
33450
+ pendingChairmanWrites.delete(event.toolCallId);
33451
+ if (!event.isError) {
33452
+ let wrote = pending.toolName === "write_file";
33453
+ if (pending.toolName === "edit_file" && typeof event.result === "string") {
33454
+ try {
33455
+ const parsed = JSON.parse(event.result);
33456
+ wrote = (parsed.occurrencesReplaced ?? 0) > 0;
33457
+ } catch {
33458
+ wrote = false;
33459
+ }
33460
+ }
33461
+ if (wrote) {
33462
+ successfulWriteCount += 1;
33463
+ const projectRoot = parseProjectRootFromWorkspaceContext(config2.workspaceContext);
33464
+ if (projectRoot) {
33465
+ chairmanProjectRoot = projectRoot;
33466
+ changedTargetFiles.add(pending.path);
33467
+ for (const w of runChairmanMicroGate({
33468
+ projectRoot,
33469
+ relPath: pending.path,
33470
+ zelariRoot: `${projectRoot}/.zelari`
33471
+ })) {
33472
+ chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
33473
+ }
33474
+ }
33475
+ }
33476
+ }
33477
+ }
33478
+ }
33479
+ if (event.type === "message_end" && event.usage) {
33480
+ usage = event.usage;
33481
+ }
33482
+ if (event.type === "message_delta") {
33483
+ fullText += event.delta;
33484
+ callbacks.onSynthesisChunk?.(event.delta);
33485
+ callbacks.onAgentChunk?.(chairman, event.delta);
33486
+ }
33487
+ if (event.type === "error") {
33488
+ if (event.severity !== "cancelled" && event.code !== "text_tools_parse_failed") {
33489
+ errored = true;
33490
+ lastErrorMessage = event.message;
33491
+ }
33246
33492
  }
33247
- } else {
33248
- resultStr = result.error;
33249
- isError = true;
33250
33493
  }
33251
33494
  } catch (err) {
33252
- resultStr = err instanceof Error ? err.message : String(err);
33253
- isError = true;
33495
+ console.error(`[council] chairman "${chairman.id}" failed:`, err);
33496
+ errored = true;
33497
+ lastErrorMessage = err instanceof Error ? err.message : String(err);
33254
33498
  }
33255
- yield createBrainEvent("tool_execution_end", args.sessionId, {
33256
- toolCallId,
33257
- result: resultStr,
33258
- isError,
33259
- durationMs: Date.now() - startMs
33260
- });
33261
- }
33262
- return applied;
33263
- }
33264
- async function* runChairmanFixLoop(args) {
33265
- const maxAttempts = args.maxAttempts ?? 3;
33266
- const zelariRoot = `${args.projectRoot}/.zelari`;
33267
- let current = Array.from(args.violations.values());
33268
- let attempt = 0;
33269
- while (current.length > 0 && attempt < maxAttempts) {
33270
- attempt++;
33271
- try {
33272
- const fixGenerator = runRetryTurnForMember({
33273
- agent: args.chairman,
33274
- missingToolNames: ["read_file", "edit_file"],
33275
- minPerTool: { read_file: 2, edit_file: 8 },
33276
- executableTools: args.executableNames,
33277
- userMessage: args.userMessage,
33278
- ragContext: args.config.ragContext,
33279
- workspaceContext: args.config.workspaceContext,
33280
- priorOutputs: args.agentOutputs,
33281
- aiConfig: args.config.aiConfig,
33282
- sessionId: args.sessionId,
33283
- effectiveModel: args.effectiveModel,
33284
- effectiveProvider: args.effectiveProvider,
33285
- eventBus: args.config.eventBus,
33286
- toolRegistry: args.config.tools,
33287
- providerStream: args.config.providerStream,
33288
- runMode: "implementation",
33289
- retryPrompt: buildMotionFixPrompt(current),
33290
- languageModule: args.languageModule
33499
+ if (isDesignPhase && !errored && !isCouncilCancelled(config2.signal)) {
33500
+ const chairmanCheck = enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
33501
+ yield* applyRetryIfMissing({
33502
+ agent: chairman,
33503
+ check: chairmanCheck,
33504
+ requirements: DESIGN_PHASE_REQUIREMENTS[chairman.id],
33505
+ emittedToolNames,
33506
+ executableNames,
33507
+ sessionId: sessionId2,
33508
+ userMessage,
33509
+ agentOutputs,
33510
+ config: config2,
33511
+ effectiveProvider,
33512
+ effectiveModel,
33513
+ onToolCall: () => {
33514
+ toolCalls += 1;
33515
+ },
33516
+ languageModule: councilLanguageModule
33291
33517
  });
33292
- for await (const event of fixGenerator) {
33293
- if (event.type === "tool_execution_start")
33294
- args.onToolCall?.();
33295
- yield event;
33518
+ } else if (isDesignPhase) {
33519
+ enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
33520
+ } else if (!errored && !isCouncilCancelled(config2.signal)) {
33521
+ if (chairmanProjectRoot) {
33522
+ const zelariRoot = `${chairmanProjectRoot}/.zelari`;
33523
+ const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
33524
+ for (const rel2 of spec.targets) {
33525
+ if (!existsSync12(join8(chairmanProjectRoot, rel2)))
33526
+ continue;
33527
+ changedTargetFiles.add(rel2);
33528
+ for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel2, zelariRoot })) {
33529
+ chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
33530
+ }
33531
+ }
33296
33532
  }
33297
- } catch (fixErr) {
33298
- console.error(`[council] chairman fix pass ${attempt} failed:`, fixErr);
33299
- break;
33300
- }
33301
- const rescanned = /* @__PURE__ */ new Map();
33302
- for (const relPath of args.changedFiles) {
33303
- for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath, zelariRoot })) {
33304
- rescanned.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
33533
+ if (fullText.includes("---TOOLS---") && chairmanProjectRoot && config2.tools) {
33534
+ const replayed = yield* replayChairmanTextTools({
33535
+ synthesisText: fullText,
33536
+ projectRoot: chairmanProjectRoot,
33537
+ toolRegistry: config2.tools,
33538
+ sessionId: sessionId2,
33539
+ memberId: chairman.id
33540
+ });
33541
+ successfulWriteCount += replayed;
33542
+ const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
33543
+ const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
33544
+ for (const rel2 of specReplay.targets) {
33545
+ if (!existsSync12(join8(chairmanProjectRoot, rel2)))
33546
+ continue;
33547
+ changedTargetFiles.add(rel2);
33548
+ for (const w of runChairmanMicroGate({
33549
+ projectRoot: chairmanProjectRoot,
33550
+ relPath: rel2,
33551
+ zelariRoot: zelariRootReplay
33552
+ })) {
33553
+ chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ""}`, w);
33554
+ }
33555
+ }
33305
33556
  }
33306
- }
33307
- current = Array.from(rescanned.values());
33308
- }
33309
- }
33310
- function checkMemberToolEmissions(_memberId, emittedToolNames, requirements) {
33311
- if (requirements.length === 0) {
33312
- return { ok: true, missing: [] };
33313
- }
33314
- const counts = /* @__PURE__ */ new Map();
33315
- for (const name of emittedToolNames) {
33316
- counts.set(name, (counts.get(name) ?? 0) + 1);
33317
- }
33318
- const missing = [];
33319
- for (const req of requirements) {
33320
- const got = counts.get(req.name) ?? 0;
33321
- if (got < req.min) {
33322
- missing.push(`${req.name} (got ${got}, need >= ${req.min})`);
33323
- }
33324
- }
33325
- return { ok: missing.length === 0, missing };
33326
- }
33327
- function checkMemberToolEmissionSets(memberId, emittedToolNames, sets) {
33328
- if (sets.length === 0) {
33329
- return { ok: true, missing: [] };
33330
- }
33331
- const results = sets.map((set2) => checkMemberToolEmissions(memberId, emittedToolNames, set2));
33332
- if (results.some((r) => r.ok)) {
33333
- return { ok: true, missing: [] };
33334
- }
33335
- return results[0];
33336
- }
33337
- function enforceDesignPhaseToolEmissions(memberId, emittedToolNames) {
33338
- const sets = DESIGN_PHASE_REQUIREMENT_SETS[memberId];
33339
- if (!sets || sets.length === 0) {
33340
- return { ok: true, missing: [] };
33341
- }
33342
- const result = checkMemberToolEmissionSets(memberId, emittedToolNames, sets);
33343
- if (!result.ok) {
33344
- console.warn(`[council] member "${memberId}" did not emit required tools: ${result.missing.join(", ")}. The downstream .zelari/ deliverable may be incomplete. (A forced retry turn scoped to the missing tools follows; the deterministic complete-design fallback covers any remaining gap.)`);
33345
- }
33346
- return result;
33347
- }
33348
- function shouldRetryMember(missingToolNames, attemptsSoFar) {
33349
- if (missingToolNames.length === 0)
33350
- return false;
33351
- if (attemptsSoFar >= MAX_RETRY_PER_MEMBER)
33352
- return false;
33353
- return true;
33354
- }
33355
- function buildRetryPrompt(missingToolNames) {
33356
- const names = missingToolNames.join(", ");
33357
- return `You did not emit the required workspace tools: ${names}. Call ${names} NOW with concrete arguments. No prose. No search.`;
33358
- }
33359
- async function* runRetryTurnForMember(args) {
33360
- const executableMissing = args.executableTools ? args.missingToolNames.filter((n) => args.executableTools.has(n)) : args.missingToolNames;
33361
- if (executableMissing.length === 0) {
33362
- return [];
33363
- }
33364
- const retryToolNames = executableMissing;
33365
- const retryToolSpecs = getProviderTools(retryToolNames).map((t) => ({
33366
- name: t.function.name,
33367
- description: t.function.description,
33368
- parameters: t.function.parameters
33369
- }));
33370
- const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? "implementation", args.languageModule);
33371
- const retryMessages = [
33372
- ...baseMessages,
33373
- {
33374
- role: "user",
33375
- content: args.retryPrompt ?? buildRetryPrompt(executableMissing)
33376
- }
33377
- ];
33378
- const maxToolCalls = args.minPerTool !== void 0 ? Object.entries(args.minPerTool).filter(([name]) => executableMissing.includes(name)).reduce((sum, [, min]) => sum + min, 0) : retryToolNames.length;
33379
- const retryHarness = new AgentHarness({
33380
- model: args.effectiveModel,
33381
- provider: args.effectiveProvider,
33382
- sessionId: args.sessionId,
33383
- messages: retryMessages,
33384
- tools: retryToolSpecs,
33385
- eventBus: args.eventBus,
33386
- toolRegistry: args.toolRegistry,
33387
- // Budget the retry so the model can satisfy every requirement in
33388
- // ONE tool_calls turn. For createTask min:12 this needs 12 calls.
33389
- maxToolCallsPerTurn: maxToolCalls,
33390
- memberId: args.agent.id,
33391
- memberName: args.agent.name,
33392
- providerStream: (params) => args.providerStream(params)
33393
- });
33394
- const retryEmitted = [];
33395
- for await (const event of retryHarness.run()) {
33396
- if (event.type === "tool_execution_start") {
33397
- retryEmitted.push(event.toolName);
33398
- }
33399
- yield event;
33400
- }
33401
- return retryEmitted;
33402
- }
33403
- async function* applyRetryIfMissing(args) {
33404
- if (args.check.ok)
33405
- return;
33406
- const missingToolNames = args.check.missing.map((m) => m.split(" ")[0]);
33407
- if (!shouldRetryMember(missingToolNames, 0))
33408
- return;
33409
- console.warn(`[council] ${args.agent.id} retrying missing tools: ${missingToolNames.join(", ")}`);
33410
- const minPerTool = {};
33411
- if (args.requirements) {
33412
- for (const req of args.requirements) {
33413
- if (missingToolNames.includes(req.name)) {
33414
- minPerTool[req.name] = req.min;
33557
+ if (chairmanProjectRoot) {
33558
+ const deliveryCheck = checkImplementationDelivery(successfulWriteCount, countEmittedWriteTools(emittedToolNames));
33559
+ if (!deliveryCheck.ok) {
33560
+ yield* applyImplementationWriteRetry({
33561
+ chairman,
33562
+ check: deliveryCheck,
33563
+ sessionId: sessionId2,
33564
+ userMessage,
33565
+ agentOutputs,
33566
+ config: config2,
33567
+ effectiveProvider,
33568
+ effectiveModel,
33569
+ executableNames,
33570
+ onToolCall: () => {
33571
+ toolCalls += 1;
33572
+ },
33573
+ onSuccessfulWrite: () => {
33574
+ successfulWriteCount += 1;
33575
+ },
33576
+ onCouncilStatus: callbacks.onCouncilStatus,
33577
+ languageModule: councilLanguageModule
33578
+ });
33579
+ }
33580
+ }
33581
+ if (chairmanViolations.size > 0 && chairmanProjectRoot) {
33582
+ yield* runChairmanFixLoop({
33583
+ chairman,
33584
+ violations: chairmanViolations,
33585
+ changedFiles: changedTargetFiles,
33586
+ projectRoot: chairmanProjectRoot,
33587
+ executableNames,
33588
+ sessionId: sessionId2,
33589
+ userMessage,
33590
+ agentOutputs,
33591
+ config: config2,
33592
+ effectiveProvider,
33593
+ effectiveModel,
33594
+ onToolCall: () => {
33595
+ toolCalls += 1;
33596
+ },
33597
+ languageModule: councilLanguageModule
33598
+ });
33599
+ }
33600
+ if (chairmanProjectRoot) {
33601
+ yield* runChairmanDeliveryLoop({
33602
+ chairman,
33603
+ projectRoot: chairmanProjectRoot,
33604
+ changedFiles: changedTargetFiles,
33605
+ executableNames,
33606
+ sessionId: sessionId2,
33607
+ userMessage,
33608
+ agentOutputs,
33609
+ config: config2,
33610
+ effectiveProvider,
33611
+ effectiveModel,
33612
+ onToolCall: () => {
33613
+ toolCalls += 1;
33614
+ },
33615
+ onCouncilStatus: callbacks.onCouncilStatus,
33616
+ languageModule: councilLanguageModule
33617
+ });
33415
33618
  }
33416
33619
  }
33417
- }
33418
- try {
33419
- const retryGenerator = runRetryTurnForMember({
33420
- agent: args.agent,
33421
- missingToolNames,
33422
- minPerTool,
33423
- executableTools: args.executableNames,
33424
- userMessage: args.userMessage,
33425
- ragContext: args.config.ragContext,
33426
- workspaceContext: args.config.workspaceContext,
33427
- priorOutputs: args.agentOutputs,
33428
- aiConfig: args.config.aiConfig,
33429
- sessionId: args.sessionId,
33430
- effectiveModel: args.effectiveModel,
33431
- effectiveProvider: args.effectiveProvider,
33432
- eventBus: args.config.eventBus,
33433
- toolRegistry: args.config.tools,
33434
- providerStream: args.config.providerStream,
33435
- runMode: args.config.runMode,
33436
- languageModule: args.languageModule
33620
+ const memberDuration = Date.now() - memberStart;
33621
+ const finalSynthesis = errored && fullText.length === 0 ? `[Chairman synthesis failed: ${lastErrorMessage || "unknown error"}]` : fullText;
33622
+ callbacks.onSynthesisDone?.(finalSynthesis, void 0, void 0);
33623
+ emitMemberCost({
33624
+ memberId: chairman.id,
33625
+ name: chairman.name,
33626
+ usage,
33627
+ durationMs: memberDuration,
33628
+ toolCalls,
33629
+ errored
33437
33630
  });
33438
- for await (const event of retryGenerator) {
33439
- if (event.type === "tool_execution_start") {
33440
- args.onToolCall();
33441
- args.emittedToolNames.push(event.toolName);
33631
+ yield createBrainEvent("member_cost", sessionId2, {
33632
+ cost: {
33633
+ memberId: chairman.id,
33634
+ name: chairman.name,
33635
+ promptTokens: usage?.promptTokens ?? 0,
33636
+ completionTokens: usage?.completionTokens ?? 0,
33637
+ totalTokens: usage?.totalTokens ?? (usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0),
33638
+ durationMs: memberDuration,
33639
+ toolCalls,
33640
+ errored
33442
33641
  }
33443
- yield event;
33444
- }
33445
- } catch (retryErr) {
33446
- console.error(`[council] ${args.agent.id} retry failed:`, retryErr);
33642
+ });
33447
33643
  }
33448
- enforceDesignPhaseToolEmissions(args.agent.id, args.emittedToolNames);
33644
+ yield {
33645
+ type: "agent_end",
33646
+ id: crypto.randomUUID(),
33647
+ ts: Date.now(),
33648
+ sessionId: sessionId2,
33649
+ reason: isCouncilCancelled(config2.signal) ? "cancelled" : "completed",
33650
+ durationMs: 0
33651
+ };
33449
33652
  }
33450
- var NON_RETRY_AGENTS, QUESTION_MARKER, QUESTION_END_MARKER, MUTATING_PROJECT_TOOLS, MAX_DELIVERY_ATTEMPTS, DESIGN_PHASE_REQUIREMENT_SETS, DESIGN_PHASE_REQUIREMENTS, MAX_RETRY_PER_MEMBER;
33451
33653
  var init_councilApi = __esm({
33452
33654
  "packages/core/dist/agents/councilApi.js"() {
33453
33655
  "use strict";
33454
33656
  init_roles();
33455
33657
  init_toolSchemas();
33456
33658
  init_systemPromptBuilder();
33457
- init_tools();
33458
33659
  init_languagePolicy();
33459
- init_secrecyPolicy();
33460
33660
  init_events();
33461
33661
  init_AgentHarness();
33462
- init_modeBanners();
33463
33662
  init_runMode();
33464
33663
  init_microGate();
33465
- init_completion();
33466
33664
  init_nfrSpecWarn();
33467
33665
  init_runChecks();
33468
33666
  init_implementationDelivery();
33469
- init_inlineJsAutofix();
33470
- NON_RETRY_AGENTS = /* @__PURE__ */ new Set([]);
33471
- QUESTION_MARKER = "---QUESTION---";
33472
- QUESTION_END_MARKER = "---END---";
33473
- MUTATING_PROJECT_TOOLS = ["write_file", "edit_file"];
33474
- MAX_DELIVERY_ATTEMPTS = 2;
33475
- DESIGN_PHASE_REQUIREMENT_SETS = {
33476
- nettun: [
33477
- [{ name: "createPlan", min: 1 }],
33478
- [
33479
- { name: "createPhase", min: 3 },
33480
- { name: "createTask", min: 6 },
33481
- { name: "createMilestone", min: 1 }
33482
- ]
33483
- ],
33484
- geryon: [
33485
- [{ name: "createDocument", min: 3 }]
33486
- ],
33487
- pluton: [
33488
- [{ name: "createDocument", min: 1 }]
33489
- ],
33490
- minos: [
33491
- [{ name: "createDocument", min: 1 }]
33492
- ],
33493
- lucifer: [
33494
- [{ name: "createDocument", min: 1 }]
33495
- ]
33496
- };
33497
- DESIGN_PHASE_REQUIREMENTS = Object.fromEntries(Object.entries(DESIGN_PHASE_REQUIREMENT_SETS).map(([id3, sets]) => [id3, sets[0]]));
33498
- MAX_RETRY_PER_MEMBER = 1;
33667
+ init_types7();
33668
+ init_cancel();
33669
+ init_types7();
33670
+ init_outputCleaning();
33671
+ init_outputCleaning();
33672
+ init_memberMessages();
33673
+ init_memberMessages();
33674
+ init_chairmanDelivery();
33675
+ init_chairmanDelivery();
33676
+ init_chairmanFixLoop();
33677
+ init_chairmanFixLoop();
33678
+ init_toolEmission();
33679
+ init_toolEmission();
33680
+ init_retryTurn();
33681
+ init_retryTurn();
33499
33682
  }
33500
33683
  });
33501
33684
 
@@ -33632,7 +33815,7 @@ var init_missionBrief = __esm({
33632
33815
  });
33633
33816
 
33634
33817
  // packages/core/dist/council/verification/types.js
33635
- var init_types7 = __esm({
33818
+ var init_types8 = __esm({
33636
33819
  "packages/core/dist/council/verification/types.js"() {
33637
33820
  "use strict";
33638
33821
  }
@@ -33836,7 +34019,7 @@ var init_autofix = __esm({
33836
34019
  var init_verification2 = __esm({
33837
34020
  "packages/core/dist/council/verification/index.js"() {
33838
34021
  "use strict";
33839
- init_types7();
34022
+ init_types8();
33840
34023
  init_runChecks();
33841
34024
  init_honesty();
33842
34025
  init_parseCssMotion();
@@ -33854,7 +34037,7 @@ var init_verification2 = __esm({
33854
34037
  });
33855
34038
 
33856
34039
  // packages/core/dist/council/lessons/types.js
33857
- var init_types8 = __esm({
34040
+ var init_types9 = __esm({
33858
34041
  "packages/core/dist/council/lessons/types.js"() {
33859
34042
  "use strict";
33860
34043
  }
@@ -34107,7 +34290,7 @@ var init_recallLessons = __esm({
34107
34290
  var init_lessons = __esm({
34108
34291
  "packages/core/dist/council/lessons/index.js"() {
34109
34292
  "use strict";
34110
- init_types8();
34293
+ init_types9();
34111
34294
  init_io();
34112
34295
  init_isAnswerLeak();
34113
34296
  init_signatures();
@@ -34117,7 +34300,7 @@ var init_lessons = __esm({
34117
34300
  });
34118
34301
 
34119
34302
  // packages/core/dist/council/completion/types.js
34120
- var init_types9 = __esm({
34303
+ var init_types10 = __esm({
34121
34304
  "packages/core/dist/council/completion/types.js"() {
34122
34305
  "use strict";
34123
34306
  }
@@ -34198,7 +34381,7 @@ var init_buildCompletion = __esm({
34198
34381
  var init_completion2 = __esm({
34199
34382
  "packages/core/dist/council/completion/index.js"() {
34200
34383
  "use strict";
34201
- init_types9();
34384
+ init_types10();
34202
34385
  init_buildCompletion();
34203
34386
  }
34204
34387
  });
@@ -34489,6 +34672,7 @@ __export(council_exports, {
34489
34672
  shouldRetryMember: () => shouldRetryMember,
34490
34673
  slugify: () => slugify2,
34491
34674
  stripClarificationProtocol: () => stripClarificationProtocol,
34675
+ stripQuestionBlocks: () => stripQuestionBlocks,
34492
34676
  swapMembers: () => swapMembers,
34493
34677
  systemMessagesFromSplit: () => systemMessagesFromSplit,
34494
34678
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
@@ -34521,7 +34705,7 @@ var init_council = __esm({
34521
34705
 
34522
34706
  // packages/core/dist/memory/types.js
34523
34707
  var MEMORY_SCHEMA_VERSION, MEMORY_KINDS, MEMORY_STATUSES, MEMORY_RELATIONS, MEMORY_VISIBILITIES;
34524
- var init_types10 = __esm({
34708
+ var init_types11 = __esm({
34525
34709
  "packages/core/dist/memory/types.js"() {
34526
34710
  "use strict";
34527
34711
  MEMORY_SCHEMA_VERSION = 1;
@@ -34572,7 +34756,7 @@ var init_schemas3 = __esm({
34572
34756
  "packages/core/dist/memory/schemas.js"() {
34573
34757
  "use strict";
34574
34758
  init_zod();
34575
- init_types10();
34759
+ init_types11();
34576
34760
  isoDate = external_exports.string().datetime({ offset: true });
34577
34761
  unit = external_exports.number().finite().min(0).max(1);
34578
34762
  metadata = external_exports.record(external_exports.string(), external_exports.unknown()).refine((value) => {
@@ -35864,7 +36048,7 @@ var init_noop = __esm({
35864
36048
  var init_memory = __esm({
35865
36049
  "packages/core/dist/memory/index.js"() {
35866
36050
  "use strict";
35867
- init_types10();
36051
+ init_types11();
35868
36052
  init_schemas3();
35869
36053
  init_scoring();
35870
36054
  init_policies();
@@ -36586,7 +36770,7 @@ var init_personas = __esm({
36586
36770
 
36587
36771
  // packages/core/dist/kraken/runtime/types.js
36588
36772
  var PlanError;
36589
- var init_types11 = __esm({
36773
+ var init_types12 = __esm({
36590
36774
  "packages/core/dist/kraken/runtime/types.js"() {
36591
36775
  "use strict";
36592
36776
  PlanError = class extends Error {
@@ -36684,7 +36868,7 @@ var MAX_BUNDLE_BYTES, FORBIDDEN_TOKENS;
36684
36868
  var init_sandbox = __esm({
36685
36869
  "packages/core/dist/kraken/runtime/sandbox.js"() {
36686
36870
  "use strict";
36687
- init_types11();
36871
+ init_types12();
36688
36872
  MAX_BUNDLE_BYTES = 256 * 1024;
36689
36873
  FORBIDDEN_TOKENS = [
36690
36874
  /\bprocess\b/,
@@ -36747,7 +36931,7 @@ var init_runner = __esm({
36747
36931
  "use strict";
36748
36932
  init_verdict();
36749
36933
  init_personas();
36750
- init_types11();
36934
+ init_types12();
36751
36935
  DEFAULT_MAX_TENTACLES = 200;
36752
36936
  DEFAULT_PLAN_TIMEOUT_MS = 30 * 6e4;
36753
36937
  ScriptRunner = class {
@@ -36931,7 +37115,7 @@ var init_runner = __esm({
36931
37115
  var init_runtime2 = __esm({
36932
37116
  "packages/core/dist/kraken/runtime/index.js"() {
36933
37117
  "use strict";
36934
- init_types11();
37118
+ init_types12();
36935
37119
  init_sandbox();
36936
37120
  init_runner();
36937
37121
  }
@@ -38031,7 +38215,7 @@ var CORE_VERSION;
38031
38215
  var init_version = __esm({
38032
38216
  "packages/core/dist/version.js"() {
38033
38217
  "use strict";
38034
- CORE_VERSION = "2.33.1";
38218
+ CORE_VERSION = "2.34.1";
38035
38219
  }
38036
38220
  });
38037
38221
 
@@ -38543,6 +38727,7 @@ __export(dist_exports, {
38543
38727
  strictBuildGate: () => strictBuildGate,
38544
38728
  stripAnsi: () => stripAnsi,
38545
38729
  stripClarificationProtocol: () => stripClarificationProtocol,
38730
+ stripQuestionBlocks: () => stripQuestionBlocks,
38546
38731
  swapMembers: () => swapMembers,
38547
38732
  systemMessagesFromSplit: () => systemMessagesFromSplit,
38548
38733
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
@@ -44033,6 +44218,7 @@ __export(krakenModel_exports, {
44033
44218
  inferModelFamily: () => inferModelFamily,
44034
44219
  isCheapModelId: () => isCheapModelId,
44035
44220
  isKrakenAutoModelEnabled: () => isKrakenAutoModelEnabled,
44221
+ isUnknownModelError: () => isUnknownModelError,
44036
44222
  parseQualifiedModelRef: () => parseQualifiedModelRef,
44037
44223
  pickCheapModel: () => pickCheapModel,
44038
44224
  pickDifferentFamily: () => pickDifferentFamily,
@@ -44144,6 +44330,12 @@ function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {})
44144
44330
  }
44145
44331
  return parentModel;
44146
44332
  }
44333
+ function isUnknownModelError(message) {
44334
+ if (!message) return false;
44335
+ const m = message.toLowerCase();
44336
+ if (!/model/.test(m)) return false;
44337
+ return /http\s*404/.test(m) || /not-found/.test(m) || /not_found/.test(m) || /does not exist/.test(m) || /unknown model/.test(m) || /model_not_found/.test(m);
44338
+ }
44147
44339
  function resolveKrakenPlannerModel(parentModel, env = process.env) {
44148
44340
  const specific = env.ZELARI_KRAKEN_PLANNER_MODEL?.trim();
44149
44341
  if (specific) return specific;
@@ -45943,6 +46135,12 @@ ${failed.error ?? "unknown error"}`,
45943
46135
  // src/cli/tools/taskTool.ts
45944
46136
  import { existsSync as existsSync26 } from "node:fs";
45945
46137
  import { randomUUID as randomUUID4 } from "node:crypto";
46138
+ function permissionsForTaskAgent(agent) {
46139
+ const kind2 = agent ?? "explore";
46140
+ if (kind2 === "general") return ["read", "write", "execute", "network"];
46141
+ if (kind2 === "verify") return ["read", "execute", "network"];
46142
+ return ["read"];
46143
+ }
45946
46144
  function resetTaskSpawnCount() {
45947
46145
  const g = globalThis;
45948
46146
  g.__zelariTaskSpawnCount = 0;
@@ -46009,6 +46207,27 @@ ${findings || "(the reviewer reported FAIL without detail)"}`;
46009
46207
  async function runAutoVerifyAfterGeneral(opts) {
46010
46208
  const g = globalThis;
46011
46209
  g.__zelariGeneralVerifyDebt = { description: opts.original.description };
46210
+ const emitVerifyPhase = (detail2, ok) => {
46211
+ const agentId = opts.general.agentId;
46212
+ if (agentId) {
46213
+ opts.deps.onTentacleEvent?.({
46214
+ type: "agent_status",
46215
+ agentId,
46216
+ status: "running",
46217
+ message: detail2,
46218
+ id: randomUUID4(),
46219
+ sessionId: opts.sessionId,
46220
+ ts: Date.now()
46221
+ });
46222
+ }
46223
+ appendKrakenRadio(opts.parentCwd, opts.sessionId, {
46224
+ kind: "progress",
46225
+ agent: "verify",
46226
+ description: `verify: ${opts.original.description}`,
46227
+ detail: detail2,
46228
+ ...ok === void 0 ? {} : { ok }
46229
+ });
46230
+ };
46012
46231
  const inheritedCwd = opts.general.worktreePath && existsSync26(opts.general.worktreePath) ? opts.general.worktreePath : void 0;
46013
46232
  const runVerify = (label) => runTentacle({
46014
46233
  deps: opts.deps,
@@ -46025,6 +46244,7 @@ async function runAutoVerifyAfterGeneral(opts) {
46025
46244
  sessionId: opts.sessionId,
46026
46245
  ...opts.signal ? { signal: opts.signal } : {}
46027
46246
  });
46247
+ emitVerifyPhase("verifying\u2026");
46028
46248
  let verify = await runVerify(`verify: ${opts.original.description}`);
46029
46249
  let verdict = verify.ok ? parseVerifyVerdict(verify.result).verdict : "unknown";
46030
46250
  let findings = verify.ok ? parseVerifyVerdict(verify.result).findings : "";
@@ -46072,10 +46292,15 @@ async function runAutoVerifyAfterGeneral(opts) {
46072
46292
  }
46073
46293
  if (verdict === "pass") {
46074
46294
  g.__zelariGeneralVerifyDebt = null;
46295
+ emitVerifyPhase("verify PASS", true);
46075
46296
  return `
46076
46297
 
46077
46298
  [kraken:auto-verify] verify PASS \u2014 general\u21D2verify obligation satisfied.`;
46078
46299
  }
46300
+ emitVerifyPhase(
46301
+ verdict === "fail" ? "verify FAIL" : verify.ok ? "verify unknown" : "verify failed",
46302
+ verdict === "fail" || !verify.ok ? false : void 0
46303
+ );
46079
46304
  const detail = verdict === "fail" ? `verify FAIL unresolved (rework budget spent): ${findings || "no findings reported"}` : verify.ok ? "verify produced no parseable VERDICT \u2014 unverified" : `verify tentacle failed: ${verify.error}`;
46080
46305
  g.__zelariGeneralVerifyDebt = { description: opts.original.description, detail };
46081
46306
  appendKrakenRadio(opts.parentCwd, opts.sessionId, {
@@ -46231,6 +46456,11 @@ async function runSubAgent(harness, opts = {}) {
46231
46456
  signal?.removeEventListener("abort", onAbort);
46232
46457
  }
46233
46458
  }
46459
+ function shortWorktreeCaption(p3) {
46460
+ const parts = p3.split(/[\\/]/).filter(Boolean);
46461
+ const short2 = parts.length > 2 ? `.../${parts.slice(-2).join("/")}` : p3;
46462
+ return short2.length > 50 ? `...${short2.slice(-47)}` : short2;
46463
+ }
46234
46464
  async function runTentacle(opts) {
46235
46465
  const { deps, args, agent, thoroughness, parentCwd, sessionId: sessionId2 } = opts;
46236
46466
  const started = Date.now();
@@ -46327,6 +46557,18 @@ async function runTentacle(opts) {
46327
46557
  ts: Date.now()
46328
46558
  });
46329
46559
  emitActivity({ type: "agent_status", agentId: liveId, status: "running", ts: Date.now() });
46560
+ const emitPhase = (message) => {
46561
+ emitActivity({ type: "agent_status", agentId: liveId, status: "running", message, ts: Date.now() });
46562
+ appendKrakenRadio(parentCwd, sessionId2, {
46563
+ kind: "progress",
46564
+ agent,
46565
+ thoroughness,
46566
+ description: args.description,
46567
+ detail: message
46568
+ });
46569
+ };
46570
+ emitPhase(`phase: ${agent}`);
46571
+ if (worktree) emitPhase(`worktree: ${shortWorktreeCaption(worktree.path)}`);
46330
46572
  const taskUserContent = buildTaskUserPrompt({
46331
46573
  prompt: args.prompt,
46332
46574
  scope: args.scope,
@@ -46380,17 +46622,45 @@ ${taskUserContent}`,
46380
46622
  };
46381
46623
  }
46382
46624
  const startedTools = /* @__PURE__ */ new Map();
46383
- const { result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46625
+ const onHarnessEvent = (ev) => {
46626
+ if (ev.type === "tool_execution_start") {
46627
+ startedTools.set(ev.toolCallId, ev.toolName);
46628
+ emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: ev.toolName, status: "started", ...ev.args ? { summary: toolCommandHint(ev.args) } : {}, ts: Date.now() });
46629
+ } else if (ev.type === "tool_execution_end") {
46630
+ emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: startedTools.get(ev.toolCallId) ?? "unknown", status: ev.isError ? "failed" : "completed", durationMs: ev.durationMs, ts: Date.now() });
46631
+ }
46632
+ };
46633
+ let { result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46384
46634
  ...opts.signal ? { signal: opts.signal } : {},
46385
- onEvent: (ev) => {
46386
- if (ev.type === "tool_execution_start") {
46387
- startedTools.set(ev.toolCallId, ev.toolName);
46388
- emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: ev.toolName, status: "started", ...ev.args ? { summary: toolCommandHint(ev.args) } : {}, ts: Date.now() });
46389
- } else if (ev.type === "tool_execution_end") {
46390
- emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: startedTools.get(ev.toolCallId) ?? "unknown", status: ev.isError ? "failed" : "completed", durationMs: ev.durationMs, ts: Date.now() });
46635
+ onEvent: onHarnessEvent
46636
+ });
46637
+ if (!aborted2 && !result && sub.fallback && sub.fallback.model !== sub.model) {
46638
+ const { isUnknownModelError: isUnknownModelError2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
46639
+ if (isUnknownModelError2(error51)) {
46640
+ emitPhase(`model ${sub.model} unavailable \u2014 retrying with ${sub.fallback.model}`);
46641
+ const retryConfig = {
46642
+ ...config2,
46643
+ model: sub.fallback.model,
46644
+ provider: sub.fallback.provider,
46645
+ providerStream: sub.fallback.providerStream
46646
+ };
46647
+ try {
46648
+ harness = deps.harnessFactory ? deps.harnessFactory(retryConfig) : new (await Promise.resolve().then(() => (init_harness(), harness_exports))).AgentHarness(retryConfig);
46649
+ const retry = await runSubAgent(harness, {
46650
+ ...opts.signal ? { signal: opts.signal } : {},
46651
+ onEvent: onHarnessEvent
46652
+ });
46653
+ result = retry.result;
46654
+ error51 = retry.error;
46655
+ aborted2 = retry.aborted;
46656
+ usage = retry.usage;
46657
+ toolTrace = retry.toolTrace;
46658
+ sub = { ...sub, model: sub.fallback.model, provider: sub.fallback.provider };
46659
+ } catch (err) {
46660
+ error51 = err instanceof Error ? err.message : String(err);
46391
46661
  }
46392
46662
  }
46393
- });
46663
+ }
46394
46664
  const durationMs = Date.now() - started;
46395
46665
  if (aborted2) {
46396
46666
  if (worktree && !shouldKeepWorktree()) await cleanupKrakenWorktree(worktree);
@@ -46441,6 +46711,7 @@ worktree deferred: branch=${worktree.branch} path=${worktree.path} (executor mer
46441
46711
  } else if (worktree) {
46442
46712
  let merge2 = null;
46443
46713
  if (!kept && isKrakenWorktreeAutoMergeEnabled()) {
46714
+ emitPhase("merging\u2026");
46444
46715
  try {
46445
46716
  merge2 = await mergeKrakenWorktree(
46446
46717
  worktree,
@@ -46454,6 +46725,7 @@ worktree deferred: branch=${worktree.branch} path=${worktree.path} (executor mer
46454
46725
  message: `merge threw: ${err instanceof Error ? err.message : String(err)}`
46455
46726
  };
46456
46727
  }
46728
+ emitPhase(merge2.ok ? "merge ok" : "merge failed");
46457
46729
  } else if (!kept) {
46458
46730
  await cleanupKrakenWorktree(worktree);
46459
46731
  }
@@ -46517,6 +46789,7 @@ ${verifyHintForGeneral(args.acceptance)}`;
46517
46789
  ok: true,
46518
46790
  agent,
46519
46791
  thoroughness,
46792
+ agentId: liveId,
46520
46793
  model: sub.model,
46521
46794
  result,
46522
46795
  footer,
@@ -53952,6 +54225,7 @@ function createBuiltinToolRegistry(options = {}) {
53952
54225
  // registry's own policy (permPolicy above) — they can never
53953
54226
  // exceed it.
53954
54227
  parentPolicy: permPolicy,
54228
+ ...options.onPermissionAsk ? { onPermissionAsk: options.onPermissionAsk } : {},
53955
54229
  ...options.subAgentProvider ? { provider: options.subAgentProvider } : {},
53956
54230
  ...options.subAgentModel ? { model: options.subAgentModel } : {}
53957
54231
  }),
@@ -54051,12 +54325,13 @@ function taskAgentToProfile(agent) {
54051
54325
  return "explore";
54052
54326
  }
54053
54327
  function createKrakenSubAgentContextFactory(opts) {
54054
- const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride, parentPolicy } = opts;
54328
+ const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride, parentPolicy, onPermissionAsk } = opts;
54055
54329
  return async ({ agent, cwd: subCwd }) => {
54056
54330
  const cfg = providerOverride ? await providerConfigFor(providerOverride) : await providerFromEnv();
54057
54331
  if (!cfg) return null;
54058
54332
  const { resolveKrakenSubModel: resolveKrakenSubModel2, parseQualifiedModelRef: parseQualifiedModelRef2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
54059
- const resolvedModel = resolveKrakenSubModel2(agent, modelOverride || cfg.model);
54333
+ const parentModel = modelOverride || cfg.model;
54334
+ const resolvedModel = resolveKrakenSubModel2(agent, parentModel);
54060
54335
  let effCfg = cfg;
54061
54336
  let model = resolvedModel;
54062
54337
  const ref = parseQualifiedModelRef2(resolvedModel);
@@ -54085,6 +54360,7 @@ function createKrakenSubAgentContextFactory(opts) {
54085
54360
  diagnostics: false,
54086
54361
  lspProvider: null,
54087
54362
  permissionPolicy: effectiveSubPolicy,
54363
+ ...onPermissionAsk ? { onPermissionAsk } : {},
54088
54364
  // P0.5: the tentacle's agent identity drives per-agent policy rules.
54089
54365
  policyAgent: agent
54090
54366
  });
@@ -54092,6 +54368,13 @@ function createKrakenSubAgentContextFactory(opts) {
54092
54368
  providerStream: buildProviderStream(subCfg),
54093
54369
  model,
54094
54370
  provider: subCfg.providerId,
54371
+ ...model !== parentModel ? {
54372
+ fallback: {
54373
+ model: parentModel,
54374
+ provider: cfg.providerId,
54375
+ providerStream: buildProviderStream({ ...cfg, model: parentModel })
54376
+ }
54377
+ } : {},
54095
54378
  registry: subRegistry,
54096
54379
  tools: subRegistry.toOpenAITools().map((t) => ({
54097
54380
  name: t.function.name,
@@ -54111,11 +54394,14 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54111
54394
  return {
54112
54395
  ...original,
54113
54396
  execute: async (input, ctx) => {
54114
- const decision = resolveToolPermission(original.name, required2, policy);
54397
+ const requiredNow = original.name === "task" ? permissionsForTaskAgent(
54398
+ input?.agent
54399
+ ) : required2;
54400
+ const decision = resolveToolPermission(original.name, requiredNow, policy);
54115
54401
  const rule = agentLayers ? matchAgentPolicyRuleLayered(
54116
54402
  agentLayers,
54117
54403
  precedence,
54118
- required2,
54404
+ requiredNow,
54119
54405
  input ?? {},
54120
54406
  root ?? process.cwd()
54121
54407
  ) : null;
@@ -54127,25 +54413,25 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54127
54413
  root ?? process.cwd()
54128
54414
  ) : void 0;
54129
54415
  const contractRule = matchContractCapabilityRule(
54130
- required2,
54416
+ requiredNow,
54131
54417
  input ?? {},
54132
54418
  root ?? process.cwd()
54133
54419
  );
54134
54420
  let action = intersectEffects(mergeRuleEffect(decision.action, rule), claims?.effect, contractRule?.effect);
54135
54421
  let actionReason = decision.reason;
54136
- if (action !== "deny" && (required2.includes("write") || required2.includes("execute"))) {
54422
+ if (action !== "deny" && (requiredNow.includes("write") || requiredNow.includes("execute"))) {
54137
54423
  const provHit = provenanceMatchIn(JSON.stringify(input ?? {}));
54138
- if (provHit && provenanceAppliesTo(provHit.source, required2)) {
54424
+ if (provHit && provenanceAppliesTo(provHit.source, requiredNow)) {
54139
54425
  const provNote = `[provenance] args embed non-user ${provHit.source} content (via ${provHit.tool})`;
54140
54426
  if (action === "allow") {
54141
54427
  action = "ask";
54142
- actionReason = `${provNote} \u2014 confirm before ${required2.join("+")}`;
54428
+ actionReason = `${provNote} \u2014 confirm before ${requiredNow.join("+")}`;
54143
54429
  } else {
54144
54430
  actionReason = `${decision.reason} \xB7 ${provNote}`;
54145
54431
  }
54146
54432
  }
54147
54433
  }
54148
- if (action === "allow" && required2.includes("execute") && activePermissionPreset() !== "yolo" && !isSessionGranted(original.name, required2)) {
54434
+ if (action === "allow" && requiredNow.includes("execute") && activePermissionPreset() !== "yolo" && !isSessionGranted(original.name, requiredNow)) {
54149
54435
  const destructiveHit = destructiveCommandHit(input ?? {});
54150
54436
  if (destructiveHit) {
54151
54437
  action = "ask";
@@ -54188,7 +54474,7 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54188
54474
  }
54189
54475
  }
54190
54476
  const outcome = await original.execute(input, ctx);
54191
- recordResultForProvenance(original.name, required2, outcome);
54477
+ recordResultForProvenance(original.name, requiredNow, outcome);
54192
54478
  return outcome;
54193
54479
  }
54194
54480
  };
@@ -55524,6 +55810,25 @@ var init_spineTelemetry = __esm({
55524
55810
  }
55525
55811
  });
55526
55812
 
55813
+ // src/cli/hooks/askUserTimeout.ts
55814
+ function askUserTimeoutMs() {
55815
+ const raw = process.env.ZELARI_ASK_USER_TIMEOUT_MS?.trim();
55816
+ if (!raw) return 3e5;
55817
+ const n = Number.parseInt(raw, 10);
55818
+ if (!Number.isFinite(n) || n < 0) return 3e5;
55819
+ return n;
55820
+ }
55821
+ function armPickerTimeout(onFire, ms) {
55822
+ if (ms <= 0) return () => void 0;
55823
+ const id3 = setTimeout(onFire, ms);
55824
+ return () => clearTimeout(id3);
55825
+ }
55826
+ var init_askUserTimeout = __esm({
55827
+ "src/cli/hooks/askUserTimeout.ts"() {
55828
+ "use strict";
55829
+ }
55830
+ });
55831
+
55527
55832
  // src/cli/state/fileStateStore.ts
55528
55833
  import { createHash as createHash17, randomUUID as randomUUID5 } from "node:crypto";
55529
55834
  import { promises as fs26 } from "node:fs";
@@ -60910,7 +61215,8 @@ async function* dispatchCouncil(userMessage, options) {
60910
61215
  maxToolLoopIterations: options.maxToolLoopIterations,
60911
61216
  maxToolLoopHardCap: options.maxToolLoopHardCap,
60912
61217
  skipSpecialists: options.skipSpecialists,
60913
- feedbackStore: options.feedbackStore
61218
+ feedbackStore: options.feedbackStore,
61219
+ signal: options.signal
60914
61220
  };
60915
61221
  if (!options.disableWorkspaceTools) {
60916
61222
  const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
@@ -62668,6 +62974,13 @@ async function runZelariMission(userMessage, brief, deps) {
62668
62974
  let forcePivot = false;
62669
62975
  const missionStartMs = now().getTime();
62670
62976
  while (true) {
62977
+ if (deps.signal?.aborted) {
62978
+ state3.status = "cancelled";
62979
+ state3.updatedAt = now().toISOString();
62980
+ await persist();
62981
+ deps.emit("[zelari] missione cancellata.");
62982
+ return state3;
62983
+ }
62671
62984
  const runMode = pendingDesign ? "design-phase" : "implementation";
62672
62985
  if (runMode === "implementation") {
62673
62986
  deps.onMissionPhase?.("build", `impl-${implStep + 1}`);
@@ -62724,6 +63037,13 @@ async function runZelariMission(userMessage, brief, deps) {
62724
63037
  );
62725
63038
  return state3;
62726
63039
  }
63040
+ if (deps.signal?.aborted) {
63041
+ state3.status = "cancelled";
63042
+ state3.updatedAt = now().toISOString();
63043
+ await persist();
63044
+ deps.emit("[zelari] missione cancellata.");
63045
+ return state3;
63046
+ }
62727
63047
  if (typeof result.costUsd === "number") cumulativeCostUsd += result.costUsd;
62728
63048
  if (typeof result.costTokens === "number") cumulativeTokens += result.costTokens;
62729
63049
  await deps.memory.add(
@@ -65842,11 +66162,12 @@ var init_facts = __esm({
65842
66162
  });
65843
66163
 
65844
66164
  // src/cli/utils/streamScrub.ts
65845
- function createStreamScrubber2() {
66165
+ function createStreamScrubber2(opts = {}) {
66166
+ const stripQuestion = opts.stripQuestion !== false;
65846
66167
  let rawBuf = "";
65847
66168
  let emittedLen = 0;
65848
66169
  const snapshot = () => {
65849
- const cleaned = cleanAgentContent(rawBuf);
66170
+ const cleaned = cleanAgentContent(rawBuf, { stripQuestion });
65850
66171
  if (cleaned.length <= emittedLen) return "";
65851
66172
  const delta = cleaned.slice(emittedLen);
65852
66173
  emittedLen = cleaned.length;
@@ -66084,6 +66405,330 @@ var init_harnessStateEmit = __esm({
66084
66405
  }
66085
66406
  });
66086
66407
 
66408
+ // src/cli/serve/sessionControl.ts
66409
+ import { AsyncLocalStorage } from "node:async_hooks";
66410
+ function runWithSession(sessionId2, fn) {
66411
+ return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
66412
+ }
66413
+ function registerLiveTurnControl(control) {
66414
+ const store6 = dispatchContext.getStore();
66415
+ if (!store6) return void 0;
66416
+ const registered = { ...control, token: store6.token };
66417
+ liveTurns.set(store6.sessionId, registered);
66418
+ return () => {
66419
+ if (liveTurns.get(store6.sessionId) === registered) {
66420
+ liveTurns.delete(store6.sessionId);
66421
+ }
66422
+ };
66423
+ }
66424
+ function getLiveTurnControl(sessionId2) {
66425
+ return liveTurns.get(sessionId2);
66426
+ }
66427
+ function clearSessionTurnControl(sessionId2) {
66428
+ const store6 = dispatchContext.getStore();
66429
+ if (!store6 || store6.sessionId !== sessionId2) return;
66430
+ const registered = liveTurns.get(sessionId2);
66431
+ if (registered && registered.token === store6.token) {
66432
+ liveTurns.delete(sessionId2);
66433
+ }
66434
+ }
66435
+ var dispatchContext, liveTurns;
66436
+ var init_sessionControl = __esm({
66437
+ "src/cli/serve/sessionControl.ts"() {
66438
+ "use strict";
66439
+ dispatchContext = new AsyncLocalStorage();
66440
+ liveTurns = /* @__PURE__ */ new Map();
66441
+ }
66442
+ });
66443
+
66444
+ // src/cli/headless/controlReader.ts
66445
+ function parseControlEvent(raw) {
66446
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
66447
+ return { ok: false, id: "", reason: "control event must be a JSON object" };
66448
+ }
66449
+ const candidate = raw;
66450
+ const type = candidate["type"];
66451
+ if (typeof type !== "string" || !CONTROL_TYPES.has(type)) {
66452
+ return {
66453
+ ok: false,
66454
+ id: typeof candidate["id"] === "string" ? candidate["id"] : "",
66455
+ reason: `unknown control type: ${String(type)}`
66456
+ };
66457
+ }
66458
+ if (typeof candidate["id"] !== "string" || candidate["id"].length === 0) {
66459
+ return { ok: false, id: "", reason: "missing control id" };
66460
+ }
66461
+ if (type === "steer" || type === "follow_up") {
66462
+ if (typeof candidate["text"] !== "string" || candidate["text"].trim().length === 0) {
66463
+ return {
66464
+ ok: false,
66465
+ id: candidate["id"],
66466
+ reason: `${type} requires a non-empty "text" field`
66467
+ };
66468
+ }
66469
+ }
66470
+ if (type === "cancel" && "reason" in candidate && candidate["reason"] !== void 0 && typeof candidate["reason"] !== "string") {
66471
+ return {
66472
+ ok: false,
66473
+ id: candidate["id"],
66474
+ reason: "cancel reason must be a string"
66475
+ };
66476
+ }
66477
+ return {
66478
+ ok: true,
66479
+ event: {
66480
+ ...candidate,
66481
+ ts: typeof candidate["ts"] === "number" ? candidate["ts"] : Date.now()
66482
+ }
66483
+ };
66484
+ }
66485
+ function parseControlLine(line) {
66486
+ const trimmed = line.trim();
66487
+ if (trimmed.length === 0) return null;
66488
+ try {
66489
+ return parseControlEvent(JSON.parse(trimmed));
66490
+ } catch (e) {
66491
+ return { ok: false, id: "", reason: `malformed JSON: ${e.message}` };
66492
+ }
66493
+ }
66494
+ function startControlReader(input, onLine) {
66495
+ let buffer = "";
66496
+ const onData = (chunk) => {
66497
+ buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
66498
+ let newlineIndex = buffer.indexOf("\n");
66499
+ while (newlineIndex !== -1) {
66500
+ const line = buffer.slice(0, newlineIndex);
66501
+ buffer = buffer.slice(newlineIndex + 1);
66502
+ if (line.trim().length > 0) onLine(line.replace(/\r$/, ""));
66503
+ newlineIndex = buffer.indexOf("\n");
66504
+ }
66505
+ };
66506
+ input.on("data", onData);
66507
+ return () => {
66508
+ input.removeListener("data", onData);
66509
+ buffer = "";
66510
+ };
66511
+ }
66512
+ var CONTROL_TYPES;
66513
+ var init_controlReader = __esm({
66514
+ "src/cli/headless/controlReader.ts"() {
66515
+ "use strict";
66516
+ CONTROL_TYPES = /* @__PURE__ */ new Set([
66517
+ "steer",
66518
+ "follow_up",
66519
+ "cancel",
66520
+ "pause",
66521
+ "resume"
66522
+ ]);
66523
+ }
66524
+ });
66525
+
66526
+ // src/cli/headless/protocol.ts
66527
+ function protocolInfoEvent() {
66528
+ return {
66529
+ type: "protocol_info",
66530
+ version: HEADLESS_PROTOCOL_VERSION,
66531
+ capabilities: HEADLESS_PROTOCOL_CAPABILITIES,
66532
+ ts: Date.now()
66533
+ };
66534
+ }
66535
+ function controlAcceptedEvent(controlId, controlType) {
66536
+ return {
66537
+ type: "control_accepted",
66538
+ controlId,
66539
+ controlType,
66540
+ ts: Date.now()
66541
+ };
66542
+ }
66543
+ function controlAppliedEvent(controlId, controlType, boundary) {
66544
+ return {
66545
+ type: "control_applied",
66546
+ controlId,
66547
+ controlType,
66548
+ boundary,
66549
+ ts: Date.now()
66550
+ };
66551
+ }
66552
+ function controlRejectedEvent(controlId, reason) {
66553
+ return {
66554
+ type: "control_rejected",
66555
+ controlId,
66556
+ reason,
66557
+ ts: Date.now()
66558
+ };
66559
+ }
66560
+ var HEADLESS_PROTOCOL_VERSION, HEADLESS_PROTOCOL_CAPABILITIES;
66561
+ var init_protocol2 = __esm({
66562
+ "src/cli/headless/protocol.ts"() {
66563
+ "use strict";
66564
+ HEADLESS_PROTOCOL_VERSION = 2;
66565
+ HEADLESS_PROTOCOL_CAPABILITIES = [
66566
+ "stdin-control",
66567
+ "steer",
66568
+ "follow_up",
66569
+ "cancel"
66570
+ ];
66571
+ }
66572
+ });
66573
+
66574
+ // src/cli/headless/controlBridge.ts
66575
+ function attachControlPlane(opts) {
66576
+ const { input, queue, emit, onCancel } = opts;
66577
+ let finalized = false;
66578
+ queue.onDrained = (events) => {
66579
+ for (const event of events) {
66580
+ emit(
66581
+ controlAppliedEvent(
66582
+ event.id,
66583
+ event.type,
66584
+ APPLIED_BOUNDARY[event.type] ?? "unknown"
66585
+ )
66586
+ );
66587
+ }
66588
+ const cancels = events.filter((e) => e.type === "cancel");
66589
+ if (cancels.length > 0 && onCancel) {
66590
+ const last = cancels[cancels.length - 1];
66591
+ onCancel(last.type === "cancel" ? last.reason : void 0);
66592
+ }
66593
+ };
66594
+ const disposeReader = startControlReader(input, (line) => {
66595
+ const outcome = parseControlLine(line);
66596
+ if (outcome === null) return;
66597
+ if (!outcome.ok) {
66598
+ emit(controlRejectedEvent(outcome.id, outcome.reason));
66599
+ return;
66600
+ }
66601
+ const event = outcome.event;
66602
+ if (event.type === "pause" || event.type === "resume") {
66603
+ emit(
66604
+ controlRejectedEvent(event.id, `${event.type} is not supported yet`)
66605
+ );
66606
+ return;
66607
+ }
66608
+ if (finalized) {
66609
+ if (event.type === "steer") {
66610
+ const converted = toFollowUp(event);
66611
+ queue.enqueue(converted);
66612
+ emit(controlAppliedEvent(event.id, "steer", "converted-to-follow-up"));
66613
+ emit(controlAcceptedEvent(converted.id, "follow_up"));
66614
+ } else if (event.type === "follow_up") {
66615
+ queue.enqueue(event);
66616
+ emit(controlAcceptedEvent(event.id, "follow_up"));
66617
+ } else {
66618
+ emit(controlRejectedEvent(event.id, "run already finished"));
66619
+ }
66620
+ return;
66621
+ }
66622
+ queue.enqueue(event);
66623
+ emit(controlAcceptedEvent(event.id, event.type));
66624
+ });
66625
+ return {
66626
+ dispose() {
66627
+ disposeReader();
66628
+ queue.onDrained = void 0;
66629
+ },
66630
+ finalize() {
66631
+ finalized = true;
66632
+ const lateSteers = queue.drainSteers();
66633
+ for (const steer of lateSteers) {
66634
+ queue.enqueue(toFollowUp(steer));
66635
+ emit(controlAppliedEvent(steer.id, "steer", "converted-to-follow-up"));
66636
+ }
66637
+ const followUps = queue.drainFollowUps();
66638
+ for (const followUp of followUps) {
66639
+ emit(controlAppliedEvent(followUp.id, "follow_up", "run-end"));
66640
+ }
66641
+ return followUps.map((f) => f.text);
66642
+ },
66643
+ get finalized() {
66644
+ return finalized;
66645
+ }
66646
+ };
66647
+ }
66648
+ function toFollowUp(steer) {
66649
+ return {
66650
+ type: "follow_up",
66651
+ id: `fu-${steer.id}`,
66652
+ text: steer.text,
66653
+ ts: steer.ts
66654
+ };
66655
+ }
66656
+ var APPLIED_BOUNDARY;
66657
+ var init_controlBridge = __esm({
66658
+ "src/cli/headless/controlBridge.ts"() {
66659
+ "use strict";
66660
+ init_controlReader();
66661
+ init_protocol2();
66662
+ APPLIED_BOUNDARY = {
66663
+ steer: "turn-end",
66664
+ follow_up: "run-end",
66665
+ cancel: "cancel"
66666
+ };
66667
+ }
66668
+ });
66669
+
66670
+ // src/cli/headless/liveTurnAbort.ts
66671
+ function attachHeadlessLiveCancel(opts) {
66672
+ const abort = new AbortController();
66673
+ const controlQueue = new RuntimeControlQueue();
66674
+ const cancel = () => {
66675
+ if (!abort.signal.aborted) abort.abort();
66676
+ return true;
66677
+ };
66678
+ const controlPlane = opts?.output === "json" && process.stdin.isTTY !== true && process.env.ZELARI_SERVE_HARNESS !== "1" ? (() => {
66679
+ emitEvent(protocolInfoEvent());
66680
+ return attachControlPlane({
66681
+ input: process.stdin,
66682
+ queue: controlQueue,
66683
+ emit: emitEvent,
66684
+ onCancel: () => {
66685
+ cancel();
66686
+ }
66687
+ });
66688
+ })() : void 0;
66689
+ const unregister = process.env.ZELARI_SERVE_HARNESS === "1" ? registerLiveTurnControl({
66690
+ queue: controlQueue,
66691
+ cancel
66692
+ }) : void 0;
66693
+ if (unregister) {
66694
+ const appliedBoundary = {
66695
+ steer: "turn-end",
66696
+ follow_up: "run-end",
66697
+ cancel: "cancel"
66698
+ };
66699
+ controlQueue.onDrained = (events) => {
66700
+ for (const event of events) {
66701
+ emitEvent(
66702
+ controlAppliedEvent(
66703
+ event.id,
66704
+ event.type,
66705
+ appliedBoundary[event.type] ?? "unknown"
66706
+ )
66707
+ );
66708
+ }
66709
+ };
66710
+ }
66711
+ return {
66712
+ signal: abort.signal,
66713
+ cancel,
66714
+ dispose() {
66715
+ controlPlane?.finalize();
66716
+ controlPlane?.dispose();
66717
+ unregister?.();
66718
+ }
66719
+ };
66720
+ }
66721
+ var init_liveTurnAbort = __esm({
66722
+ "src/cli/headless/liveTurnAbort.ts"() {
66723
+ "use strict";
66724
+ init_runtime();
66725
+ init_sessionControl();
66726
+ init_controlBridge();
66727
+ init_protocol2();
66728
+ init_headless();
66729
+ }
66730
+ });
66731
+
66087
66732
  // src/cli/headless/policyGate.ts
66088
66733
  import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
66089
66734
  import { randomUUID as randomUUID9 } from "node:crypto";
@@ -66296,268 +66941,6 @@ var init_verifierLifecycle = __esm({
66296
66941
  }
66297
66942
  });
66298
66943
 
66299
- // src/cli/headless/controlReader.ts
66300
- function parseControlEvent(raw) {
66301
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
66302
- return { ok: false, id: "", reason: "control event must be a JSON object" };
66303
- }
66304
- const candidate = raw;
66305
- const type = candidate["type"];
66306
- if (typeof type !== "string" || !CONTROL_TYPES.has(type)) {
66307
- return {
66308
- ok: false,
66309
- id: typeof candidate["id"] === "string" ? candidate["id"] : "",
66310
- reason: `unknown control type: ${String(type)}`
66311
- };
66312
- }
66313
- if (typeof candidate["id"] !== "string" || candidate["id"].length === 0) {
66314
- return { ok: false, id: "", reason: "missing control id" };
66315
- }
66316
- if (type === "steer" || type === "follow_up") {
66317
- if (typeof candidate["text"] !== "string" || candidate["text"].trim().length === 0) {
66318
- return {
66319
- ok: false,
66320
- id: candidate["id"],
66321
- reason: `${type} requires a non-empty "text" field`
66322
- };
66323
- }
66324
- }
66325
- if (type === "cancel" && "reason" in candidate && candidate["reason"] !== void 0 && typeof candidate["reason"] !== "string") {
66326
- return {
66327
- ok: false,
66328
- id: candidate["id"],
66329
- reason: "cancel reason must be a string"
66330
- };
66331
- }
66332
- return {
66333
- ok: true,
66334
- event: {
66335
- ...candidate,
66336
- ts: typeof candidate["ts"] === "number" ? candidate["ts"] : Date.now()
66337
- }
66338
- };
66339
- }
66340
- function parseControlLine(line) {
66341
- const trimmed = line.trim();
66342
- if (trimmed.length === 0) return null;
66343
- try {
66344
- return parseControlEvent(JSON.parse(trimmed));
66345
- } catch (e) {
66346
- return { ok: false, id: "", reason: `malformed JSON: ${e.message}` };
66347
- }
66348
- }
66349
- function startControlReader(input, onLine) {
66350
- let buffer = "";
66351
- const onData = (chunk) => {
66352
- buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
66353
- let newlineIndex = buffer.indexOf("\n");
66354
- while (newlineIndex !== -1) {
66355
- const line = buffer.slice(0, newlineIndex);
66356
- buffer = buffer.slice(newlineIndex + 1);
66357
- if (line.trim().length > 0) onLine(line.replace(/\r$/, ""));
66358
- newlineIndex = buffer.indexOf("\n");
66359
- }
66360
- };
66361
- input.on("data", onData);
66362
- return () => {
66363
- input.removeListener("data", onData);
66364
- buffer = "";
66365
- };
66366
- }
66367
- var CONTROL_TYPES;
66368
- var init_controlReader = __esm({
66369
- "src/cli/headless/controlReader.ts"() {
66370
- "use strict";
66371
- CONTROL_TYPES = /* @__PURE__ */ new Set([
66372
- "steer",
66373
- "follow_up",
66374
- "cancel",
66375
- "pause",
66376
- "resume"
66377
- ]);
66378
- }
66379
- });
66380
-
66381
- // src/cli/headless/protocol.ts
66382
- function protocolInfoEvent() {
66383
- return {
66384
- type: "protocol_info",
66385
- version: HEADLESS_PROTOCOL_VERSION,
66386
- capabilities: HEADLESS_PROTOCOL_CAPABILITIES,
66387
- ts: Date.now()
66388
- };
66389
- }
66390
- function controlAcceptedEvent(controlId, controlType) {
66391
- return {
66392
- type: "control_accepted",
66393
- controlId,
66394
- controlType,
66395
- ts: Date.now()
66396
- };
66397
- }
66398
- function controlAppliedEvent(controlId, controlType, boundary) {
66399
- return {
66400
- type: "control_applied",
66401
- controlId,
66402
- controlType,
66403
- boundary,
66404
- ts: Date.now()
66405
- };
66406
- }
66407
- function controlRejectedEvent(controlId, reason) {
66408
- return {
66409
- type: "control_rejected",
66410
- controlId,
66411
- reason,
66412
- ts: Date.now()
66413
- };
66414
- }
66415
- var HEADLESS_PROTOCOL_VERSION, HEADLESS_PROTOCOL_CAPABILITIES;
66416
- var init_protocol2 = __esm({
66417
- "src/cli/headless/protocol.ts"() {
66418
- "use strict";
66419
- HEADLESS_PROTOCOL_VERSION = 2;
66420
- HEADLESS_PROTOCOL_CAPABILITIES = [
66421
- "stdin-control",
66422
- "steer",
66423
- "follow_up",
66424
- "cancel"
66425
- ];
66426
- }
66427
- });
66428
-
66429
- // src/cli/headless/controlBridge.ts
66430
- function attachControlPlane(opts) {
66431
- const { input, queue, emit, onCancel } = opts;
66432
- let finalized = false;
66433
- queue.onDrained = (events) => {
66434
- for (const event of events) {
66435
- emit(
66436
- controlAppliedEvent(
66437
- event.id,
66438
- event.type,
66439
- APPLIED_BOUNDARY[event.type] ?? "unknown"
66440
- )
66441
- );
66442
- }
66443
- const cancels = events.filter((e) => e.type === "cancel");
66444
- if (cancels.length > 0 && onCancel) {
66445
- const last = cancels[cancels.length - 1];
66446
- onCancel(last.type === "cancel" ? last.reason : void 0);
66447
- }
66448
- };
66449
- const disposeReader = startControlReader(input, (line) => {
66450
- const outcome = parseControlLine(line);
66451
- if (outcome === null) return;
66452
- if (!outcome.ok) {
66453
- emit(controlRejectedEvent(outcome.id, outcome.reason));
66454
- return;
66455
- }
66456
- const event = outcome.event;
66457
- if (event.type === "pause" || event.type === "resume") {
66458
- emit(
66459
- controlRejectedEvent(event.id, `${event.type} is not supported yet`)
66460
- );
66461
- return;
66462
- }
66463
- if (finalized) {
66464
- if (event.type === "steer") {
66465
- const converted = toFollowUp(event);
66466
- queue.enqueue(converted);
66467
- emit(controlAppliedEvent(event.id, "steer", "converted-to-follow-up"));
66468
- emit(controlAcceptedEvent(converted.id, "follow_up"));
66469
- } else if (event.type === "follow_up") {
66470
- queue.enqueue(event);
66471
- emit(controlAcceptedEvent(event.id, "follow_up"));
66472
- } else {
66473
- emit(controlRejectedEvent(event.id, "run already finished"));
66474
- }
66475
- return;
66476
- }
66477
- queue.enqueue(event);
66478
- emit(controlAcceptedEvent(event.id, event.type));
66479
- });
66480
- return {
66481
- dispose() {
66482
- disposeReader();
66483
- queue.onDrained = void 0;
66484
- },
66485
- finalize() {
66486
- finalized = true;
66487
- const lateSteers = queue.drainSteers();
66488
- for (const steer of lateSteers) {
66489
- queue.enqueue(toFollowUp(steer));
66490
- emit(controlAppliedEvent(steer.id, "steer", "converted-to-follow-up"));
66491
- }
66492
- const followUps = queue.drainFollowUps();
66493
- for (const followUp of followUps) {
66494
- emit(controlAppliedEvent(followUp.id, "follow_up", "run-end"));
66495
- }
66496
- return followUps.map((f) => f.text);
66497
- },
66498
- get finalized() {
66499
- return finalized;
66500
- }
66501
- };
66502
- }
66503
- function toFollowUp(steer) {
66504
- return {
66505
- type: "follow_up",
66506
- id: `fu-${steer.id}`,
66507
- text: steer.text,
66508
- ts: steer.ts
66509
- };
66510
- }
66511
- var APPLIED_BOUNDARY;
66512
- var init_controlBridge = __esm({
66513
- "src/cli/headless/controlBridge.ts"() {
66514
- "use strict";
66515
- init_controlReader();
66516
- init_protocol2();
66517
- APPLIED_BOUNDARY = {
66518
- steer: "turn-end",
66519
- follow_up: "run-end",
66520
- cancel: "cancel"
66521
- };
66522
- }
66523
- });
66524
-
66525
- // src/cli/serve/sessionControl.ts
66526
- import { AsyncLocalStorage } from "node:async_hooks";
66527
- function runWithSession(sessionId2, fn) {
66528
- return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
66529
- }
66530
- function registerLiveTurnControl(control) {
66531
- const store6 = dispatchContext.getStore();
66532
- if (!store6) return void 0;
66533
- const registered = { ...control, token: store6.token };
66534
- liveTurns.set(store6.sessionId, registered);
66535
- return () => {
66536
- if (liveTurns.get(store6.sessionId) === registered) {
66537
- liveTurns.delete(store6.sessionId);
66538
- }
66539
- };
66540
- }
66541
- function getLiveTurnControl(sessionId2) {
66542
- return liveTurns.get(sessionId2);
66543
- }
66544
- function clearSessionTurnControl(sessionId2) {
66545
- const store6 = dispatchContext.getStore();
66546
- if (!store6 || store6.sessionId !== sessionId2) return;
66547
- const registered = liveTurns.get(sessionId2);
66548
- if (registered && registered.token === store6.token) {
66549
- liveTurns.delete(sessionId2);
66550
- }
66551
- }
66552
- var dispatchContext, liveTurns;
66553
- var init_sessionControl = __esm({
66554
- "src/cli/serve/sessionControl.ts"() {
66555
- "use strict";
66556
- dispatchContext = new AsyncLocalStorage();
66557
- liveTurns = /* @__PURE__ */ new Map();
66558
- }
66559
- });
66560
-
66561
66944
  // src/cli/extensions/sandboxedFs.ts
66562
66945
  import { promises as fsp } from "node:fs";
66563
66946
  import path89 from "node:path";
@@ -66916,6 +67299,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
66916
67299
  // an interactive approval (permission.request over NDJSON) instead of
66917
67300
  // the fail-closed typedErr. Absent handler ⇒ unchanged fail-closed.
66918
67301
  ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
67302
+ ...opts.onAskUser ? { onAskUser: opts.onAskUser } : {},
66919
67303
  permissionPolicy: defaultPermissionPolicy2(),
66920
67304
  ...nativeMemory ? { memoryService: nativeMemory } : {},
66921
67305
  memoryAutoWrite,
@@ -67142,7 +67526,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
67142
67526
  let finalReason = "completed";
67143
67527
  let exitCode = 0;
67144
67528
  const textBuffer = [];
67145
- const scrub = createStreamScrubber2();
67529
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
67146
67530
  try {
67147
67531
  for await (const event of harness.run()) {
67148
67532
  progressRuntime.observe(event);
@@ -68684,6 +69068,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68684
69068
  // ask without a UI fails closed), so this intersection actually
68685
69069
  // bites: tentacles can never exceed the preset.
68686
69070
  parentPolicy: defaultPermissionPolicy2(),
69071
+ ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
68687
69072
  // Anchor every tentacle to the SAME provider/model this run
68688
69073
  // resolved (Desktop's selector, or --provider/--model), instead
68689
69074
  // of the persisted provider.json default the factory falls back
@@ -68800,6 +69185,8 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
68800
69185
  planMode,
68801
69186
  ...extras?.lspProvider ? { lspProvider: extras.lspProvider } : {},
68802
69187
  permissionPolicy: defaultPermissionPolicy2(),
69188
+ ...opts?.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
69189
+ ...opts?.onAskUser ? { onAskUser: opts.onAskUser } : {},
68803
69190
  ...memoryService ? { memoryService } : {},
68804
69191
  memoryAutoWrite
68805
69192
  });
@@ -68819,6 +69206,14 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
68819
69206
  return { toolRegistry, workspaceCtx: realCtx };
68820
69207
  }
68821
69208
  async function runHeadlessCouncil(opts, provider, model, providerStream, extras) {
69209
+ const live = attachHeadlessLiveCancel({ output: opts.output });
69210
+ try {
69211
+ return await runHeadlessCouncilBody(opts, provider, model, providerStream, extras, live.signal);
69212
+ } finally {
69213
+ live.dispose();
69214
+ }
69215
+ }
69216
+ async function runHeadlessCouncilBody(opts, provider, model, providerStream, extras, signal) {
68822
69217
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
68823
69218
  const sessionId2 = crypto.randomUUID();
68824
69219
  const cwd = resolveHeadlessCwd(opts);
@@ -68894,7 +69289,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
68894
69289
  const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
68895
69290
  if (opts.task) spine.userMessage(effectiveTask);
68896
69291
  let exitCode = 0;
68897
- const scrub = createStreamScrubber2();
69292
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
68898
69293
  let lastAssistantText = "";
68899
69294
  let currentAssistantText = "";
68900
69295
  try {
@@ -68929,6 +69324,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
68929
69324
  tools: toolRegistry,
68930
69325
  feedbackStore,
68931
69326
  runMode: councilRunMode,
69327
+ signal,
68932
69328
  // t23: an auto-SELECTED council runs the LITE tier (3 members) unless
68933
69329
  // ZELARI_COUNCIL_TIER / ZELARI_COUNCIL_SIZE explicitly opt into full.
68934
69330
  ...opts.orchestrationDecision?.strategy === "council" && process.env["ZELARI_COUNCIL_TIER"] === void 0 && process.env["ZELARI_COUNCIL_SIZE"] === void 0 ? { councilSize: COUNCIL_TIER_SIZES.lite } : {},
@@ -68987,7 +69383,9 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
68987
69383
  return 2;
68988
69384
  }
68989
69385
  try {
68990
- await spine.close(exitCode === 0 ? "completed" : "error");
69386
+ await spine.close(
69387
+ signal.aborted ? "stopped" : exitCode === 0 ? "completed" : "error"
69388
+ );
68991
69389
  } catch {
68992
69390
  }
68993
69391
  try {
@@ -68999,7 +69397,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
68999
69397
  at: (/* @__PURE__ */ new Date()).toISOString(),
69000
69398
  mode: "shadow",
69001
69399
  taskClass: classifyTask2({ prompt: effectiveTask }).taskClass,
69002
- verdict: exitCode === 0 ? "PASS" : exitCode === 3 ? "FAIL" : "UNKNOWN"
69400
+ verdict: signal.aborted ? "UNKNOWN" : exitCode === 0 ? "PASS" : exitCode === 3 ? "FAIL" : "UNKNOWN"
69003
69401
  });
69004
69402
  }
69005
69403
  } catch {
@@ -69018,7 +69416,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69018
69416
  } catch {
69019
69417
  }
69020
69418
  }
69021
- if (nativeMemory && memoryAutoWrite && lastAssistantText) {
69419
+ if (nativeMemory && memoryAutoWrite && lastAssistantText && !signal.aborted) {
69022
69420
  try {
69023
69421
  await nativeMemory.remember({
69024
69422
  kind: councilRunMode === "design-phase" ? "decision" : "outcome",
@@ -69045,6 +69443,14 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69045
69443
  return exitCode;
69046
69444
  }
69047
69445
  async function runHeadlessZelari(opts, provider, model, providerStream, extras) {
69446
+ const live = attachHeadlessLiveCancel({ output: opts.output });
69447
+ try {
69448
+ return await runHeadlessZelariBody(opts, provider, model, providerStream, extras, live.signal);
69449
+ } finally {
69450
+ live.dispose();
69451
+ }
69452
+ }
69453
+ async function runHeadlessZelariBody(opts, provider, model, providerStream, extras, signal) {
69048
69454
  const projectRoot = resolveHeadlessCwd(opts);
69049
69455
  const sessionId2 = opts.resumeSessionId ?? crypto.randomUUID();
69050
69456
  const spine = await openHeadlessSpine({
@@ -69155,6 +69561,7 @@ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMv
69155
69561
  memory,
69156
69562
  emit,
69157
69563
  buildViaAgent,
69564
+ signal,
69158
69565
  onMissionPhase: (phase2, note) => spine.missionPhase(phase2, note),
69159
69566
  onMissionProgress: (advice, iteration) => spine.missionProgress({
69160
69567
  recommendation: advice.recommendation,
@@ -69180,7 +69587,7 @@ ${ragContext}` : slicePrompt;
69180
69587
  let writeCount = 0;
69181
69588
  let chairmanErrored = false;
69182
69589
  let membersCompleted = 0;
69183
- const scrub = createStreamScrubber2();
69590
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
69184
69591
  const { composeProjectContext: composeProjectContext3 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
69185
69592
  const { loadDurableContext: loadDurableContext3 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
69186
69593
  const memOnly = ragContext?.trim() ? ragContext : void 0;
@@ -69203,6 +69610,7 @@ ${ragContext}` : slicePrompt;
69203
69610
  tools: toolRegistry,
69204
69611
  feedbackStore,
69205
69612
  runMode: effectiveRunMode,
69613
+ signal,
69206
69614
  maxToolCallsChairman: chairmanBudget,
69207
69615
  ...implementerRetry ? { skipSpecialists: true } : {},
69208
69616
  workspaceContext: composed2.workspaceContext,
@@ -69250,11 +69658,20 @@ ${ragContext}` : slicePrompt;
69250
69658
  }
69251
69659
  let completionOk = false;
69252
69660
  let degraded = false;
69661
+ if (signal.aborted) {
69662
+ return {
69663
+ completionOk: false,
69664
+ ran: membersCompleted > 0 || synthesisText.length > 0,
69665
+ synthesisText: synthesisText || void 0,
69666
+ writeCount,
69667
+ degraded: true
69668
+ };
69669
+ }
69253
69670
  try {
69254
69671
  const { detectDegradedRun: detectDegradedRun3 } = await Promise.resolve().then(() => (init_council(), council_exports));
69255
69672
  const d = detectDegradedRun3({
69256
69673
  chairmanErrored,
69257
- councilAborted: false,
69674
+ councilAborted: signal.aborted,
69258
69675
  luciferWriteCount: writeCount,
69259
69676
  synthesisText,
69260
69677
  runMode: effectiveRunMode
@@ -69321,7 +69738,9 @@ ${ragContext}` : slicePrompt;
69321
69738
  const { registry: agentRegistry } = createBuiltinToolRegistry2({
69322
69739
  root: projectRoot,
69323
69740
  planMode: false,
69324
- permissionPolicy: defaultPermissionPolicy2()
69741
+ permissionPolicy: defaultPermissionPolicy2(),
69742
+ ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
69743
+ ...opts.onAskUser ? { onAskUser: opts.onAskUser } : {}
69325
69744
  });
69326
69745
  await registerHeadlessMcp(agentRegistry, opts);
69327
69746
  const durableState = await loadDurableContext2(projectRoot);
@@ -69388,7 +69807,10 @@ ${ragContext}` : slicePrompt;
69388
69807
  }
69389
69808
  });
69390
69809
  if (state3.status === "error") exitCode = exitCode || 3;
69391
- else if (state3.status === "success") {
69810
+ else if (state3.status === "cancelled") {
69811
+ exitCode = 0;
69812
+ spine.missionPhase("verification", "mission-cancelled");
69813
+ } else if (state3.status === "success") {
69392
69814
  const missionGate = await evaluateStrictBuildGate("build", {
69393
69815
  emit: (input) => spine.appendEvent(input),
69394
69816
  surface: "mission",
@@ -69426,7 +69848,8 @@ ${ragContext}` : slicePrompt;
69426
69848
  }
69427
69849
  await memory.close().catch(() => void 0);
69428
69850
  try {
69429
- if (exitCode === 0) await spine.close("completed");
69851
+ if (signal.aborted) await spine.close("stopped");
69852
+ else if (exitCode === 0) await spine.close("completed");
69430
69853
  else await spine.close(exitCode === 2 ? "error" : "stopped");
69431
69854
  } catch {
69432
69855
  }
@@ -69473,6 +69896,7 @@ var init_runHeadless = __esm({
69473
69896
  init_metrics3();
69474
69897
  init_headlessSpine();
69475
69898
  init_harnessStateEmit();
69899
+ init_liveTurnAbort();
69476
69900
  init_policyGate();
69477
69901
  init_policyLoadMode();
69478
69902
  init_runOneTurn();
@@ -71060,38 +71484,61 @@ function applyTurnPermissionPreset(input) {
71060
71484
  process.env[PRESET_ENV] = value;
71061
71485
  return true;
71062
71486
  }
71487
+ function isPermissionDecision(value) {
71488
+ return typeof value === "string" && PERMISSION_DECISIONS.includes(value);
71489
+ }
71063
71490
  function createServePermissionBridge(write, timeoutMs2 = 12e4) {
71064
71491
  const pending = /* @__PURE__ */ new Map();
71065
71492
  let seq = 0;
71066
- const settle = (requestId, decision) => {
71493
+ const settle = (requestId, decision, timedOut = false) => {
71067
71494
  const entry = pending.get(requestId);
71068
71495
  if (!entry) return false;
71069
71496
  pending.delete(requestId);
71070
71497
  clearTimeout(entry.timer);
71498
+ write(
71499
+ JSON.stringify({
71500
+ type: "permission.settled",
71501
+ requestId,
71502
+ decision,
71503
+ ...timedOut ? { timedOut: true } : {}
71504
+ })
71505
+ );
71071
71506
  entry.resolve(decision);
71072
71507
  return true;
71073
71508
  };
71074
71509
  return {
71075
71510
  onPermissionAsk(payload) {
71076
71511
  const requestId = `perm-${Date.now()}-${++seq}`;
71512
+ const categories = payload.categories && payload.categories.length > 0 ? payload.categories : payload.category ? payload.category.split(",").map((c) => c.trim()).filter(Boolean) : [];
71077
71513
  return new Promise((resolve9) => {
71078
71514
  const timer = setTimeout(() => {
71079
- settle(requestId, "deny");
71515
+ settle(requestId, "deny", true);
71080
71516
  }, timeoutMs2);
71081
- pending.set(requestId, { resolve: resolve9, timer });
71517
+ pending.set(requestId, { resolve: resolve9, timer, payload });
71082
71518
  write(
71083
71519
  JSON.stringify({
71084
71520
  type: "permission.request",
71085
71521
  requestId,
71086
71522
  tool: payload.tool,
71087
71523
  category: payload.category,
71524
+ categories,
71088
71525
  ...payload.inputPreview !== void 0 ? { inputPreview: payload.inputPreview } : {},
71089
71526
  ...payload.reason !== void 0 ? { reason: payload.reason } : {}
71090
71527
  })
71091
71528
  );
71092
71529
  });
71093
71530
  },
71094
- respond: settle,
71531
+ respond: (requestId, decision) => settle(requestId, decision, false),
71532
+ releaseGranted() {
71533
+ let n = 0;
71534
+ for (const [id3, entry] of [...pending]) {
71535
+ const cats = entry.payload.categories && entry.payload.categories.length > 0 ? entry.payload.categories : entry.payload.category ? entry.payload.category.split(",").map((c) => c.trim()).filter(Boolean) : [];
71536
+ if (isSessionGranted(entry.payload.tool, cats)) {
71537
+ if (settle(id3, "allow", false)) n += 1;
71538
+ }
71539
+ }
71540
+ return n;
71541
+ },
71095
71542
  pendingCount: () => pending.size
71096
71543
  };
71097
71544
  }
@@ -71103,8 +71550,11 @@ function servePermissionRespond(bridge, params) {
71103
71550
  if (typeof requestId !== "string" || requestId.length === 0) {
71104
71551
  return { accepted: false, reason: "permission.respond requires a non-empty string requestId" };
71105
71552
  }
71106
- if (decision !== "allow" && decision !== "deny") {
71107
- return { accepted: false, reason: "permission.respond decision must be 'allow' or 'deny'" };
71553
+ if (!isPermissionDecision(decision)) {
71554
+ return {
71555
+ accepted: false,
71556
+ reason: "permission.respond decision must be 'allow' | 'deny' | 'always-tool' | 'always-category'"
71557
+ };
71108
71558
  }
71109
71559
  return { accepted: bridge.respond(requestId, decision) };
71110
71560
  }
@@ -71114,18 +71564,104 @@ function asRegistryAskHandler(bridge) {
71114
71564
  const decision = await bridge.onPermissionAsk({
71115
71565
  tool: req.toolName,
71116
71566
  category: req.categories.join(",") || "other",
71567
+ categories: req.categories,
71117
71568
  reason,
71118
71569
  ...req.claims && req.claims.length > 0 ? { inputPreview: req.claims.map((c) => c.summary).join(" \xB7 ") } : {}
71119
71570
  });
71120
- return decision === "allow";
71571
+ if (decision === "deny") return false;
71572
+ if (decision === "always-tool") {
71573
+ grantSessionTool(req.toolName);
71574
+ bridge.releaseGranted();
71575
+ } else if (decision === "always-category") {
71576
+ for (const cat of req.categories) {
71577
+ grantSessionCategory(cat);
71578
+ }
71579
+ grantSessionTool(req.toolName);
71580
+ bridge.releaseGranted();
71581
+ }
71582
+ return true;
71121
71583
  };
71122
71584
  }
71123
- var SERVE_PERMISSION_PRESETS, PRESET_ENV;
71585
+ var SERVE_PERMISSION_PRESETS, PRESET_ENV, PERMISSION_DECISIONS;
71124
71586
  var init_permissionBridge = __esm({
71125
71587
  "src/cli/serve/permissionBridge.ts"() {
71126
71588
  "use strict";
71589
+ init_toolPermissions();
71127
71590
  SERVE_PERMISSION_PRESETS = ["standard", "strict", "yolo"];
71128
71591
  PRESET_ENV = "ZELARI_PERMISSION_PRESET";
71592
+ PERMISSION_DECISIONS = [
71593
+ "allow",
71594
+ "deny",
71595
+ "always-tool",
71596
+ "always-category"
71597
+ ];
71598
+ }
71599
+ });
71600
+
71601
+ // src/cli/serve/askUserBridge.ts
71602
+ function createServeAskUserBridge(write, timeoutMs2 = askUserTimeoutMs()) {
71603
+ const pending = /* @__PURE__ */ new Map();
71604
+ let seq = 0;
71605
+ const settle = (requestId, answer, timedOut = false) => {
71606
+ const entry = pending.get(requestId);
71607
+ if (!entry) return false;
71608
+ pending.delete(requestId);
71609
+ clearTimeout(entry.timer);
71610
+ write(
71611
+ JSON.stringify({
71612
+ type: "ask_user.settled",
71613
+ requestId,
71614
+ answer,
71615
+ ...timedOut ? { timedOut: true } : {}
71616
+ })
71617
+ );
71618
+ entry.resolve(answer);
71619
+ return true;
71620
+ };
71621
+ return {
71622
+ onAskUser(req) {
71623
+ const question = req.question.trim();
71624
+ const choices = req.choices.map((c) => c.trim()).filter(Boolean);
71625
+ if (choices.length < 2) return Promise.resolve(null);
71626
+ const requestId = `ask-${Date.now()}-${++seq}`;
71627
+ return new Promise((resolve9) => {
71628
+ const timer = setTimeout(() => {
71629
+ settle(requestId, null, true);
71630
+ }, timeoutMs2);
71631
+ pending.set(requestId, { resolve: resolve9, timer });
71632
+ write(
71633
+ JSON.stringify({
71634
+ type: "ask_user.request",
71635
+ requestId,
71636
+ question,
71637
+ choices,
71638
+ ...req.context ? { context: req.context } : {}
71639
+ })
71640
+ );
71641
+ });
71642
+ },
71643
+ respond: (requestId, answer) => settle(requestId, answer, false),
71644
+ pendingCount: () => pending.size
71645
+ };
71646
+ }
71647
+ function serveAskUserRespond(bridge, params) {
71648
+ if (!params || typeof params !== "object") {
71649
+ return { accepted: false, reason: "ask_user.respond requires an object params" };
71650
+ }
71651
+ const { requestId, answer } = params;
71652
+ if (typeof requestId !== "string" || requestId.length === 0) {
71653
+ return { accepted: false, reason: "ask_user.respond requires a non-empty string requestId" };
71654
+ }
71655
+ if (answer !== null && typeof answer !== "string") {
71656
+ return { accepted: false, reason: "ask_user.respond answer must be a string or null" };
71657
+ }
71658
+ const text = typeof answer === "string" ? answer.trim() : null;
71659
+ return { accepted: bridge.respond(requestId, text && text.length > 0 ? text : null) };
71660
+ }
71661
+ var init_askUserBridge = __esm({
71662
+ "src/cli/serve/askUserBridge.ts"() {
71663
+ "use strict";
71664
+ init_askUserTimeout();
71129
71665
  }
71130
71666
  });
71131
71667
 
@@ -71242,7 +71778,7 @@ function resolveTurnLspProvider(services) {
71242
71778
  const candidate = services?.lspManager;
71243
71779
  return candidate instanceof LspManager ? candidate : void 0;
71244
71780
  }
71245
- function createCliRunTurn(onPermissionAsk) {
71781
+ function createCliRunTurn(onPermissionAsk, onAskUser) {
71246
71782
  let streamPromise = null;
71247
71783
  const ensureStream = () => {
71248
71784
  if (!streamPromise) {
@@ -71269,6 +71805,7 @@ function createCliRunTurn(onPermissionAsk) {
71269
71805
  const { provider, model, stream } = await ensureStream();
71270
71806
  const opts = bindHarnessTurnOptions(input, deps.session.workspaceRoot);
71271
71807
  if (onPermissionAsk) opts.onPermissionAsk = onPermissionAsk;
71808
+ if (onAskUser) opts.onAskUser = onAskUser;
71272
71809
  applyTurnPermissionPreset(input);
71273
71810
  const lspProvider = resolveTurnLspProvider(deps.services);
71274
71811
  const exitCode = await dispatchHeadlessTurn(
@@ -71304,6 +71841,7 @@ function startHarnessServer(options = {}) {
71304
71841
  write(JSON.stringify(envelope));
71305
71842
  };
71306
71843
  const permissionBridge = createServePermissionBridge(write);
71844
+ const askUserBridge = createServeAskUserBridge(write);
71307
71845
  const dispatch = async (req) => {
71308
71846
  if (typeof req.method !== "string") {
71309
71847
  return { id: req.id ?? null, ok: false, error: { code: "bad_request", message: "missing method" } };
@@ -71317,11 +71855,21 @@ function startHarnessServer(options = {}) {
71317
71855
  result: servePermissionRespond(permissionBridge, params)
71318
71856
  };
71319
71857
  }
71858
+ case "ask_user.respond": {
71859
+ return {
71860
+ id: req.id ?? null,
71861
+ ok: true,
71862
+ result: serveAskUserRespond(askUserBridge, params)
71863
+ };
71864
+ }
71320
71865
  case "session.create": {
71321
71866
  const root = typeof params.workspaceRoot === "string" ? params.workspaceRoot : process.cwd();
71322
71867
  const session = server.createSession({
71323
71868
  workspaceRoot: root,
71324
- runTurn: options.runTurn ?? createCliRunTurn(asRegistryAskHandler(permissionBridge))
71869
+ runTurn: options.runTurn ?? createCliRunTurn(
71870
+ asRegistryAskHandler(permissionBridge),
71871
+ askUserBridge.onAskUser
71872
+ )
71325
71873
  });
71326
71874
  return { id: req.id ?? null, ok: true, result: { sessionId: session.id, workspaceRoot: session.workspaceRoot } };
71327
71875
  }
@@ -71475,6 +72023,7 @@ var init_harnessServer = __esm({
71475
72023
  init_policyGate();
71476
72024
  init_policyLoadMode();
71477
72025
  init_permissionBridge();
72026
+ init_askUserBridge();
71478
72027
  init_spineLockSweep();
71479
72028
  }
71480
72029
  });
@@ -72517,7 +73066,12 @@ function readPackageJson4() {
72517
73066
  }
72518
73067
  }
72519
73068
  function getGlobalPrefix() {
72520
- return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || tryExec("npm prefix -g");
73069
+ const fromNpm = tryExec("npm prefix -g");
73070
+ if (fromNpm) return fromNpm;
73071
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim();
73072
+ }
73073
+ function isSourceCheckout() {
73074
+ return existsSync62(path95.join(packageRoot, "src", "cli", "main.ts")) && existsSync62(path95.join(packageRoot, "apps", "desktop", "package.json"));
72521
73075
  }
72522
73076
  function checkShim(pkgName) {
72523
73077
  const prefix = getGlobalPrefix();
@@ -72528,6 +73082,14 @@ function checkShim(pkgName) {
72528
73082
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
72529
73083
  const shimPath = path95.join(prefix, shimName);
72530
73084
  if (!existsSync62(shimPath)) {
73085
+ const localBin = path95.join(packageRoot, "bin", "zelari-code.js");
73086
+ if (isSourceCheckout() && existsSync62(localBin)) {
73087
+ return WARN(
73088
+ `global shim not found at ${shimPath}
73089
+ source checkout \u2014 using ${localBin}
73090
+ optional: npm install -g ${pkgName}@latest --force`
73091
+ );
73092
+ }
72531
73093
  return FAIL(
72532
73094
  `shim not found at ${shimPath}
72533
73095
  fix: npm install -g ${pkgName}@latest --force`
@@ -72922,16 +73484,15 @@ __export(fixPath_exports, {
72922
73484
  });
72923
73485
  import { spawnSync as spawnSync3 } from "node:child_process";
72924
73486
  function getGlobalPrefix2() {
72925
- return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || (() => {
72926
- try {
72927
- return spawnSync3("npm", ["prefix", "-g"], {
72928
- encoding: "utf8",
72929
- stdio: ["ignore", "pipe", "ignore"]
72930
- }).stdout?.trim() ?? "";
72931
- } catch {
72932
- return "";
72933
- }
72934
- })();
73487
+ try {
73488
+ const fromNpm = spawnSync3("npm", ["prefix", "-g"], {
73489
+ encoding: "utf8",
73490
+ stdio: ["ignore", "pipe", "ignore"]
73491
+ }).stdout?.trim() ?? "";
73492
+ if (fromNpm) return fromNpm;
73493
+ } catch {
73494
+ }
73495
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim();
72935
73496
  }
72936
73497
  function powershell(script) {
72937
73498
  try {
@@ -76570,22 +77131,7 @@ init_spineTelemetry();
76570
77131
 
76571
77132
  // src/cli/hooks/permissionPicker.ts
76572
77133
  init_toolPermissions();
76573
-
76574
- // src/cli/hooks/askUserTimeout.ts
76575
- function askUserTimeoutMs() {
76576
- const raw = process.env.ZELARI_ASK_USER_TIMEOUT_MS?.trim();
76577
- if (!raw) return 3e5;
76578
- const n = Number.parseInt(raw, 10);
76579
- if (!Number.isFinite(n) || n < 0) return 3e5;
76580
- return n;
76581
- }
76582
- function armPickerTimeout(onFire, ms) {
76583
- if (ms <= 0) return () => void 0;
76584
- const id3 = setTimeout(onFire, ms);
76585
- return () => clearTimeout(id3);
76586
- }
76587
-
76588
- // src/cli/hooks/permissionPicker.ts
77134
+ init_askUserTimeout();
76589
77135
  function createPermissionAskHandler(opts) {
76590
77136
  const { setPicker: setPicker2, appendSystem: appendSystem2 } = opts;
76591
77137
  return (req) => new Promise((resolve9) => {
@@ -76678,6 +77224,7 @@ ${detail}${note}${claimsBlock}
76678
77224
  }
76679
77225
 
76680
77226
  // src/cli/hooks/useChatTurn.ts
77227
+ init_askUserTimeout();
76681
77228
  init_toolPermissions();
76682
77229
  init_skills2();
76683
77230
  init_fileStateStore();
@@ -78577,6 +79124,7 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
78577
79124
  const hook = await runPostCouncilHook2(workspaceCtx, {
78578
79125
  runMode: "implementation",
78579
79126
  userMessage,
79127
+ sessionId: deps.sessionId,
78580
79128
  synthesisText: synthesisText || void 0,
78581
79129
  degradedRun: d.degraded,
78582
79130
  degradedReasons: d.reasons
@@ -78606,6 +79154,7 @@ init_permissionBroker();
78606
79154
  init_brokerHandlers();
78607
79155
  import { useEffect as useEffect5, useRef as useRef5 } from "react";
78608
79156
  init_messageHelpers();
79157
+ init_askUserTimeout();
78609
79158
  function usePermissionBroker(opts) {
78610
79159
  const { setPicker: setPicker2, setMessages } = opts;
78611
79160
  const handleRef = useRef5(null);