zelari-code 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18431,7 +18431,8 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
18431
18431
  - **Verify**: after non-trivial edits, run the project's tests/typecheck/build when available; fix failures you introduced.
18432
18432
  - **Use project scripts**: prefer package.json / Makefile / existing tooling over ad-hoc commands.
18433
18433
  - **Finish**: list the paths you wrote/edited and how to verify. If you wrote nothing, say so honestly \u2014 do not invent a done report.
18434
- - **No spam**: never repeat the same paragraph or status line. One diagnosis, then tools (or one short final answer).`
18434
+ - **No spam**: never repeat the same paragraph or status line. One diagnosis, then tools (or one short final answer).
18435
+ - **Browser smoke honesty**: with \`browser_check\`, prefer \`waitForSelector\` / \`waitForText\` / \`evaluate\` on DOM (or explicit test hooks). Do not claim a logic fix is verified from \u201Cno console errors after N seconds\u201D alone (\`smokeStrength: weak\`). ES modules keep symbols off \`window\` \u2014 do not loop on exposing globals; assert visible UI or inject \`evaluate\` on \`document\`.`
18435
18436
  };
18436
18437
  CLARIFICATION_PROTOCOL_MODULE = {
18437
18438
  type: "custom",
@@ -22623,10 +22624,41 @@ async function loadPlaywright(cwd) {
22623
22624
  return null;
22624
22625
  }
22625
22626
  }
22627
+ function serializeEvaluateValue(value) {
22628
+ try {
22629
+ const json2 = JSON.stringify(value, (_k, v) => {
22630
+ if (typeof v === "function") return "[Function]";
22631
+ if (typeof v === "bigint") return v.toString();
22632
+ if (v instanceof Error) return { name: v.name, message: v.message };
22633
+ return v;
22634
+ });
22635
+ if (json2 === void 0) return null;
22636
+ if (json2.length > MAX_EVAL_RESULT) {
22637
+ return {
22638
+ truncated: true,
22639
+ preview: json2.slice(0, MAX_EVAL_RESULT)
22640
+ };
22641
+ }
22642
+ return JSON.parse(json2);
22643
+ } catch (err) {
22644
+ return {
22645
+ error: `non-serializable: ${err instanceof Error ? err.message : String(err)}`
22646
+ };
22647
+ }
22648
+ }
22649
+ function wrapEvaluateExpression(expression) {
22650
+ const expr = expression.trim();
22651
+ if (!expr) return "return undefined";
22652
+ if (/\breturn\b/.test(expr) || expr.includes("\n")) {
22653
+ return expr;
22654
+ }
22655
+ return `return (${expr})`;
22656
+ }
22626
22657
  async function runBrowserCheck(options, loader) {
22627
22658
  const consoleErrors = [];
22628
22659
  const pageErrors = [];
22629
22660
  const failedRequests = [];
22661
+ const evaluateResults = [];
22630
22662
  const base = { ok: false, consoleErrors, pageErrors, failedRequests };
22631
22663
  const resolve = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
22632
22664
  const pw = await resolve();
@@ -22638,6 +22670,7 @@ async function runBrowserCheck(options, loader) {
22638
22670
  }
22639
22671
  const timeout = options.timeoutMs ?? 15e3;
22640
22672
  let browser;
22673
+ let textFound;
22641
22674
  try {
22642
22675
  browser = await pw.chromium.launch({ headless: true });
22643
22676
  const page = await browser.newPage();
@@ -22664,6 +22697,63 @@ async function runBrowserCheck(options, loader) {
22664
22697
  case "wait":
22665
22698
  await page.waitForTimeout(action.ms);
22666
22699
  break;
22700
+ case "press": {
22701
+ const key = action.key.trim();
22702
+ if (!key) break;
22703
+ if (page.keyboard && typeof page.keyboard.press === "function") {
22704
+ await page.keyboard.press(key, { timeout });
22705
+ } else {
22706
+ await page.evaluate(
22707
+ wrapEvaluateExpression(
22708
+ `document.dispatchEvent(new KeyboardEvent('keydown',{key:${JSON.stringify(key)},bubbles:true})); true`
22709
+ )
22710
+ );
22711
+ }
22712
+ break;
22713
+ }
22714
+ case "waitForText": {
22715
+ const needle = action.text;
22716
+ const t = action.timeoutMs ?? timeout;
22717
+ try {
22718
+ await page.waitForFunction(
22719
+ `return (document.body && (document.body.innerText || document.body.textContent || '')).includes(arguments[0])`,
22720
+ needle,
22721
+ { timeout: t }
22722
+ );
22723
+ textFound = textFound === false ? false : true;
22724
+ } catch {
22725
+ textFound = false;
22726
+ }
22727
+ break;
22728
+ }
22729
+ case "evaluate": {
22730
+ const raw = action.expression ?? "";
22731
+ if (raw.length > MAX_EVAL_EXPR) {
22732
+ evaluateResults.push({
22733
+ expression: raw.slice(0, 120) + "\u2026",
22734
+ error: `expression too long (max ${MAX_EVAL_EXPR} chars)`
22735
+ });
22736
+ break;
22737
+ }
22738
+ if (!raw.trim()) {
22739
+ evaluateResults.push({ expression: raw, error: "empty expression" });
22740
+ break;
22741
+ }
22742
+ try {
22743
+ const wrapped = wrapEvaluateExpression(raw);
22744
+ const value = await page.evaluate(wrapped);
22745
+ evaluateResults.push({
22746
+ expression: raw.length > 200 ? raw.slice(0, 200) + "\u2026" : raw,
22747
+ value: serializeEvaluateValue(value)
22748
+ });
22749
+ } catch (err) {
22750
+ evaluateResults.push({
22751
+ expression: raw.length > 200 ? raw.slice(0, 200) + "\u2026" : raw,
22752
+ error: err instanceof Error ? err.message : String(err)
22753
+ });
22754
+ }
22755
+ break;
22756
+ }
22667
22757
  default:
22668
22758
  break;
22669
22759
  }
@@ -22677,6 +22767,20 @@ async function runBrowserCheck(options, loader) {
22677
22767
  selectorFound = false;
22678
22768
  }
22679
22769
  }
22770
+ let bodyTextSnippet;
22771
+ if (options.textSample) {
22772
+ try {
22773
+ const text = await page.evaluate(
22774
+ wrapEvaluateExpression(
22775
+ `(document.body?.innerText || document.body?.textContent || '').trim()`
22776
+ )
22777
+ );
22778
+ if (typeof text === "string" && text.length > 0) {
22779
+ bodyTextSnippet = text.length > MAX_BODY_SNIPPET ? text.slice(0, MAX_BODY_SNIPPET) + "\u2026" : text;
22780
+ }
22781
+ } catch {
22782
+ }
22783
+ }
22680
22784
  let screenshotPath;
22681
22785
  if (options.screenshotPath) {
22682
22786
  try {
@@ -22694,6 +22798,9 @@ async function runBrowserCheck(options, loader) {
22694
22798
  url: page.url(),
22695
22799
  ...title !== void 0 ? { title } : {},
22696
22800
  ...selectorFound !== void 0 ? { selectorFound } : {},
22801
+ ...evaluateResults.length > 0 ? { evaluateResults } : {},
22802
+ ...textFound !== void 0 ? { textFound } : {},
22803
+ ...bodyTextSnippet !== void 0 ? { bodyTextSnippet } : {},
22697
22804
  ...screenshotPath ? { screenshotPath } : {}
22698
22805
  };
22699
22806
  } catch (err) {
@@ -22705,9 +22812,13 @@ async function runBrowserCheck(options, loader) {
22705
22812
  }
22706
22813
  }
22707
22814
  }
22815
+ var MAX_EVAL_EXPR, MAX_EVAL_RESULT, MAX_BODY_SNIPPET;
22708
22816
  var init_driver = __esm({
22709
22817
  "src/cli/browser/driver.ts"() {
22710
22818
  "use strict";
22819
+ MAX_EVAL_EXPR = 4e3;
22820
+ MAX_EVAL_RESULT = 8e3;
22821
+ MAX_BODY_SNIPPET = 2e3;
22711
22822
  }
22712
22823
  });
22713
22824
 
@@ -22717,15 +22828,16 @@ import os7 from "node:os";
22717
22828
  function createBrowserTool(deps = {}) {
22718
22829
  return {
22719
22830
  name: "browser_check",
22720
- description: 'Open a URL in a headless browser to VERIFY a web change: optionally run click/fill/goto/wait actions, then report console errors, uncaught page exceptions, failed network requests, the final title/URL, whether an expected selector appeared, and a screenshot path. Use it to confirm UI edits actually render and run \u2014 stronger than "tests pass" for front-end. Requires Playwright (optional dependency).',
22831
+ description: "Open a URL in a headless browser to VERIFY a web change: optionally run click/fill/goto/wait/press/waitForText/evaluate actions, then report console errors, uncaught page exceptions, failed network requests, final title/URL, selector/text presence, evaluate results, and a screenshot path. Prefer DOM assertions (waitForSelector, waitForText, evaluate on document.querySelector) over window.* globals \u2014 ES modules and const keep symbols off window. Absence of console errors after a short wait is WEAK smoke only; assert UI state when claiming a fix is verified. Requires Playwright (optional dependency).",
22721
22832
  permissions: ["network"],
22722
22833
  // A browser launch + navigation can take a while.
22723
22834
  timeoutMs: 6e4,
22724
22835
  inputSchema: external_exports.object({
22725
22836
  url: external_exports.string().min(1).describe("URL to open (e.g. http://localhost:3000)."),
22726
- actions: external_exports.array(ActionSchema).optional().describe("Optional sequence of interactions before checking."),
22837
+ actions: external_exports.array(ActionSchema).optional().describe("Optional sequence of interactions / probes before checking."),
22727
22838
  waitForSelector: external_exports.string().optional().describe("Assert this CSS selector is present after actions."),
22728
- screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true).")
22839
+ screenshot: external_exports.boolean().optional().describe("Save a screenshot (default true)."),
22840
+ textSample: external_exports.boolean().optional().describe("Include a short body.innerText snippet (HUD / Game Over text).")
22729
22841
  }),
22730
22842
  execute: async (args, ctx) => {
22731
22843
  const a = args;
@@ -22737,14 +22849,17 @@ function createBrowserTool(deps = {}) {
22737
22849
  cwd: ctx.cwd,
22738
22850
  ...a.actions ? { actions: a.actions } : {},
22739
22851
  ...a.waitForSelector ? { waitForSelector: a.waitForSelector } : {},
22740
- ...screenshotPath ? { screenshotPath } : {}
22852
+ ...screenshotPath ? { screenshotPath } : {},
22853
+ ...a.textSample ? { textSample: true } : {}
22741
22854
  },
22742
22855
  deps.loader
22743
22856
  );
22744
22857
  if (!result.ok) {
22745
22858
  return typedOk({ ok: false, note: result.error ?? "browser check failed" });
22746
22859
  }
22747
- const clean = result.consoleErrors.length === 0 && result.pageErrors.length === 0 && result.failedRequests.length === 0 && result.selectorFound !== false;
22860
+ const clean = result.consoleErrors.length === 0 && result.pageErrors.length === 0 && result.failedRequests.length === 0 && result.selectorFound !== false && result.textFound !== false;
22861
+ const hadDomAssertion = result.selectorFound !== void 0 || result.textFound !== void 0 || result.evaluateResults !== void 0 && result.evaluateResults.length > 0 || !!result.bodyTextSnippet;
22862
+ const onlyWeakSmoke = clean && !hadDomAssertion;
22748
22863
  return typedOk({
22749
22864
  ok: true,
22750
22865
  clean,
@@ -22754,7 +22869,14 @@ function createBrowserTool(deps = {}) {
22754
22869
  pageErrors: result.pageErrors,
22755
22870
  failedRequests: result.failedRequests,
22756
22871
  ...result.selectorFound !== void 0 ? { selectorFound: result.selectorFound } : {},
22757
- ...result.screenshotPath ? { screenshotPath: result.screenshotPath } : {}
22872
+ ...result.evaluateResults ? { evaluateResults: result.evaluateResults } : {},
22873
+ ...result.textFound !== void 0 ? { textFound: result.textFound } : {},
22874
+ ...result.bodyTextSnippet ? { bodyTextSnippet: result.bodyTextSnippet } : {},
22875
+ ...result.screenshotPath ? { screenshotPath: result.screenshotPath } : {},
22876
+ ...onlyWeakSmoke ? {
22877
+ note: "Weak smoke only: no selector/text/evaluate assertions. No console/page errors is necessary but not sufficient to claim a logic fix. Add waitForSelector, waitForText, or evaluate (DOM/read hooks) for stronger evidence.",
22878
+ smokeStrength: "weak"
22879
+ } : { smokeStrength: "asserted" }
22758
22880
  });
22759
22881
  }
22760
22882
  };
@@ -22770,7 +22892,22 @@ var init_tools5 = __esm({
22770
22892
  external_exports.object({ type: external_exports.literal("click"), selector: external_exports.string().min(1) }),
22771
22893
  external_exports.object({ type: external_exports.literal("fill"), selector: external_exports.string().min(1), value: external_exports.string() }),
22772
22894
  external_exports.object({ type: external_exports.literal("wait"), ms: external_exports.number().int().positive().max(3e4) }),
22773
- external_exports.object({ type: external_exports.literal("goto"), url: external_exports.string().min(1) })
22895
+ external_exports.object({ type: external_exports.literal("goto"), url: external_exports.string().min(1) }),
22896
+ external_exports.object({
22897
+ type: external_exports.literal("evaluate"),
22898
+ expression: external_exports.string().min(1).max(4e3).describe(
22899
+ "JS expression or statements run in the page (page.evaluate). Prefer DOM reads (document.querySelector\u2026), not assuming window.game exists. Return JSON-safe values."
22900
+ )
22901
+ }),
22902
+ external_exports.object({
22903
+ type: external_exports.literal("press"),
22904
+ key: external_exports.string().min(1).describe('Playwright key name, e.g. "Space", "KeyW", "ArrowLeft", "Enter".')
22905
+ }),
22906
+ external_exports.object({
22907
+ type: external_exports.literal("waitForText"),
22908
+ text: external_exports.string().min(1).describe("Substring that must appear in document body text."),
22909
+ timeoutMs: external_exports.number().int().positive().max(6e4).optional()
22910
+ })
22774
22911
  ]);
22775
22912
  }
22776
22913
  });
@@ -24153,6 +24290,53 @@ var init_fileStateStore = __esm({
24153
24290
  }
24154
24291
  });
24155
24292
 
24293
+ // packages/core/dist/shared/eventBus.js
24294
+ var init_eventBus = __esm({
24295
+ "packages/core/dist/shared/eventBus.js"() {
24296
+ "use strict";
24297
+ }
24298
+ });
24299
+
24300
+ // packages/core/dist/events/index.js
24301
+ var init_events2 = __esm({
24302
+ "packages/core/dist/events/index.js"() {
24303
+ "use strict";
24304
+ init_events();
24305
+ init_eventBus();
24306
+ }
24307
+ });
24308
+
24309
+ // packages/core/dist/types/context.js
24310
+ var init_context = __esm({
24311
+ "packages/core/dist/types/context.js"() {
24312
+ "use strict";
24313
+ }
24314
+ });
24315
+
24316
+ // packages/core/dist/types/systemTypes.js
24317
+ var init_systemTypes = __esm({
24318
+ "packages/core/dist/types/systemTypes.js"() {
24319
+ "use strict";
24320
+ }
24321
+ });
24322
+
24323
+ // packages/core/dist/types/knowledge.js
24324
+ var init_knowledge = __esm({
24325
+ "packages/core/dist/types/knowledge.js"() {
24326
+ "use strict";
24327
+ }
24328
+ });
24329
+
24330
+ // packages/core/dist/types/index.js
24331
+ var init_types = __esm({
24332
+ "packages/core/dist/types/index.js"() {
24333
+ "use strict";
24334
+ init_context();
24335
+ init_systemTypes();
24336
+ init_knowledge();
24337
+ }
24338
+ });
24339
+
24156
24340
  // packages/core/dist/agents/roles.js
24157
24341
  function resolveRoleSystemPrompt(agent, runMode = "implementation") {
24158
24342
  const parts = [agent.systemPrompt];
@@ -27007,7 +27191,7 @@ var init_missionBrief = __esm({
27007
27191
  });
27008
27192
 
27009
27193
  // packages/core/dist/council/verification/types.js
27010
- var init_types = __esm({
27194
+ var init_types2 = __esm({
27011
27195
  "packages/core/dist/council/verification/types.js"() {
27012
27196
  "use strict";
27013
27197
  }
@@ -27211,7 +27395,7 @@ var init_autofix = __esm({
27211
27395
  var init_verification = __esm({
27212
27396
  "packages/core/dist/council/verification/index.js"() {
27213
27397
  "use strict";
27214
- init_types();
27398
+ init_types2();
27215
27399
  init_runChecks();
27216
27400
  init_honesty();
27217
27401
  init_parseCssMotion();
@@ -27229,7 +27413,7 @@ var init_verification = __esm({
27229
27413
  });
27230
27414
 
27231
27415
  // packages/core/dist/council/lessons/types.js
27232
- var init_types2 = __esm({
27416
+ var init_types3 = __esm({
27233
27417
  "packages/core/dist/council/lessons/types.js"() {
27234
27418
  "use strict";
27235
27419
  }
@@ -27482,7 +27666,7 @@ var init_recallLessons = __esm({
27482
27666
  var init_lessons = __esm({
27483
27667
  "packages/core/dist/council/lessons/index.js"() {
27484
27668
  "use strict";
27485
- init_types2();
27669
+ init_types3();
27486
27670
  init_io();
27487
27671
  init_isAnswerLeak();
27488
27672
  init_signatures();
@@ -27492,7 +27676,7 @@ var init_lessons = __esm({
27492
27676
  });
27493
27677
 
27494
27678
  // packages/core/dist/council/completion/types.js
27495
- var init_types3 = __esm({
27679
+ var init_types4 = __esm({
27496
27680
  "packages/core/dist/council/completion/types.js"() {
27497
27681
  "use strict";
27498
27682
  }
@@ -27573,7 +27757,7 @@ var init_buildCompletion = __esm({
27573
27757
  var init_completion2 = __esm({
27574
27758
  "packages/core/dist/council/completion/index.js"() {
27575
27759
  "use strict";
27576
- init_types3();
27760
+ init_types4();
27577
27761
  init_buildCompletion();
27578
27762
  }
27579
27763
  });
@@ -27888,6 +28072,34 @@ var init_council = __esm({
27888
28072
  }
27889
28073
  });
27890
28074
 
28075
+ // packages/core/dist/memory/types.js
28076
+ var init_types5 = __esm({
28077
+ "packages/core/dist/memory/types.js"() {
28078
+ "use strict";
28079
+ }
28080
+ });
28081
+
28082
+ // packages/core/dist/state/index.js
28083
+ var init_state = __esm({
28084
+ "packages/core/dist/state/index.js"() {
28085
+ "use strict";
28086
+ }
28087
+ });
28088
+
28089
+ // packages/core/dist/index.js
28090
+ var init_dist = __esm({
28091
+ "packages/core/dist/index.js"() {
28092
+ "use strict";
28093
+ init_events2();
28094
+ init_types();
28095
+ init_harness();
28096
+ init_council();
28097
+ init_skills2();
28098
+ init_types5();
28099
+ init_state();
28100
+ }
28101
+ });
28102
+
27891
28103
  // src/cli/utils/envNumber.ts
27892
28104
  function envNumber(raw, opts) {
27893
28105
  const { default: def, min, max } = opts;
@@ -31614,6 +31826,45 @@ var init_councilFeedback = __esm({
31614
31826
  }
31615
31827
  });
31616
31828
 
31829
+ // src/cli/buildPolicy.ts
31830
+ var buildPolicy_exports = {};
31831
+ __export(buildPolicy_exports, {
31832
+ describeBuildPolicy: () => describeBuildPolicy,
31833
+ resolveAgentMissionToolBudget: () => resolveAgentMissionToolBudget,
31834
+ shouldAllowCouncilBuild: () => shouldAllowCouncilBuild,
31835
+ shouldBuildViaAgent: () => shouldBuildViaAgent
31836
+ });
31837
+ function shouldBuildViaAgent(env = process.env) {
31838
+ if (shouldAllowCouncilBuild(env)) return false;
31839
+ if (env.ZELARI_BUILD_VIA_AGENT === "0") return false;
31840
+ return true;
31841
+ }
31842
+ function shouldAllowCouncilBuild(env = process.env) {
31843
+ return env.ZELARI_COUNCIL_CAN_BUILD === "1";
31844
+ }
31845
+ function resolveAgentMissionToolBudget(env = process.env) {
31846
+ const raw = env.ZELARI_MODE_MAX_TOOLS_AGENT;
31847
+ if (raw === void 0 || raw === "") return DEFAULT_AGENT_TOOL_BUDGET;
31848
+ const n = Number.parseInt(raw, 10);
31849
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_AGENT_TOOL_BUDGET;
31850
+ }
31851
+ function describeBuildPolicy(env = process.env) {
31852
+ const viaAgent = shouldBuildViaAgent(env);
31853
+ const councilBuild = shouldAllowCouncilBuild(env);
31854
+ const parts = [
31855
+ viaAgent ? "zelari build@agent (default)" : "zelari build@council (legacy)",
31856
+ councilBuild ? "council may implement" : "council plan-only unless ZELARI_COUNCIL_CAN_BUILD=1"
31857
+ ];
31858
+ return parts.join(" \xB7 ");
31859
+ }
31860
+ var DEFAULT_AGENT_TOOL_BUDGET;
31861
+ var init_buildPolicy = __esm({
31862
+ "src/cli/buildPolicy.ts"() {
31863
+ "use strict";
31864
+ DEFAULT_AGENT_TOOL_BUDGET = 40;
31865
+ }
31866
+ });
31867
+
31617
31868
  // src/cli/checkpoint/checkpointManager.ts
31618
31869
  import { execFile as execFile2 } from "node:child_process";
31619
31870
  import { promisify } from "node:util";
@@ -32074,6 +32325,10 @@ async function runZelariMission(userMessage, brief, deps) {
32074
32325
  deps.emit(
32075
32326
  `[zelari] design-phase (fuori budget) \xB7 step ${step} \xB7 slice ${brief.sliceMvp.id}`
32076
32327
  );
32328
+ } else if (deps.buildViaAgent) {
32329
+ deps.emit(
32330
+ `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 build@agent \xB7 slice ${brief.sliceMvp.id}`
32331
+ );
32077
32332
  } else if (implementerRetry) {
32078
32333
  deps.emit(
32079
32334
  `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 roster ridotto (Minosse+Lucifero) \xB7 slice ${brief.sliceMvp.id}`
@@ -32118,7 +32373,14 @@ async function runZelariMission(userMessage, brief, deps) {
32118
32373
  iteration: step
32119
32374
  }
32120
32375
  );
32121
- state2.lastCompletionOk = result.completionOk;
32376
+ let completionOk = result.completionOk;
32377
+ if (runMode === "implementation" && completionOk && typeof result.writeCount === "number" && result.writeCount === 0) {
32378
+ completionOk = false;
32379
+ deps.emit(
32380
+ "[zelari] completion.ok ignored: implementation slice wrote 0 project files"
32381
+ );
32382
+ }
32383
+ state2.lastCompletionOk = completionOk;
32122
32384
  state2.updatedAt = now().toISOString();
32123
32385
  if (runMode === "design-phase") {
32124
32386
  pendingDesign = false;
@@ -32126,7 +32388,7 @@ async function runZelariMission(userMessage, brief, deps) {
32126
32388
  continue;
32127
32389
  }
32128
32390
  const wrote = typeof result.writeCount === "number" && result.writeCount > 0;
32129
- if (result.completionOk || wrote) {
32391
+ if (completionOk || wrote) {
32130
32392
  const hard = result.completionOk === true;
32131
32393
  const commitRes = await tryStateCommit({
32132
32394
  projectRoot: deps.projectRoot,
@@ -32162,7 +32424,7 @@ async function runZelariMission(userMessage, brief, deps) {
32162
32424
  deps.emit(`[zelari] state commit skipped: ${commitRes.error}`);
32163
32425
  }
32164
32426
  }
32165
- if (result.completionOk) {
32427
+ if (completionOk) {
32166
32428
  state2.status = "success";
32167
32429
  await writeMissionState(deps.projectRoot, state2);
32168
32430
  deps.emit(
@@ -32206,6 +32468,290 @@ var init_zelariMission = __esm({
32206
32468
  }
32207
32469
  });
32208
32470
 
32471
+ // src/cli/missionSlice.ts
32472
+ var missionSlice_exports = {};
32473
+ __export(missionSlice_exports, {
32474
+ AGENT_MISSION_IMPLEMENTER_PREAMBLE: () => AGENT_MISSION_IMPLEMENTER_PREAMBLE,
32475
+ buildAgentMissionUserPrompt: () => buildAgentMissionUserPrompt,
32476
+ createWriteCounter: () => createWriteCounter,
32477
+ runAgentMissionSlice: () => runAgentMissionSlice
32478
+ });
32479
+ function createWriteCounter() {
32480
+ const state2 = { successfulWrites: 0, emittedWrites: 0 };
32481
+ const pending = /* @__PURE__ */ new Map();
32482
+ return {
32483
+ state: state2,
32484
+ onEvent(event) {
32485
+ if (event.type === "tool_execution_start") {
32486
+ const name = event.toolName ?? event.name ?? "";
32487
+ const id = event.toolCallId ?? "";
32488
+ if (id && name) pending.set(id, name);
32489
+ if (MUTATING.has(name)) state2.emittedWrites += 1;
32490
+ return;
32491
+ }
32492
+ if (event.type === "tool_execution_end") {
32493
+ const id = event.toolCallId ?? "";
32494
+ const name = pending.get(id) ?? event.toolName ?? event.name ?? "";
32495
+ if (id) pending.delete(id);
32496
+ if (!MUTATING.has(name) || event.isError) return;
32497
+ const result = String(event.result ?? "");
32498
+ const zeroEdit = name === "edit_file" && /occurrencesReplaced["']?\s*[:=]\s*0\b|0 occurrence|no changes/i.test(
32499
+ result
32500
+ );
32501
+ if (!zeroEdit) state2.successfulWrites += 1;
32502
+ }
32503
+ }
32504
+ };
32505
+ }
32506
+ function buildAgentMissionUserPrompt(slicePrompt, ragContext) {
32507
+ const body = `${AGENT_MISSION_IMPLEMENTER_PREAMBLE}
32508
+
32509
+ ${slicePrompt}`;
32510
+ if (ragContext?.trim()) {
32511
+ return `${body}
32512
+
32513
+ ## Memory context
32514
+ ${ragContext.trim()}`;
32515
+ }
32516
+ return body;
32517
+ }
32518
+ async function runAgentMissionSlice(deps) {
32519
+ const env = deps.env ?? process.env;
32520
+ const provider = deps.provider ?? "openai-compatible";
32521
+ const sessionId = deps.sessionId ?? crypto.randomUUID();
32522
+ const maxToolCalls = resolveAgentMissionToolBudget(env);
32523
+ const maxToolLoop = envNumber(env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
32524
+ default: 30,
32525
+ min: 1
32526
+ });
32527
+ const maxToolLoopHard = envNumber(env.ZELARI_MAX_TOOL_LOOP_HARD, {
32528
+ default: 0,
32529
+ min: 0
32530
+ });
32531
+ const writeRetry = deps.writeRetry !== false;
32532
+ const toolSpecs = deps.toolRegistry.toOpenAITools().map((t) => ({
32533
+ name: t.function.name,
32534
+ description: t.function.description,
32535
+ parameters: t.function.parameters
32536
+ }));
32537
+ const toolNames = toolSpecs.map((t) => t.name);
32538
+ let languageDirective;
32539
+ try {
32540
+ languageDirective = buildLanguagePolicyModuleFor(deps.slicePrompt).content;
32541
+ } catch {
32542
+ languageDirective = "# Response Language\nReply in the user's language when possible, otherwise Italian.";
32543
+ }
32544
+ const rolePrompt = [
32545
+ "# Platform",
32546
+ `platform: ${process.platform}`,
32547
+ `shell: ${process.platform === "win32" ? "cmd.exe / Git Bash (auto-detected)" : "/bin/sh"}`,
32548
+ "",
32549
+ "# Working Directory",
32550
+ `You are running in: ${deps.projectRoot}`,
32551
+ "All relative file paths are resolved against this directory.",
32552
+ "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template).",
32553
+ "",
32554
+ "# Work phase: BUILD (Zelari mission implementation slice)",
32555
+ "IMPLEMENT ON DISK. Prior design under .zelari/ is a SPEC, not proof of delivery.",
32556
+ "You MUST call write_file and/or edit_file before claiming done."
32557
+ ].join("\n");
32558
+ const headlessRole = {
32559
+ id: "mission-agent",
32560
+ name: "Zelari Code",
32561
+ codename: "zelari-build",
32562
+ role: "mission implementer",
32563
+ color: "#00d9a3",
32564
+ avatar: "\u25C6",
32565
+ tools: toolNames,
32566
+ systemPrompt: rolePrompt
32567
+ };
32568
+ let systemPrompt;
32569
+ try {
32570
+ systemPrompt = buildSystemPrompt(
32571
+ headlessRole,
32572
+ {
32573
+ tools: getAllTools(),
32574
+ toolNames,
32575
+ mode: "agent",
32576
+ projectInstructions: deps.projectInstructions || void 0,
32577
+ workspaceContext: deps.workspaceContext || void 0,
32578
+ ragContext: void 0,
32579
+ aiConfig: {
32580
+ enabledSkills: [],
32581
+ enabledTools: toolNames,
32582
+ customPromptModules: [
32583
+ SINGLE_AGENT_IDENTITY_MODULE,
32584
+ {
32585
+ type: "language-policy",
32586
+ title: "Response Language",
32587
+ priority: 5,
32588
+ content: languageDirective
32589
+ }
32590
+ ],
32591
+ agentSkillConfigs: []
32592
+ }
32593
+ }
32594
+ );
32595
+ } catch {
32596
+ systemPrompt = [
32597
+ "You are zelari-code, implementing a mission slice. Write real files.",
32598
+ languageDirective,
32599
+ deps.workspaceContext ?? ""
32600
+ ].filter(Boolean).join("\n\n");
32601
+ }
32602
+ const userContent = buildAgentMissionUserPrompt(
32603
+ deps.slicePrompt,
32604
+ deps.ragContext
32605
+ );
32606
+ async function runPass(messages, passId) {
32607
+ const counter = createWriteCounter();
32608
+ const harness = new AgentHarness({
32609
+ model: deps.model,
32610
+ provider,
32611
+ sessionId: passId,
32612
+ messages,
32613
+ tools: toolSpecs,
32614
+ toolRegistry: deps.toolRegistry,
32615
+ providerStream: deps.providerStream,
32616
+ cwd: deps.projectRoot,
32617
+ maxToolCallsPerTurn: maxToolCalls,
32618
+ maxToolLoopIterations: maxToolLoop,
32619
+ ...maxToolLoopHard > 0 ? { maxToolLoopHardCap: maxToolLoopHard } : {}
32620
+ });
32621
+ let text = "";
32622
+ let errored2 = false;
32623
+ try {
32624
+ for await (const event of harness.run()) {
32625
+ counter.onEvent(event);
32626
+ if (deps.onEvent) await deps.onEvent(event);
32627
+ if (event.type === "message_delta" && typeof event.delta === "string") {
32628
+ text += event.delta;
32629
+ }
32630
+ if (event.type === "agent_end" && event.reason === "error") {
32631
+ errored2 = true;
32632
+ }
32633
+ if (event.type === "error" && event.severity === "fatal") {
32634
+ errored2 = true;
32635
+ }
32636
+ }
32637
+ } catch {
32638
+ errored2 = true;
32639
+ }
32640
+ if (!text.trim()) {
32641
+ const all = harness.getMessages();
32642
+ for (let i = all.length - 1; i >= 0; i--) {
32643
+ const m = all[i];
32644
+ if (m?.role === "assistant" && (m.content ?? "").trim()) {
32645
+ text = m.content ?? "";
32646
+ break;
32647
+ }
32648
+ }
32649
+ }
32650
+ return {
32651
+ text,
32652
+ successfulWrites: counter.state.successfulWrites,
32653
+ emittedWrites: counter.state.emittedWrites,
32654
+ errored: errored2,
32655
+ messages: harness.getMessages()
32656
+ };
32657
+ }
32658
+ const initial = [
32659
+ { role: "system", content: systemPrompt },
32660
+ { role: "user", content: userContent }
32661
+ ];
32662
+ let pass = await runPass(initial, sessionId);
32663
+ let totalWrites = pass.successfulWrites;
32664
+ let totalEmitted = pass.emittedWrites;
32665
+ let synthesisText = pass.text;
32666
+ let errored = pass.errored;
32667
+ if (writeRetry && totalWrites === 0 && !errored) {
32668
+ deps.emit?.(
32669
+ "[zelari] build@agent: 0 write \u2014 forcing implementation retry"
32670
+ );
32671
+ const retryPrompt = buildImplementationWriteRetryPrompt(deps.slicePrompt);
32672
+ const retryMessages = [
32673
+ { role: "system", content: systemPrompt },
32674
+ ...pass.messages.filter((m) => m.role !== "system"),
32675
+ { role: "user", content: retryPrompt }
32676
+ ];
32677
+ const retry = await runPass(retryMessages, `${sessionId}-write-retry`);
32678
+ totalWrites += retry.successfulWrites;
32679
+ totalEmitted += retry.emittedWrites;
32680
+ if (retry.text.trim()) {
32681
+ synthesisText = synthesisText ? `${synthesisText}
32682
+
32683
+ ${retry.text}` : retry.text;
32684
+ }
32685
+ errored = errored || retry.errored;
32686
+ }
32687
+ const cleaned = cleanAgentContent(synthesisText, {
32688
+ stripQuestion: false,
32689
+ stripThink: false
32690
+ });
32691
+ let completionOk = false;
32692
+ let degraded = false;
32693
+ if (deps.runCompletionHook) {
32694
+ try {
32695
+ const hook = await deps.runCompletionHook({
32696
+ synthesisText: cleaned,
32697
+ writeCount: totalWrites,
32698
+ errored
32699
+ });
32700
+ completionOk = hook.completionOk;
32701
+ degraded = hook.degraded;
32702
+ } catch {
32703
+ }
32704
+ }
32705
+ if (!deps.runCompletionHook) {
32706
+ const d = detectDegradedRun({
32707
+ chairmanErrored: errored,
32708
+ luciferWriteCount: totalWrites,
32709
+ synthesisText: cleaned,
32710
+ runMode: "implementation"
32711
+ });
32712
+ degraded = d.degraded;
32713
+ completionOk = totalWrites > 0 && !errored && !degraded;
32714
+ }
32715
+ if (totalWrites === 0) {
32716
+ if (completionOk) {
32717
+ deps.emit?.(
32718
+ "[zelari] build@agent: completion.ok overridden \u2014 0 project writes (not done)"
32719
+ );
32720
+ }
32721
+ completionOk = false;
32722
+ const d = detectDegradedRun({
32723
+ chairmanErrored: errored,
32724
+ luciferWriteCount: 0,
32725
+ synthesisText: cleaned || "completato",
32726
+ runMode: "implementation"
32727
+ });
32728
+ if (d.degraded) degraded = true;
32729
+ else degraded = true;
32730
+ }
32731
+ void totalEmitted;
32732
+ return {
32733
+ completionOk,
32734
+ ran: true,
32735
+ synthesisText: cleaned || void 0,
32736
+ writeCount: totalWrites,
32737
+ degraded
32738
+ };
32739
+ }
32740
+ var MUTATING, AGENT_MISSION_IMPLEMENTER_PREAMBLE;
32741
+ var init_missionSlice = __esm({
32742
+ "src/cli/missionSlice.ts"() {
32743
+ "use strict";
32744
+ init_harness();
32745
+ init_skills2();
32746
+ init_council();
32747
+ init_dist();
32748
+ init_buildPolicy();
32749
+ init_envNumber();
32750
+ MUTATING = /* @__PURE__ */ new Set(["write_file", "edit_file", "apply_diff"]);
32751
+ AGENT_MISSION_IMPLEMENTER_PREAMBLE = "You are the sole implementer for this Zelari mission slice. A multi-agent council may already have produced a plan under `.zelari/` \u2014 treat it as a SPEC to apply on disk. You MUST create or modify real project files with write_file / edit_file. Prose without successful writes is a failed slice.";
32752
+ }
32753
+ });
32754
+
32209
32755
  // src/cli/utils/prereqChecks.ts
32210
32756
  var prereqChecks_exports = {};
32211
32757
  __export(prereqChecks_exports, {
@@ -36797,14 +37343,7 @@ init_keyStore();
36797
37343
  init_toolRegistry();
36798
37344
  init_skills2();
36799
37345
  init_fileStateStore();
36800
-
36801
- // packages/core/dist/events/index.js
36802
- init_events();
36803
-
36804
- // packages/core/dist/index.js
36805
- init_harness();
36806
- init_council();
36807
- init_skills2();
37346
+ init_dist();
36808
37347
 
36809
37348
  // src/cli/hooks/messageHelpers.ts
36810
37349
  function appendSystem(setMessages, content, ts = Date.now()) {
@@ -37746,8 +38285,23 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
37746
38285
  onCancel: () => finish(null)
37747
38286
  });
37748
38287
  }) : void 0;
38288
+ let phaseRunMode = overrides.runMode ?? (workPhase === "plan" ? "design-phase" : "implementation");
38289
+ let softGatedToDesign = false;
38290
+ if (phaseRunMode === "implementation") {
38291
+ const { shouldAllowCouncilBuild: shouldAllowCouncilBuild2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
38292
+ const allowed = overrides.allowCouncilBuild === true || shouldAllowCouncilBuild2();
38293
+ if (!allowed) {
38294
+ phaseRunMode = "design-phase";
38295
+ softGatedToDesign = true;
38296
+ appendSystem(
38297
+ setMessages,
38298
+ "[council] build soft-gate: implementation disabled for free-form council (experiment: multi-agent = plan). Forced design-phase + plan tools. Set ZELARI_COUNCIL_CAN_BUILD=1 to let Lucifero implement, or use mode agent/zelari for on-disk work.",
38299
+ Date.now()
38300
+ );
38301
+ }
38302
+ }
37749
38303
  const { registry: councilToolRegistry } = createBuiltinToolRegistry({
37750
- planMode: workPhase === "plan",
38304
+ planMode: workPhase === "plan" || softGatedToDesign,
37751
38305
  onAskUser: onAskUserCouncil
37752
38306
  });
37753
38307
  const workspaceCtx = createWorkspaceContext2();
@@ -37757,7 +38311,6 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
37757
38311
  if (!td) continue;
37758
38312
  councilToolRegistry.register(td);
37759
38313
  }
37760
- const phaseRunMode = overrides.runMode ?? (workPhase === "plan" ? "design-phase" : "implementation");
37761
38314
  try {
37762
38315
  const { registerMcpTools: registerMcpTools2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
37763
38316
  const mcp = await registerMcpTools2(councilToolRegistry, process.cwd(), {
@@ -38268,30 +38821,132 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
38268
38821
  default: 30,
38269
38822
  min: 1
38270
38823
  });
38824
+ const { shouldBuildViaAgent: shouldBuildViaAgent2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
38825
+ const buildViaAgent = shouldBuildViaAgent2();
38826
+ if (buildViaAgent) {
38827
+ emit(
38828
+ "[zelari] policy: design@council \xB7 build@agent (ZELARI_BUILD_VIA_AGENT; set=0 for legacy council impl)"
38829
+ );
38830
+ }
38271
38831
  try {
38272
38832
  await runZelariMission2(userMessage, brief, {
38273
38833
  projectRoot,
38274
38834
  memory,
38275
38835
  emit,
38836
+ buildViaAgent,
38276
38837
  runSlice: async ({
38277
38838
  userMessage: slicePrompt,
38278
38839
  runMode,
38279
38840
  ragContext,
38280
38841
  implementerRetry
38281
38842
  }) => {
38282
- const r = await dispatchCouncilPromptImpl(slicePrompt, deps, {
38283
- ragContext,
38284
- runMode,
38285
- maxToolCallsChairman: chairmanBudget,
38286
- ...implementerRetry ? { skipSpecialists: true } : {}
38843
+ if (runMode === "design-phase" || !buildViaAgent) {
38844
+ const r = await dispatchCouncilPromptImpl(slicePrompt, deps, {
38845
+ ragContext,
38846
+ runMode,
38847
+ maxToolCallsChairman: chairmanBudget,
38848
+ ...implementerRetry ? { skipSpecialists: true } : {},
38849
+ // Mission may need Lucifero to write even when free-form council is plan-only.
38850
+ allowCouncilBuild: true
38851
+ });
38852
+ return {
38853
+ completionOk: r.completionOk,
38854
+ ran: r.ran,
38855
+ synthesisText: r.synthesisText,
38856
+ writeCount: r.writeCount,
38857
+ degraded: r.degraded
38858
+ };
38859
+ }
38860
+ const { runAgentMissionSlice: runAgentMissionSlice2 } = await Promise.resolve().then(() => (init_missionSlice(), missionSlice_exports));
38861
+ const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
38862
+ const { loadDurableContext: loadDurableContext2 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
38863
+ const { createWorkspaceContext: createWorkspaceContext2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
38864
+ const { runPostCouncilHook: runPostCouncilHook2 } = await Promise.resolve().then(() => (init_postCouncilHook(), postCouncilHook_exports));
38865
+ const { detectDegradedRun: detectDegradedRun2 } = await Promise.resolve().then(() => (init_council(), council_exports));
38866
+ const envConfig2 = await providerFromEnv();
38867
+ if (!envConfig2) {
38868
+ emit(
38869
+ "[zelari] build@agent aborted: missing API key for active provider"
38870
+ );
38871
+ return { completionOk: false, ran: false };
38872
+ }
38873
+ const { registry: toolRegistry } = createBuiltinToolRegistry({
38874
+ planMode: false
38287
38875
  });
38288
- return {
38289
- completionOk: r.completionOk,
38290
- ran: r.ran,
38291
- synthesisText: r.synthesisText,
38292
- writeCount: r.writeCount,
38293
- degraded: r.degraded
38294
- };
38876
+ try {
38877
+ const { registerMcpTools: registerMcpTools2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
38878
+ await registerMcpTools2(toolRegistry, projectRoot);
38879
+ } catch {
38880
+ }
38881
+ const durableState = await loadDurableContext2(projectRoot);
38882
+ const composed = composeProjectContext2({
38883
+ mode: "zelari",
38884
+ cwd: projectRoot,
38885
+ userMessage: slicePrompt,
38886
+ memoryHits: ragContext,
38887
+ durableState: durableState || void 0,
38888
+ includeLessons: true,
38889
+ includeDurableState: false
38890
+ });
38891
+ for (const w of composed.warnings) {
38892
+ emit(w);
38893
+ }
38894
+ const workspaceCtx = createWorkspaceContext2(projectRoot);
38895
+ const { setMessages: setMessages2, writerRef, setBusy } = deps;
38896
+ setBusy(true);
38897
+ try {
38898
+ return await runAgentMissionSlice2({
38899
+ projectRoot,
38900
+ model: envConfig2.model,
38901
+ provider: "openai-compatible",
38902
+ providerStream: openaiCompatibleProvider(envConfig2),
38903
+ toolRegistry,
38904
+ slicePrompt,
38905
+ ragContext: composed.ragContext ?? ragContext,
38906
+ workspaceContext: composed.workspaceContext,
38907
+ projectInstructions: composed.projectInstructions,
38908
+ emit,
38909
+ onEvent: async (event) => {
38910
+ if (writerRef.current) await writerRef.current.append(event);
38911
+ if (event.type === "tool_execution_start") {
38912
+ const name = event.toolName ?? "tool";
38913
+ appendSystem(
38914
+ setMessages2,
38915
+ `[build@agent] \u2192 ${name}`,
38916
+ Date.now()
38917
+ );
38918
+ }
38919
+ },
38920
+ runCompletionHook: async ({ synthesisText, writeCount, errored }) => {
38921
+ const d = detectDegradedRun2({
38922
+ chairmanErrored: errored,
38923
+ luciferWriteCount: writeCount,
38924
+ synthesisText,
38925
+ runMode: "implementation"
38926
+ });
38927
+ if (d.degraded) {
38928
+ appendSystem(
38929
+ setMessages2,
38930
+ `[zelari] DEGRADED_RUN \u2014 ${d.reasons.join("; ")}`,
38931
+ Date.now()
38932
+ );
38933
+ }
38934
+ const hook = await runPostCouncilHook2(workspaceCtx, {
38935
+ runMode: "implementation",
38936
+ userMessage,
38937
+ synthesisText: synthesisText || void 0,
38938
+ degradedRun: d.degraded,
38939
+ degradedReasons: d.reasons
38940
+ });
38941
+ return {
38942
+ completionOk: hook.completion?.completion?.ok ?? false,
38943
+ degraded: d.degraded
38944
+ };
38945
+ }
38946
+ });
38947
+ } finally {
38948
+ setBusy(false);
38949
+ }
38295
38950
  }
38296
38951
  });
38297
38952
  } catch (err) {
@@ -39301,11 +39956,11 @@ function parseMode(input) {
39301
39956
  function describeMode(mode) {
39302
39957
  switch (mode) {
39303
39958
  case "council":
39304
- return "council \u2014 6-member pipeline (Caronte\u2026Lucifero)";
39959
+ return "council \u2014 multi-member plan/design (Caronte\u2026Lucifero; build needs ZELARI_COUNCIL_CAN_BUILD=1)";
39305
39960
  case "zelari":
39306
- return "zelari \u2014 autonomous multi-run mission";
39961
+ return "zelari \u2014 mission: plan@council \u2192 build@agent (legacy: ZELARI_BUILD_VIA_AGENT=0)";
39307
39962
  default:
39308
- return "agent \u2014 single LLM turn";
39963
+ return "agent \u2014 single LLM turn (default implementer)";
39309
39964
  }
39310
39965
  }
39311
39966
 
@@ -41966,6 +42621,7 @@ function emitEvent(event) {
41966
42621
 
41967
42622
  // src/cli/runHeadless.ts
41968
42623
  init_harness();
42624
+ init_dist();
41969
42625
  init_conversationContext();
41970
42626
  init_council();
41971
42627
  init_toolRegistry();
@@ -41975,6 +42631,7 @@ init_phaseState();
41975
42631
  init_phase();
41976
42632
 
41977
42633
  // src/cli/utils/streamScrub.ts
42634
+ init_dist();
41978
42635
  function createStreamScrubber() {
41979
42636
  let rawBuf = "";
41980
42637
  let emittedLen = 0;
@@ -42429,8 +43086,18 @@ async function buildCouncilToolRegistry(planMode, opts) {
42429
43086
  async function runHeadlessCouncil(opts, provider, model, providerStream) {
42430
43087
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
42431
43088
  const sessionId = crypto.randomUUID();
43089
+ const { shouldAllowCouncilBuild: shouldAllowCouncilBuild2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
43090
+ let councilRunMode = planModeFromOpts(opts) ? "design-phase" : "implementation";
43091
+ let softGated = false;
43092
+ if (councilRunMode === "implementation" && !shouldAllowCouncilBuild2()) {
43093
+ councilRunMode = "design-phase";
43094
+ softGated = true;
43095
+ process.stderr.write(
43096
+ "[zelari-code --headless] council build soft-gate: forced design-phase (set ZELARI_COUNCIL_CAN_BUILD=1 to allow Lucifero implement)\n"
43097
+ );
43098
+ }
42432
43099
  const { toolRegistry } = await buildCouncilToolRegistry(
42433
- planModeFromOpts(opts),
43100
+ planModeFromOpts(opts) || softGated,
42434
43101
  opts
42435
43102
  );
42436
43103
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
@@ -42470,7 +43137,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
42470
43137
  sessionId,
42471
43138
  tools: toolRegistry,
42472
43139
  feedbackStore,
42473
- runMode: planModeFromOpts(opts) ? "design-phase" : "implementation",
43140
+ runMode: councilRunMode,
42474
43141
  workspaceContext: composed.workspaceContext,
42475
43142
  ...composed.ragContext ? { ragContext: composed.ragContext } : {},
42476
43143
  maxToolLoopIterations: envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
@@ -42558,6 +43225,8 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
42558
43225
  default: 30,
42559
43226
  min: 1
42560
43227
  });
43228
+ const { shouldBuildViaAgent: shouldBuildViaAgent2 } = await Promise.resolve().then(() => (init_buildPolicy(), buildPolicy_exports));
43229
+ const buildViaAgent = shouldBuildViaAgent2();
42561
43230
  const emit = (message) => {
42562
43231
  if (opts.output === "json") {
42563
43232
  emitEvent({ type: "log", message });
@@ -42577,6 +43246,11 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
42577
43246
  const missionTask = buildCouncilTaskWithHistory(opts.task, historySeed);
42578
43247
  emit(`[zelari] mission brief
42579
43248
  ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMvp?.title }, null, 0)}`);
43249
+ if (buildViaAgent) {
43250
+ emit(
43251
+ "[zelari] policy: design@council \xB7 build@agent (set ZELARI_BUILD_VIA_AGENT=0 for legacy)"
43252
+ );
43253
+ }
42580
43254
  let exitCode = 0;
42581
43255
  let lastMissionAssistant = "";
42582
43256
  try {
@@ -42584,129 +43258,199 @@ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMv
42584
43258
  projectRoot,
42585
43259
  memory,
42586
43260
  emit,
43261
+ buildViaAgent,
42587
43262
  runSlice: async ({
42588
43263
  userMessage: slicePrompt,
42589
43264
  runMode,
42590
43265
  ragContext,
42591
43266
  implementerRetry
42592
43267
  }) => {
42593
- const sessionId = crypto.randomUUID();
42594
- const fullPrompt = ragContext ? `${slicePrompt}
43268
+ const effectiveRunMode = planModeFromOpts(opts) ? "design-phase" : runMode;
43269
+ if (effectiveRunMode === "design-phase" || !buildViaAgent) {
43270
+ const sessionId = crypto.randomUUID();
43271
+ const fullPrompt = ragContext ? `${slicePrompt}
42595
43272
 
42596
43273
  ## Memory context
42597
43274
  ${ragContext}` : slicePrompt;
42598
- let synthesisText = "";
42599
- let writeCount = 0;
42600
- let chairmanErrored = false;
42601
- let membersCompleted = 0;
42602
- const scrub = createStreamScrubber();
43275
+ let synthesisText = "";
43276
+ let writeCount = 0;
43277
+ let chairmanErrored = false;
43278
+ let membersCompleted = 0;
43279
+ const scrub = createStreamScrubber();
43280
+ const { composeProjectContext: composeProjectContext3 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
43281
+ const { loadDurableContext: loadDurableContext3 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
43282
+ const memOnly = ragContext?.trim() ? ragContext : void 0;
43283
+ const durableState2 = await loadDurableContext3(projectRoot);
43284
+ const composed2 = composeProjectContext3({
43285
+ mode: "zelari",
43286
+ cwd: projectRoot,
43287
+ userMessage: slicePrompt,
43288
+ memoryHits: memOnly,
43289
+ durableState: durableState2 || void 0,
43290
+ includeLessons: true,
43291
+ includeDurableState: false
43292
+ });
43293
+ for await (const event of dispatchCouncil2(fullPrompt, {
43294
+ apiKey: "REDACTED",
43295
+ model,
43296
+ provider: "openai-compatible",
43297
+ providerStream,
43298
+ sessionId,
43299
+ tools: toolRegistry,
43300
+ feedbackStore,
43301
+ runMode: effectiveRunMode,
43302
+ maxToolCallsChairman: chairmanBudget,
43303
+ ...implementerRetry ? { skipSpecialists: true } : {},
43304
+ workspaceContext: composed2.workspaceContext,
43305
+ ...composed2.ragContext ? { ragContext: composed2.ragContext } : {},
43306
+ maxToolLoopIterations: envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
43307
+ default: 30,
43308
+ min: 1
43309
+ }),
43310
+ ...(() => {
43311
+ const hard = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
43312
+ default: 0,
43313
+ min: 0
43314
+ });
43315
+ return hard > 0 ? { maxToolLoopHardCap: hard } : {};
43316
+ })()
43317
+ })) {
43318
+ if (event.type === "message_start") {
43319
+ scrub.reset();
43320
+ }
43321
+ if (event.type === "message_delta" && typeof event.delta === "string") {
43322
+ synthesisText += event.delta;
43323
+ const cleanDelta = scrub.push(event.delta);
43324
+ if (opts.output === "json") {
43325
+ if (cleanDelta.length > 0) emitEvent({ ...event, delta: cleanDelta });
43326
+ } else if (opts.output === "plain" && cleanDelta.length > 0) {
43327
+ process.stdout.write(cleanDelta);
43328
+ }
43329
+ } else if (opts.output === "json") {
43330
+ emitEvent(event);
43331
+ }
43332
+ if (event.type === "tool_execution_end") {
43333
+ const name = event.toolName ?? event.name ?? "";
43334
+ if (name === "write_file" || name === "edit_file" || name === "apply_diff") {
43335
+ writeCount += 1;
43336
+ }
43337
+ }
43338
+ if (event.type === "agent_end") {
43339
+ membersCompleted += 1;
43340
+ if (event.reason === "error") chairmanErrored = true;
43341
+ }
43342
+ if (event.type === "error" && event.severity === "fatal") {
43343
+ chairmanErrored = true;
43344
+ exitCode = 2;
43345
+ }
43346
+ }
43347
+ let completionOk = false;
43348
+ let degraded = false;
43349
+ try {
43350
+ const { detectDegradedRun: detectDegradedRun3 } = await Promise.resolve().then(() => (init_council(), council_exports));
43351
+ const d = detectDegradedRun3({
43352
+ chairmanErrored,
43353
+ councilAborted: false,
43354
+ luciferWriteCount: writeCount,
43355
+ synthesisText,
43356
+ runMode: effectiveRunMode
43357
+ });
43358
+ degraded = d.degraded;
43359
+ const hook = await runPostCouncilHook2(workspaceCtx, {
43360
+ runMode: effectiveRunMode,
43361
+ userMessage: opts.task,
43362
+ synthesisText: synthesisText || void 0,
43363
+ degradedRun: d.degraded,
43364
+ degradedReasons: d.reasons
43365
+ });
43366
+ completionOk = hook.completion?.completion?.ok ?? false;
43367
+ if (completionOk) {
43368
+ emit(`[zelari] slice completion ok`);
43369
+ }
43370
+ } catch {
43371
+ }
43372
+ if (synthesisText.trim()) {
43373
+ lastMissionAssistant = cleanAgentContent(synthesisText, {
43374
+ stripQuestion: false,
43375
+ stripThink: false
43376
+ });
43377
+ }
43378
+ return {
43379
+ completionOk,
43380
+ ran: membersCompleted > 0 || synthesisText.length > 0,
43381
+ synthesisText: synthesisText || void 0,
43382
+ writeCount,
43383
+ degraded
43384
+ };
43385
+ }
43386
+ const { runAgentMissionSlice: runAgentMissionSlice2 } = await Promise.resolve().then(() => (init_missionSlice(), missionSlice_exports));
43387
+ const { createBuiltinToolRegistry: createBuiltinToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
42603
43388
  const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
42604
43389
  const { loadDurableContext: loadDurableContext2 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
42605
- const memOnly = ragContext?.trim() ? ragContext : void 0;
43390
+ const { detectDegradedRun: detectDegradedRun2 } = await Promise.resolve().then(() => (init_council(), council_exports));
43391
+ const { registry: agentRegistry } = createBuiltinToolRegistry2({
43392
+ planMode: false
43393
+ });
43394
+ await registerHeadlessMcp(agentRegistry, opts);
42606
43395
  const durableState = await loadDurableContext2(projectRoot);
42607
43396
  const composed = composeProjectContext2({
42608
43397
  mode: "zelari",
42609
43398
  cwd: projectRoot,
42610
43399
  userMessage: slicePrompt,
42611
- memoryHits: memOnly,
43400
+ memoryHits: ragContext?.trim() ? ragContext : void 0,
42612
43401
  durableState: durableState || void 0,
42613
43402
  includeLessons: true,
42614
43403
  includeDurableState: false
42615
43404
  });
42616
- for await (const event of dispatchCouncil2(fullPrompt, {
42617
- apiKey: "REDACTED",
43405
+ const sliceResult = await runAgentMissionSlice2({
43406
+ projectRoot,
42618
43407
  model,
42619
43408
  provider: "openai-compatible",
42620
43409
  providerStream,
42621
- sessionId,
42622
- tools: toolRegistry,
42623
- feedbackStore,
42624
- runMode: planModeFromOpts(opts) ? "design-phase" : runMode,
42625
- maxToolCallsChairman: chairmanBudget,
42626
- ...implementerRetry ? { skipSpecialists: true } : {},
43410
+ toolRegistry: agentRegistry,
43411
+ slicePrompt,
43412
+ ragContext: composed.ragContext ?? ragContext,
42627
43413
  workspaceContext: composed.workspaceContext,
42628
- ...composed.ragContext ? { ragContext: composed.ragContext } : {},
42629
- maxToolLoopIterations: envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
42630
- default: 30,
42631
- min: 1
42632
- }),
42633
- ...(() => {
42634
- const hard = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, {
42635
- default: 0,
42636
- min: 0
42637
- });
42638
- return hard > 0 ? { maxToolLoopHardCap: hard } : {};
42639
- })()
42640
- })) {
42641
- if (event.type === "message_start") {
42642
- scrub.reset();
42643
- }
42644
- if (event.type === "message_delta" && typeof event.delta === "string") {
42645
- synthesisText += event.delta;
42646
- const cleanDelta = scrub.push(event.delta);
42647
- if (opts.output === "json") {
42648
- if (cleanDelta.length > 0) emitEvent({ ...event, delta: cleanDelta });
42649
- } else if (opts.output === "plain" && cleanDelta.length > 0) {
42650
- process.stdout.write(cleanDelta);
42651
- }
42652
- } else {
43414
+ projectInstructions: composed.projectInstructions,
43415
+ emit,
43416
+ onEvent: async (event) => {
42653
43417
  if (opts.output === "json") {
42654
- emitEvent(event);
43418
+ if (event.type === "message_delta" && typeof event.delta === "string") {
43419
+ emitEvent(event);
43420
+ } else {
43421
+ emitEvent(event);
43422
+ }
43423
+ } else if (opts.output === "plain" && event.type === "message_delta" && typeof event.delta === "string") {
43424
+ process.stdout.write(event.delta);
42655
43425
  }
42656
- }
42657
- if (event.type === "tool_execution_end") {
42658
- const name = event.toolName ?? event.name ?? "";
42659
- if (name === "write_file" || name === "edit_file" || name === "apply_diff") {
42660
- writeCount += 1;
43426
+ },
43427
+ runCompletionHook: async ({ synthesisText, writeCount, errored }) => {
43428
+ const d = detectDegradedRun2({
43429
+ chairmanErrored: errored,
43430
+ luciferWriteCount: writeCount,
43431
+ synthesisText,
43432
+ runMode: "implementation"
43433
+ });
43434
+ const hook = await runPostCouncilHook2(workspaceCtx, {
43435
+ runMode: "implementation",
43436
+ userMessage: opts.task,
43437
+ synthesisText: synthesisText || void 0,
43438
+ degradedRun: d.degraded,
43439
+ degradedReasons: d.reasons
43440
+ });
43441
+ if (hook.completion?.completion?.ok) {
43442
+ emit(`[zelari] slice completion ok`);
42661
43443
  }
43444
+ return {
43445
+ completionOk: hook.completion?.completion?.ok ?? false,
43446
+ degraded: d.degraded
43447
+ };
42662
43448
  }
42663
- if (event.type === "agent_end") {
42664
- membersCompleted += 1;
42665
- if (event.reason === "error") chairmanErrored = true;
42666
- }
42667
- if (event.type === "error" && event.severity === "fatal") {
42668
- chairmanErrored = true;
42669
- exitCode = 2;
42670
- }
42671
- }
42672
- let completionOk = false;
42673
- let degraded = false;
42674
- try {
42675
- const { detectDegradedRun: detectDegradedRun2 } = await Promise.resolve().then(() => (init_council(), council_exports));
42676
- const d = detectDegradedRun2({
42677
- chairmanErrored,
42678
- councilAborted: false,
42679
- luciferWriteCount: writeCount,
42680
- synthesisText,
42681
- runMode
42682
- });
42683
- degraded = d.degraded;
42684
- const hook = await runPostCouncilHook2(workspaceCtx, {
42685
- runMode,
42686
- userMessage: opts.task,
42687
- synthesisText: synthesisText || void 0,
42688
- degradedRun: d.degraded,
42689
- degradedReasons: d.reasons
42690
- });
42691
- completionOk = hook.completion?.completion?.ok ?? false;
42692
- if (completionOk) {
42693
- emit(`[zelari] slice completion ok`);
42694
- }
42695
- } catch {
42696
- }
42697
- if (synthesisText.trim()) {
42698
- lastMissionAssistant = cleanAgentContent(synthesisText, {
42699
- stripQuestion: false,
42700
- stripThink: false
42701
- });
43449
+ });
43450
+ if (sliceResult.synthesisText?.trim()) {
43451
+ lastMissionAssistant = sliceResult.synthesisText;
42702
43452
  }
42703
- return {
42704
- completionOk,
42705
- ran: membersCompleted > 0 || synthesisText.length > 0,
42706
- synthesisText: synthesisText || void 0,
42707
- writeCount,
42708
- degraded
42709
- };
43453
+ return sliceResult;
42710
43454
  }
42711
43455
  });
42712
43456
  if (state2.status === "error") exitCode = exitCode || 3;