zelari-code 2.34.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cli/browser/tools.js +23 -1
  2. package/dist/cli/browser/tools.js.map +1 -1
  3. package/dist/cli/councilDispatcher.js +1 -0
  4. package/dist/cli/councilDispatcher.js.map +1 -1
  5. package/dist/cli/headless/liveTurnAbort.js +67 -0
  6. package/dist/cli/headless/liveTurnAbort.js.map +1 -0
  7. package/dist/cli/headless/runOneTurn.js +5 -1
  8. package/dist/cli/headless/runOneTurn.js.map +1 -1
  9. package/dist/cli/headless.js.map +1 -1
  10. package/dist/cli/main.bundled.js +1352 -818
  11. package/dist/cli/main.bundled.js.map +4 -4
  12. package/dist/cli/provider/openai-compatible.js +53 -24
  13. package/dist/cli/provider/openai-compatible.js.map +1 -1
  14. package/dist/cli/runHeadless.js +55 -7
  15. package/dist/cli/runHeadless.js.map +1 -1
  16. package/dist/cli/serve/askUserBridge.js +59 -0
  17. package/dist/cli/serve/askUserBridge.js.map +1 -0
  18. package/dist/cli/serve/harnessServer.js +14 -2
  19. package/dist/cli/serve/harnessServer.js.map +1 -1
  20. package/dist/cli/serve/permissionBridge.js +64 -8
  21. package/dist/cli/serve/permissionBridge.js.map +1 -1
  22. package/dist/cli/serve/sessionControl.js +5 -3
  23. package/dist/cli/serve/sessionControl.js.map +1 -1
  24. package/dist/cli/toolRegistry.js +44 -17
  25. package/dist/cli/toolRegistry.js.map +1 -1
  26. package/dist/cli/tools/krakenModel.js +18 -0
  27. package/dist/cli/tools/krakenModel.js.map +1 -1
  28. package/dist/cli/tools/screenshotTool.js +111 -0
  29. package/dist/cli/tools/screenshotTool.js.map +1 -0
  30. package/dist/cli/tools/taskTool.js +59 -9
  31. package/dist/cli/tools/taskTool.js.map +1 -1
  32. package/dist/cli/utils/doctor.js +24 -3
  33. package/dist/cli/utils/doctor.js.map +1 -1
  34. package/dist/cli/utils/fixPath.js +18 -14
  35. package/dist/cli/utils/fixPath.js.map +1 -1
  36. package/dist/cli/utils/streamScrub.js +5 -3
  37. package/dist/cli/utils/streamScrub.js.map +1 -1
  38. package/dist/cli/zelariMission.js +14 -0
  39. package/dist/cli/zelariMission.js.map +1 -1
  40. package/package.json +2 -2
@@ -3414,10 +3414,10 @@ function mergeDefs(...defs) {
3414
3414
  function cloneDef(schema) {
3415
3415
  return mergeDefs(schema._zod.def);
3416
3416
  }
3417
- function getElementAtPath(obj, path99) {
3418
- if (!path99)
3417
+ function getElementAtPath(obj, path100) {
3418
+ if (!path100)
3419
3419
  return obj;
3420
- return path99.reduce((acc, key) => acc?.[key], obj);
3420
+ return path100.reduce((acc, key) => acc?.[key], obj);
3421
3421
  }
3422
3422
  function promiseAllObject(promisesObj) {
3423
3423
  const keys = Object.keys(promisesObj);
@@ -3745,11 +3745,11 @@ function explicitlyAborted(x, startIndex = 0) {
3745
3745
  }
3746
3746
  return false;
3747
3747
  }
3748
- function prefixIssues(path99, issues) {
3748
+ function prefixIssues(path100, issues) {
3749
3749
  return issues.map((iss) => {
3750
3750
  var _a3;
3751
3751
  (_a3 = iss).path ?? (_a3.path = []);
3752
- iss.path.unshift(path99);
3752
+ iss.path.unshift(path100);
3753
3753
  return iss;
3754
3754
  });
3755
3755
  }
@@ -3967,16 +3967,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
3967
3967
  }
3968
3968
  function formatError(error51, mapper = (issue2) => issue2.message) {
3969
3969
  const fieldErrors = { _errors: [] };
3970
- const processError = (error52, path99 = []) => {
3970
+ const processError = (error52, path100 = []) => {
3971
3971
  for (const issue2 of error52.issues) {
3972
3972
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3973
- issue2.errors.map((issues) => processError({ issues }, [...path99, ...issue2.path]));
3973
+ issue2.errors.map((issues) => processError({ issues }, [...path100, ...issue2.path]));
3974
3974
  } else if (issue2.code === "invalid_key") {
3975
- processError({ issues: issue2.issues }, [...path99, ...issue2.path]);
3975
+ processError({ issues: issue2.issues }, [...path100, ...issue2.path]);
3976
3976
  } else if (issue2.code === "invalid_element") {
3977
- processError({ issues: issue2.issues }, [...path99, ...issue2.path]);
3977
+ processError({ issues: issue2.issues }, [...path100, ...issue2.path]);
3978
3978
  } else {
3979
- const fullpath = [...path99, ...issue2.path];
3979
+ const fullpath = [...path100, ...issue2.path];
3980
3980
  if (fullpath.length === 0) {
3981
3981
  fieldErrors._errors.push(mapper(issue2));
3982
3982
  } else {
@@ -4003,17 +4003,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
4003
4003
  }
4004
4004
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
4005
4005
  const result = { errors: [] };
4006
- const processError = (error52, path99 = []) => {
4006
+ const processError = (error52, path100 = []) => {
4007
4007
  var _a3, _b;
4008
4008
  for (const issue2 of error52.issues) {
4009
4009
  if (issue2.code === "invalid_union" && issue2.errors.length) {
4010
- issue2.errors.map((issues) => processError({ issues }, [...path99, ...issue2.path]));
4010
+ issue2.errors.map((issues) => processError({ issues }, [...path100, ...issue2.path]));
4011
4011
  } else if (issue2.code === "invalid_key") {
4012
- processError({ issues: issue2.issues }, [...path99, ...issue2.path]);
4012
+ processError({ issues: issue2.issues }, [...path100, ...issue2.path]);
4013
4013
  } else if (issue2.code === "invalid_element") {
4014
- processError({ issues: issue2.issues }, [...path99, ...issue2.path]);
4014
+ processError({ issues: issue2.issues }, [...path100, ...issue2.path]);
4015
4015
  } else {
4016
- const fullpath = [...path99, ...issue2.path];
4016
+ const fullpath = [...path100, ...issue2.path];
4017
4017
  if (fullpath.length === 0) {
4018
4018
  result.errors.push(mapper(issue2));
4019
4019
  continue;
@@ -4045,8 +4045,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
4045
4045
  }
4046
4046
  function toDotPath(_path) {
4047
4047
  const segs = [];
4048
- const path99 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4049
- for (const seg of path99) {
4048
+ const path100 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4049
+ for (const seg of path100) {
4050
4050
  if (typeof seg === "number")
4051
4051
  segs.push(`[${seg}]`);
4052
4052
  else if (typeof seg === "symbol")
@@ -17549,13 +17549,13 @@ function resolveRef(ref, ctx) {
17549
17549
  if (!ref.startsWith("#")) {
17550
17550
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17551
17551
  }
17552
- const path99 = ref.slice(1).split("/").filter(Boolean);
17553
- if (path99.length === 0) {
17552
+ const path100 = ref.slice(1).split("/").filter(Boolean);
17553
+ if (path100.length === 0) {
17554
17554
  return ctx.rootSchema;
17555
17555
  }
17556
17556
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17557
- if (path99[0] === defsKey) {
17558
- const key = path99[1];
17557
+ if (path100[0] === defsKey) {
17558
+ const key = path100[1];
17559
17559
  if (!key || !ctx.defs[key]) {
17560
17560
  throw new Error(`Reference not found: ${ref}`);
17561
17561
  }
@@ -18327,7 +18327,9 @@ var init_zod = __esm({
18327
18327
  });
18328
18328
 
18329
18329
  // packages/core/dist/core/tools/toolTypes.js
18330
- function typedOk(value, meta3) {
18330
+ function typedOk(value, meta3, images) {
18331
+ if (images && images.length > 0)
18332
+ return { ok: true, value, meta: meta3, images };
18331
18333
  return meta3 ? { ok: true, value, meta: meta3 } : { ok: true, value };
18332
18334
  }
18333
18335
  function typedErr(error51, meta3) {
@@ -18387,17 +18389,17 @@ var init_newlines = __esm({
18387
18389
  });
18388
18390
 
18389
18391
  // packages/core/dist/core/tools/builtin/fileEvents.js
18390
- function fileReadEvent(path99, snapshotId) {
18391
- return { kind: "file.read", actor: { type: "tool" }, data: { path: path99, snapshotId } };
18392
+ function fileReadEvent(path100, snapshotId) {
18393
+ return { kind: "file.read", actor: { type: "tool" }, data: { path: path100, snapshotId } };
18392
18394
  }
18393
- function fileAppliedEvent(path99, snapshotId, bytes) {
18394
- return { kind: "file.applied", actor: { type: "tool" }, data: { path: path99, snapshotId, bytes } };
18395
+ function fileAppliedEvent(path100, snapshotId, bytes) {
18396
+ return { kind: "file.applied", actor: { type: "tool" }, data: { path: path100, snapshotId, bytes } };
18395
18397
  }
18396
- function fileRejectedEvent(path99, reason, hint) {
18398
+ function fileRejectedEvent(path100, reason, hint) {
18397
18399
  return {
18398
18400
  kind: "file.rejected",
18399
18401
  actor: { type: "tool" },
18400
- data: hint === void 0 ? { path: path99, reason } : { path: path99, reason, hint }
18402
+ data: hint === void 0 ? { path: path100, reason } : { path: path100, reason, hint }
18401
18403
  };
18402
18404
  }
18403
18405
  function reReadHint(reject) {
@@ -19313,8 +19315,8 @@ async function searchFile(absPath, relPath, regex, contextLines, remainingSlots)
19313
19315
  }
19314
19316
  async function isDirectory(p3) {
19315
19317
  try {
19316
- const stat7 = await fs7.stat(p3);
19317
- return stat7.isDirectory();
19318
+ const stat8 = await fs7.stat(p3);
19319
+ return stat8.isDirectory();
19318
19320
  } catch {
19319
19321
  return false;
19320
19322
  }
@@ -20266,11 +20268,11 @@ var init_tools = __esm({
20266
20268
  if (!ctx.addDocument)
20267
20269
  return "Knowledge vault tool not available.";
20268
20270
  const title = args["title"] || "New Document";
20269
- const path99 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
20271
+ const path100 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
20270
20272
  const content = args["content"] || "";
20271
20273
  const tags = args["tags"] || [];
20272
20274
  ctx.addDocument({
20273
- path: path99,
20275
+ path: path100,
20274
20276
  title,
20275
20277
  content,
20276
20278
  format: "markdown",
@@ -20279,7 +20281,7 @@ var init_tools = __esm({
20279
20281
  workspaceId: ctx.workspaceId
20280
20282
  });
20281
20283
  ctx.addActivity("vault", "created document", title);
20282
- return `Document "${title}" created at "${path99}".`;
20284
+ return `Document "${title}" created at "${path100}".`;
20283
20285
  }
20284
20286
  }
20285
20287
  ];
@@ -25104,16 +25106,16 @@ function runRetentionFromEnv() {
25104
25106
  maxTotalBytes: Number.isFinite(parseMb) && parseMb > 0 ? Math.round(parseMb * 1024 * 1024) : DEFAULT_RUN_RETENTION_MAX_MB * 1024 * 1024
25105
25107
  };
25106
25108
  }
25107
- async function dirSize(path99) {
25109
+ async function dirSize(path100) {
25108
25110
  let total = 0;
25109
25111
  let entries;
25110
25112
  try {
25111
- entries = await readdir(path99, { withFileTypes: true });
25113
+ entries = await readdir(path100, { withFileTypes: true });
25112
25114
  } catch {
25113
25115
  return 0;
25114
25116
  }
25115
25117
  for (const entry of entries) {
25116
- const child = join3(path99, entry.name);
25118
+ const child = join3(path100, entry.name);
25117
25119
  if (entry.isDirectory())
25118
25120
  total += await dirSize(child);
25119
25121
  else {
@@ -25140,19 +25142,19 @@ async function enforceRunRetention(runsDir, options = {}) {
25140
25142
  for (const entry of entries) {
25141
25143
  if (!entry.isDirectory())
25142
25144
  continue;
25143
- const path99 = join3(runsDir, entry.name);
25145
+ const path100 = join3(runsDir, entry.name);
25144
25146
  let startedAt = 0;
25145
25147
  let endedAt;
25146
25148
  let completed = false;
25147
25149
  try {
25148
- const manifest = JSON.parse(await readFile(join3(path99, "manifest.json"), "utf8"));
25150
+ const manifest = JSON.parse(await readFile(join3(path100, "manifest.json"), "utf8"));
25149
25151
  startedAt = manifest.startedAt ?? 0;
25150
25152
  endedAt = manifest.endedAt;
25151
25153
  completed = Boolean(endedAt) && manifest.status !== "running";
25152
25154
  } catch {
25153
25155
  completed = false;
25154
25156
  }
25155
- infos.push({ name: entry.name, path: path99, startedAt, endedAt, completed, bytes: await dirSize(path99) });
25157
+ infos.push({ name: entry.name, path: path100, startedAt, endedAt, completed, bytes: await dirSize(path100) });
25156
25158
  }
25157
25159
  const remove = async (info) => {
25158
25160
  await rm(info.path, { recursive: true, force: true });
@@ -25639,12 +25641,12 @@ var init_engine = __esm({
25639
25641
  * content digest) and the returned ref carries the event seq when the
25640
25642
  * emitter resolved one.
25641
25643
  */
25642
- async fsEvidence(observation, path99, sha256, content, extra = {}) {
25644
+ async fsEvidence(observation, path100, sha256, content, extra = {}) {
25643
25645
  const digest = sha256 && content !== void 0 ? sha256(content) : void 0;
25644
- const seq = await this.emitEvidence({ observation, path: path99, ...extra, ...digest ? { digest } : {} });
25646
+ const seq = await this.emitEvidence({ observation, path: path100, ...extra, ...digest ? { digest } : {} });
25645
25647
  return {
25646
25648
  tier: "fs-observation",
25647
- ref: path99,
25649
+ ref: path100,
25648
25650
  capturedAt: Date.now(),
25649
25651
  ...digest ? { digest } : {},
25650
25652
  ...seq !== void 0 ? { seq } : {}
@@ -28372,6 +28374,136 @@ var init_textLoopDetect = __esm({
28372
28374
  }
28373
28375
  });
28374
28376
 
28377
+ // packages/core/dist/agents/council/outputCleaning.js
28378
+ function extractBalancedJsonObject(s) {
28379
+ const start = s.indexOf("{");
28380
+ if (start < 0)
28381
+ return null;
28382
+ let depth = 0;
28383
+ let inString = false;
28384
+ let escape = false;
28385
+ for (let i = start; i < s.length; i++) {
28386
+ const ch = s[i];
28387
+ if (inString) {
28388
+ if (escape) {
28389
+ escape = false;
28390
+ continue;
28391
+ }
28392
+ if (ch === "\\") {
28393
+ escape = true;
28394
+ continue;
28395
+ }
28396
+ if (ch === '"')
28397
+ inString = false;
28398
+ continue;
28399
+ }
28400
+ if (ch === '"') {
28401
+ inString = true;
28402
+ continue;
28403
+ }
28404
+ if (ch === "{")
28405
+ depth++;
28406
+ else if (ch === "}") {
28407
+ depth--;
28408
+ if (depth === 0)
28409
+ return s.slice(start, i + 1);
28410
+ }
28411
+ }
28412
+ return null;
28413
+ }
28414
+ function parseClarificationRequest(text) {
28415
+ const start = text.indexOf(QUESTION_MARKER);
28416
+ if (start < 0)
28417
+ return null;
28418
+ const rest = text.slice(start + QUESTION_MARKER.length);
28419
+ const end = rest.indexOf(QUESTION_END_MARKER);
28420
+ const block = end >= 0 ? rest.slice(0, end) : rest;
28421
+ const cleaned = block.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
28422
+ const jsonText = extractBalancedJsonObject(cleaned) ?? (() => {
28423
+ const objStart = cleaned.indexOf("{");
28424
+ const objEnd = cleaned.lastIndexOf("}");
28425
+ return objStart >= 0 && objEnd > objStart ? cleaned.slice(objStart, objEnd + 1) : cleaned;
28426
+ })();
28427
+ try {
28428
+ const parsed = JSON.parse(jsonText);
28429
+ if (typeof parsed.question !== "string" || !parsed.question.trim())
28430
+ return null;
28431
+ return {
28432
+ question: parsed.question.trim(),
28433
+ choices: Array.isArray(parsed.choices) ? parsed.choices.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : void 0,
28434
+ context: typeof parsed.context === "string" ? parsed.context.trim() : void 0
28435
+ };
28436
+ } catch {
28437
+ return null;
28438
+ }
28439
+ }
28440
+ function hasInteractiveClarification(text) {
28441
+ const c = parseClarificationRequest(text);
28442
+ return !!(c && c.choices && c.choices.length >= 2);
28443
+ }
28444
+ function stripQuestionBlocks(text) {
28445
+ let out = "";
28446
+ let rest = text;
28447
+ while (true) {
28448
+ const start = rest.indexOf(QUESTION_MARKER);
28449
+ if (start < 0) {
28450
+ out += rest;
28451
+ break;
28452
+ }
28453
+ out += rest.slice(0, start);
28454
+ const afterMarker = rest.slice(start + QUESTION_MARKER.length);
28455
+ const trimmed = afterMarker.replace(/^\s+/, "");
28456
+ if (!trimmed.startsWith("{")) {
28457
+ out += QUESTION_MARKER;
28458
+ rest = afterMarker;
28459
+ continue;
28460
+ }
28461
+ const endIdx = afterMarker.indexOf(QUESTION_END_MARKER);
28462
+ if (endIdx >= 0) {
28463
+ rest = afterMarker.slice(endIdx + QUESTION_END_MARKER.length);
28464
+ continue;
28465
+ }
28466
+ const json3 = extractBalancedJsonObject(trimmed);
28467
+ if (json3) {
28468
+ const jsonAt = afterMarker.indexOf(json3);
28469
+ rest = afterMarker.slice(jsonAt + json3.length);
28470
+ continue;
28471
+ }
28472
+ break;
28473
+ }
28474
+ return out.replace(/\n{3,}/g, "\n\n").trim();
28475
+ }
28476
+ function parseThinking(text) {
28477
+ const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
28478
+ if (complete)
28479
+ return complete[1].trim();
28480
+ const open2 = text.match(/<think(?:ing)?>([\s\S]*)$/i);
28481
+ return open2 ? open2[1].trim() : "";
28482
+ }
28483
+ function cleanAgentContent(text, opts = {}) {
28484
+ const stripQuestion = opts.stripQuestion !== false;
28485
+ const stripThink = opts.stripThink !== false;
28486
+ let out = text;
28487
+ if (stripThink) {
28488
+ out = out.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "");
28489
+ }
28490
+ 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, "");
28491
+ if (stripQuestion) {
28492
+ out = stripQuestionBlocks(out);
28493
+ }
28494
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
28495
+ return scrubProprietaryLeak(out);
28496
+ }
28497
+ var QUESTION_MARKER, QUESTION_END_MARKER;
28498
+ var init_outputCleaning = __esm({
28499
+ "packages/core/dist/agents/council/outputCleaning.js"() {
28500
+ "use strict";
28501
+ init_secrecyPolicy();
28502
+ QUESTION_MARKER = "---QUESTION---";
28503
+ QUESTION_END_MARKER = "---END---";
28504
+ }
28505
+ });
28506
+
28375
28507
  // packages/core/dist/core/AgentHarness.js
28376
28508
  function gateAdvice(hardLimit) {
28377
28509
  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 +28800,7 @@ var init_AgentHarness = __esm({
28668
28800
  init_requestSnapshot();
28669
28801
  init_textLoopDetect();
28670
28802
  init_ObserverBus();
28803
+ init_outputCleaning();
28671
28804
  init_textLoopDetect();
28672
28805
  TOOL_CALL_TRUNCATED_RECOVERY_MARKER = "[harness] Previous tool call was truncated";
28673
28806
  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.`;
@@ -28975,7 +29108,8 @@ ${shared.content}`,
28975
29108
  return {
28976
29109
  content: resultStr,
28977
29110
  isError: !result.ok,
28978
- durationMs: Date.now() - startMs
29111
+ durationMs: Date.now() - startMs,
29112
+ ...result.ok && result.images && result.images.length > 0 ? { images: result.images } : {}
28979
29113
  };
28980
29114
  })();
28981
29115
  inflight.set(callKey, prom);
@@ -28996,7 +29130,8 @@ ${shared.content}`,
28996
29130
  toolCallId: p3.toolCallId,
28997
29131
  content: r.content,
28998
29132
  isError: r.isError,
28999
- endEvent
29133
+ endEvent,
29134
+ ...r.images ? { images: r.images } : {}
29000
29135
  // Cache already written in invokeOne; no need to re-set.
29001
29136
  };
29002
29137
  };
@@ -29548,7 +29683,8 @@ ${cached2}`
29548
29683
  yield item.endEvent;
29549
29684
  turnToolResults.push({
29550
29685
  toolCallId: item.toolCallId,
29551
- content: item.content
29686
+ content: item.content,
29687
+ ...item.images ? { images: item.images } : {}
29552
29688
  });
29553
29689
  if (item.cacheKey && item.content && !item.isError) {
29554
29690
  this.toolCallCache.set(item.cacheKey, item.content);
@@ -29562,7 +29698,7 @@ ${cached2}`
29562
29698
  }
29563
29699
  pendingNativeTools.length = 0;
29564
29700
  }
29565
- const clarificationPause = /---QUESTION---/.test(turnText) && /"choices"\s*:\s*\[/.test(turnText);
29701
+ const clarificationPause = hasInteractiveClarification(turnText);
29566
29702
  if (clarificationPause) {
29567
29703
  finishRef.value = "stop";
29568
29704
  finishRef.clarificationRequested = true;
@@ -29728,7 +29864,8 @@ ${cached2}`
29728
29864
  this.config.messages.push({
29729
29865
  role: "tool",
29730
29866
  toolCallId: tr.toolCallId,
29731
- content: tr.content
29867
+ content: tr.content,
29868
+ ...tr.images ? { images: tr.images } : {}
29732
29869
  });
29733
29870
  }
29734
29871
  if (truncatedToolCall) {
@@ -31529,11 +31666,11 @@ var init_synthesisAudit = __esm({
31529
31666
  import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
31530
31667
  import { join as join5 } from "node:path";
31531
31668
  function loadNfrSpec(zelariRoot) {
31532
- const path99 = join5(zelariRoot, "nfr-spec.json");
31533
- if (!existsSync10(path99))
31669
+ const path100 = join5(zelariRoot, "nfr-spec.json");
31670
+ if (!existsSync10(path100))
31534
31671
  return null;
31535
31672
  try {
31536
- const raw = JSON.parse(readFileSync9(path99, "utf8"));
31673
+ const raw = JSON.parse(readFileSync9(path100, "utf8"));
31537
31674
  if (raw.version !== 1 || !Array.isArray(raw.targets))
31538
31675
  return null;
31539
31676
  return raw;
@@ -32128,101 +32265,36 @@ var init_types7 = __esm({
32128
32265
  }
32129
32266
  });
32130
32267
 
32131
- // packages/core/dist/agents/council/outputCleaning.js
32132
- function extractBalancedJsonObject(s) {
32133
- const start = s.indexOf("{");
32134
- if (start < 0)
32135
- return null;
32136
- let depth = 0;
32137
- let inString = false;
32138
- let escape = false;
32139
- for (let i = start; i < s.length; i++) {
32140
- const ch = s[i];
32141
- if (inString) {
32142
- if (escape) {
32143
- escape = false;
32144
- continue;
32145
- }
32146
- if (ch === "\\") {
32147
- escape = true;
32148
- continue;
32149
- }
32150
- if (ch === '"')
32151
- inString = false;
32152
- continue;
32153
- }
32154
- if (ch === '"') {
32155
- inString = true;
32156
- continue;
32157
- }
32158
- if (ch === "{")
32159
- depth++;
32160
- else if (ch === "}") {
32161
- depth--;
32162
- if (depth === 0)
32163
- return s.slice(start, i + 1);
32164
- }
32165
- }
32166
- return null;
32268
+ // packages/core/dist/agents/council/cancel.js
32269
+ function isCouncilCancelled(signal) {
32270
+ return signal?.aborted === true;
32167
32271
  }
32168
- function parseClarificationRequest(text) {
32169
- const start = text.indexOf(QUESTION_MARKER);
32170
- if (start < 0)
32171
- return null;
32172
- const rest = text.slice(start + QUESTION_MARKER.length);
32173
- const end = rest.indexOf(QUESTION_END_MARKER);
32174
- const block = end >= 0 ? rest.slice(0, end) : rest;
32175
- const cleaned = block.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
32176
- const jsonText = extractBalancedJsonObject(cleaned) ?? (() => {
32177
- const objStart = cleaned.indexOf("{");
32178
- const objEnd = cleaned.lastIndexOf("}");
32179
- return objStart >= 0 && objEnd > objStart ? cleaned.slice(objStart, objEnd + 1) : cleaned;
32180
- })();
32181
- try {
32182
- const parsed = JSON.parse(jsonText);
32183
- if (typeof parsed.question !== "string" || !parsed.question.trim())
32184
- return null;
32185
- return {
32186
- question: parsed.question.trim(),
32187
- choices: Array.isArray(parsed.choices) ? parsed.choices.filter((c) => typeof c === "string" && c.trim().length > 0).map((c) => c.trim()) : void 0,
32188
- context: typeof parsed.context === "string" ? parsed.context.trim() : void 0
32189
- };
32190
- } catch {
32191
- return null;
32272
+ function bindHarnessAbort(harness, signal) {
32273
+ if (!signal)
32274
+ return () => void 0;
32275
+ if (signal.aborted) {
32276
+ harness.cancel();
32277
+ return () => void 0;
32192
32278
  }
32279
+ const onAbort = () => {
32280
+ harness.cancel();
32281
+ };
32282
+ signal.addEventListener("abort", onAbort);
32283
+ return () => {
32284
+ signal.removeEventListener("abort", onAbort);
32285
+ };
32193
32286
  }
32194
- function hasInteractiveClarification(text) {
32195
- const c = parseClarificationRequest(text);
32196
- return !!(c && c.choices && c.choices.length >= 2);
32197
- }
32198
- function parseThinking(text) {
32199
- const complete = text.match(/<think(?:ing)?>([\s\S]*?)<\/think(?:ing)?>/i);
32200
- if (complete)
32201
- return complete[1].trim();
32202
- const open2 = text.match(/<think(?:ing)?>([\s\S]*)$/i);
32203
- return open2 ? open2[1].trim() : "";
32204
- }
32205
- function cleanAgentContent(text, opts = {}) {
32206
- const stripQuestion = opts.stripQuestion !== false;
32207
- const stripThink = opts.stripThink !== false;
32208
- let out = text;
32209
- if (stripThink) {
32210
- out = out.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, "").replace(/<think(?:ing)?>[\s\S]*$/gi, "").replace(/<\/think(?:ing)?>/gi, "");
32211
- }
32212
- 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, "");
32213
- if (stripQuestion) {
32214
- out = out.replace(/---QUESTION---[\s\S]*?---END---/g, "").replace(/---QUESTION---[\s\S]*$/g, "");
32287
+ async function* runHarnessWithAbort(harness, signal) {
32288
+ const unbind = bindHarnessAbort(harness, signal);
32289
+ try {
32290
+ yield* harness.run();
32291
+ } finally {
32292
+ unbind();
32215
32293
  }
32216
- out = out.replace(/\n{3,}/g, "\n\n").trim();
32217
- return scrubProprietaryLeak(out);
32218
32294
  }
32219
- var QUESTION_MARKER, QUESTION_END_MARKER;
32220
- var init_outputCleaning = __esm({
32221
- "packages/core/dist/agents/council/outputCleaning.js"() {
32295
+ var init_cancel = __esm({
32296
+ "packages/core/dist/agents/council/cancel.js"() {
32222
32297
  "use strict";
32223
- init_secrecyPolicy();
32224
- QUESTION_MARKER = "---QUESTION---";
32225
- QUESTION_END_MARKER = "---END---";
32226
32298
  }
32227
32299
  });
32228
32300
 
@@ -32531,6 +32603,8 @@ function buildRetryPrompt(missingToolNames) {
32531
32603
  return `You did not emit the required workspace tools: ${names}. Call ${names} NOW with concrete arguments. No prose. No search.`;
32532
32604
  }
32533
32605
  async function* runRetryTurnForMember(args) {
32606
+ if (isCouncilCancelled(args.signal))
32607
+ return [];
32534
32608
  const executableMissing = args.executableTools ? args.missingToolNames.filter((n) => args.executableTools.has(n)) : args.missingToolNames;
32535
32609
  if (executableMissing.length === 0) {
32536
32610
  return [];
@@ -32566,7 +32640,7 @@ async function* runRetryTurnForMember(args) {
32566
32640
  providerStream: (params) => args.providerStream(params)
32567
32641
  });
32568
32642
  const retryEmitted = [];
32569
- for await (const event of retryHarness.run()) {
32643
+ for await (const event of runHarnessWithAbort(retryHarness, args.signal)) {
32570
32644
  if (event.type === "tool_execution_start") {
32571
32645
  retryEmitted.push(event.toolName);
32572
32646
  }
@@ -32575,6 +32649,8 @@ async function* runRetryTurnForMember(args) {
32575
32649
  return retryEmitted;
32576
32650
  }
32577
32651
  async function* applyRetryIfMissing(args) {
32652
+ if (isCouncilCancelled(args.config.signal))
32653
+ return;
32578
32654
  if (args.check.ok)
32579
32655
  return;
32580
32656
  const missingToolNames = args.check.missing.map((m) => m.split(" ")[0]);
@@ -32607,7 +32683,8 @@ async function* applyRetryIfMissing(args) {
32607
32683
  toolRegistry: args.config.tools,
32608
32684
  providerStream: args.config.providerStream,
32609
32685
  runMode: args.config.runMode,
32610
- languageModule: args.languageModule
32686
+ languageModule: args.languageModule,
32687
+ signal: args.config.signal
32611
32688
  });
32612
32689
  for await (const event of retryGenerator) {
32613
32690
  if (event.type === "tool_execution_start") {
@@ -32626,6 +32703,7 @@ var init_retryTurn = __esm({
32626
32703
  "packages/core/dist/agents/council/retryTurn.js"() {
32627
32704
  "use strict";
32628
32705
  init_AgentHarness();
32706
+ init_cancel();
32629
32707
  init_toolSchemas();
32630
32708
  init_memberMessages();
32631
32709
  init_toolEmission();
@@ -32635,6 +32713,8 @@ var init_retryTurn = __esm({
32635
32713
 
32636
32714
  // packages/core/dist/agents/council/chairmanDelivery.js
32637
32715
  async function* applyCompletionRetry(args) {
32716
+ if (isCouncilCancelled(args.config.signal))
32717
+ return;
32638
32718
  const check2 = checkImplementationCompletion(args.emittedToolNames);
32639
32719
  if (check2.ok)
32640
32720
  return;
@@ -32664,7 +32744,8 @@ async function* applyCompletionRetry(args) {
32664
32744
  providerStream: args.config.providerStream,
32665
32745
  runMode: args.config.runMode,
32666
32746
  retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
32667
- languageModule: args.languageModule
32747
+ languageModule: args.languageModule,
32748
+ signal: args.config.signal
32668
32749
  });
32669
32750
  for await (const event of retryGenerator) {
32670
32751
  if (event.type === "tool_execution_start") {
@@ -32697,6 +32778,8 @@ ${blocks}
32697
32778
  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.`;
32698
32779
  }
32699
32780
  async function* applyImplementationWriteRetry(args) {
32781
+ if (isCouncilCancelled(args.config.signal))
32782
+ return;
32700
32783
  if (args.check.ok)
32701
32784
  return;
32702
32785
  if (!shouldRetryMember(["write_file"], 0))
@@ -32723,7 +32806,8 @@ async function* applyImplementationWriteRetry(args) {
32723
32806
  providerStream: args.config.providerStream,
32724
32807
  runMode: "implementation",
32725
32808
  retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
32726
- languageModule: args.languageModule
32809
+ languageModule: args.languageModule,
32810
+ signal: args.config.signal
32727
32811
  });
32728
32812
  for await (const event of retryGenerator) {
32729
32813
  if (event.type === "tool_execution_start")
@@ -32751,6 +32835,8 @@ async function* runChairmanDeliveryLoop(args) {
32751
32835
  const zelariRoot = `${args.projectRoot}/.zelari`;
32752
32836
  let attempt = 0;
32753
32837
  while (attempt < maxAttempts) {
32838
+ if (isCouncilCancelled(args.config.signal))
32839
+ return false;
32754
32840
  const report = runImplementationVerification({
32755
32841
  projectRoot: args.projectRoot,
32756
32842
  zelariRoot
@@ -32793,7 +32879,8 @@ async function* runChairmanDeliveryLoop(args) {
32793
32879
  providerStream: args.config.providerStream,
32794
32880
  runMode: "implementation",
32795
32881
  retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
32796
- languageModule: args.languageModule
32882
+ languageModule: args.languageModule,
32883
+ signal: args.config.signal
32797
32884
  });
32798
32885
  for await (const event of fixGenerator) {
32799
32886
  if (event.type === "tool_execution_start")
@@ -32826,6 +32913,7 @@ var init_chairmanDelivery = __esm({
32826
32913
  init_runChecks();
32827
32914
  init_inlineJsAutofix();
32828
32915
  init_retryTurn();
32916
+ init_cancel();
32829
32917
  MAX_DELIVERY_ATTEMPTS = 2;
32830
32918
  }
32831
32919
  });
@@ -32887,6 +32975,8 @@ async function* runChairmanFixLoop(args) {
32887
32975
  let current = Array.from(args.violations.values());
32888
32976
  let attempt = 0;
32889
32977
  while (current.length > 0 && attempt < maxAttempts) {
32978
+ if (isCouncilCancelled(args.config.signal))
32979
+ return;
32890
32980
  attempt++;
32891
32981
  try {
32892
32982
  const fixGenerator = runRetryTurnForMember({
@@ -32907,7 +32997,8 @@ async function* runChairmanFixLoop(args) {
32907
32997
  providerStream: args.config.providerStream,
32908
32998
  runMode: "implementation",
32909
32999
  retryPrompt: buildMotionFixPrompt(current),
32910
- languageModule: args.languageModule
33000
+ languageModule: args.languageModule,
33001
+ signal: args.config.signal
32911
33002
  });
32912
33003
  for await (const event of fixGenerator) {
32913
33004
  if (event.type === "tool_execution_start")
@@ -32935,6 +33026,7 @@ var init_chairmanFixLoop = __esm({
32935
33026
  init_microGate();
32936
33027
  init_chairmanDelivery();
32937
33028
  init_retryTurn();
33029
+ init_cancel();
32938
33030
  }
32939
33031
  });
32940
33032
 
@@ -32976,6 +33068,17 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
32976
33068
  model: config2.model,
32977
33069
  provider: config2.provider ?? "minimax"
32978
33070
  };
33071
+ if (isCouncilCancelled(config2.signal)) {
33072
+ yield {
33073
+ type: "agent_end",
33074
+ id: crypto.randomUUID(),
33075
+ ts: Date.now(),
33076
+ sessionId: sessionId2,
33077
+ reason: "cancelled",
33078
+ durationMs: 0
33079
+ };
33080
+ return;
33081
+ }
32979
33082
  const emitMemberCost = (input) => {
32980
33083
  const usage = input.usage;
32981
33084
  const prompt = usage?.promptTokens ?? 0;
@@ -33005,6 +33108,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33005
33108
  const oracle = agents.find((a) => a.id === "minos") ?? (config2.skipSpecialists ? getAgent("minos") : void 0);
33006
33109
  const chairman = agents.find((a) => a.id === "lucifer") ?? (config2.skipSpecialists ? getAgent("lucifer") : void 0);
33007
33110
  for (const agent of specialists) {
33111
+ if (isCouncilCancelled(config2.signal))
33112
+ break;
33008
33113
  if (completedIds.has(agent.id))
33009
33114
  continue;
33010
33115
  callbacks.onAgentStart?.(agent);
@@ -33050,7 +33155,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33050
33155
  const emittedToolNames = [];
33051
33156
  const memberStart = Date.now();
33052
33157
  try {
33053
- for await (const event of harness.run()) {
33158
+ for await (const event of runHarnessWithAbort(harness, config2.signal)) {
33054
33159
  yield event;
33055
33160
  if (event.type === "tool_execution_start") {
33056
33161
  toolCalls += 1;
@@ -33072,7 +33177,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33072
33177
  fullText = `Error: ${err instanceof Error ? err.message : "Unknown"}`;
33073
33178
  errored = true;
33074
33179
  }
33075
- if (isDesignPhase && !errored && !NON_RETRY_AGENTS.has(agent.id)) {
33180
+ if (isDesignPhase && !errored && !NON_RETRY_AGENTS.has(agent.id) && !isCouncilCancelled(config2.signal)) {
33076
33181
  const specialistCheck = enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
33077
33182
  yield* applyRetryIfMissing({
33078
33183
  agent,
@@ -33155,7 +33260,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33155
33260
  }
33156
33261
  }
33157
33262
  }
33158
- if (oracle && !completedIds.has(oracle.id)) {
33263
+ if (oracle && !completedIds.has(oracle.id) && !isCouncilCancelled(config2.signal)) {
33159
33264
  callbacks.onAgentStart?.(oracle);
33160
33265
  const override = config2.agentModels?.[oracle.id];
33161
33266
  const effectiveProvider = override?.providerId ?? config2.provider ?? "minimax";
@@ -33204,7 +33309,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33204
33309
  const emittedToolNames = [];
33205
33310
  const memberStart = Date.now();
33206
33311
  try {
33207
- for await (const event of harness.run()) {
33312
+ for await (const event of runHarnessWithAbort(harness, config2.signal)) {
33208
33313
  yield event;
33209
33314
  if (event.type === "tool_execution_start") {
33210
33315
  toolCalls += 1;
@@ -33226,7 +33331,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33226
33331
  fullText = `Review error: ${err instanceof Error ? err.message : "Unknown"}`;
33227
33332
  errored = true;
33228
33333
  }
33229
- if (isDesignPhase && !errored) {
33334
+ if (isDesignPhase && !errored && !isCouncilCancelled(config2.signal)) {
33230
33335
  const oracleCheck = enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
33231
33336
  yield* applyRetryIfMissing({
33232
33337
  agent: oracle,
@@ -33286,7 +33391,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33286
33391
  });
33287
33392
  agentOutputs.push({ name: oracle.name, role: oracle.role, content: cleaned });
33288
33393
  }
33289
- if (chairman && !completedIds.has(chairman.id)) {
33394
+ if (chairman && !completedIds.has(chairman.id) && !isCouncilCancelled(config2.signal)) {
33290
33395
  callbacks.onSynthesisStart?.();
33291
33396
  callbacks.onAgentStart?.(chairman);
33292
33397
  const override = config2.agentModels?.[chairman.id];
@@ -33333,7 +33438,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33333
33438
  let chairmanProjectRoot = parseProjectRootFromWorkspaceContext(config2.workspaceContext ?? "");
33334
33439
  const memberStart = Date.now();
33335
33440
  try {
33336
- for await (const event of chairmanHarness.run()) {
33441
+ for await (const event of runHarnessWithAbort(chairmanHarness, config2.signal)) {
33337
33442
  yield event;
33338
33443
  if (event.type === "tool_execution_start") {
33339
33444
  toolCalls += 1;
@@ -33397,7 +33502,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33397
33502
  errored = true;
33398
33503
  lastErrorMessage = err instanceof Error ? err.message : String(err);
33399
33504
  }
33400
- if (isDesignPhase && !errored) {
33505
+ if (isDesignPhase && !errored && !isCouncilCancelled(config2.signal)) {
33401
33506
  const chairmanCheck = enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
33402
33507
  yield* applyRetryIfMissing({
33403
33508
  agent: chairman,
@@ -33418,7 +33523,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33418
33523
  });
33419
33524
  } else if (isDesignPhase) {
33420
33525
  enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
33421
- } else if (!errored) {
33526
+ } else if (!errored && !isCouncilCancelled(config2.signal)) {
33422
33527
  if (chairmanProjectRoot) {
33423
33528
  const zelariRoot = `${chairmanProjectRoot}/.zelari`;
33424
33529
  const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
@@ -33547,7 +33652,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
33547
33652
  id: crypto.randomUUID(),
33548
33653
  ts: Date.now(),
33549
33654
  sessionId: sessionId2,
33550
- reason: "completed",
33655
+ reason: isCouncilCancelled(config2.signal) ? "cancelled" : "completed",
33551
33656
  durationMs: 0
33552
33657
  };
33553
33658
  }
@@ -33566,6 +33671,7 @@ var init_councilApi = __esm({
33566
33671
  init_runChecks();
33567
33672
  init_implementationDelivery();
33568
33673
  init_types7();
33674
+ init_cancel();
33569
33675
  init_types7();
33570
33676
  init_outputCleaning();
33571
33677
  init_outputCleaning();
@@ -33947,9 +34053,9 @@ var init_types9 = __esm({
33947
34053
  import { readFileSync as readFileSync14 } from "node:fs";
33948
34054
  import { join as join11 } from "node:path";
33949
34055
  function readLessonsDeduped(zelariRoot) {
33950
- const path99 = join11(zelariRoot, LESSONS_FILE);
34056
+ const path100 = join11(zelariRoot, LESSONS_FILE);
33951
34057
  try {
33952
- const raw = readFileSync14(path99, "utf8");
34058
+ const raw = readFileSync14(path100, "utf8");
33953
34059
  const byId = /* @__PURE__ */ new Map();
33954
34060
  for (const line of raw.split(/\r?\n/)) {
33955
34061
  if (!line.trim())
@@ -34050,8 +34156,8 @@ function keywordsFrom(check2, signature) {
34050
34156
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
34051
34157
  }
34052
34158
  function writeLesson(zelariRoot, lesson) {
34053
- const path99 = join12(zelariRoot, LESSONS_FILE);
34054
- appendFileSync(path99, `${JSON.stringify(lesson)}
34159
+ const path100 = join12(zelariRoot, LESSONS_FILE);
34160
+ appendFileSync(path100, `${JSON.stringify(lesson)}
34055
34161
  `, "utf8");
34056
34162
  }
34057
34163
  function findSimilar(lessons, signature) {
@@ -34572,6 +34678,7 @@ __export(council_exports, {
34572
34678
  shouldRetryMember: () => shouldRetryMember,
34573
34679
  slugify: () => slugify2,
34574
34680
  stripClarificationProtocol: () => stripClarificationProtocol,
34681
+ stripQuestionBlocks: () => stripQuestionBlocks,
34575
34682
  swapMembers: () => swapMembers,
34576
34683
  systemMessagesFromSplit: () => systemMessagesFromSplit,
34577
34684
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
@@ -36017,9 +36124,9 @@ function findCycle(nodes) {
36017
36124
  if (color.get(start) !== WHITE)
36018
36125
  continue;
36019
36126
  const stack = [[start, 0]];
36020
- const path99 = [];
36127
+ const path100 = [];
36021
36128
  color.set(start, GRAY);
36022
- path99.push(start);
36129
+ path100.push(start);
36023
36130
  while (stack.length > 0) {
36024
36131
  const top = stack[stack.length - 1];
36025
36132
  const [id3, idx] = top;
@@ -36032,17 +36139,17 @@ function findCycle(nodes) {
36032
36139
  continue;
36033
36140
  const c = color.get(dep);
36034
36141
  if (c === GRAY) {
36035
- const at = path99.indexOf(dep);
36036
- return [...path99.slice(at), dep];
36142
+ const at = path100.indexOf(dep);
36143
+ return [...path100.slice(at), dep];
36037
36144
  }
36038
36145
  if (c === WHITE) {
36039
36146
  color.set(dep, GRAY);
36040
- path99.push(dep);
36147
+ path100.push(dep);
36041
36148
  stack.push([dep, 0]);
36042
36149
  }
36043
36150
  } else {
36044
36151
  color.set(id3, BLACK);
36045
- path99.pop();
36152
+ path100.pop();
36046
36153
  stack.pop();
36047
36154
  }
36048
36155
  }
@@ -36962,8 +37069,8 @@ var init_runner = __esm({
36962
37069
  failed: [...this.tentaclesById.values()].filter((r) => r.status === "error"),
36963
37070
  pending: []
36964
37071
  };
36965
- const path99 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
36966
- this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path99}`);
37072
+ const path100 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
37073
+ this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path100}`);
36967
37074
  return snapshot;
36968
37075
  }
36969
37076
  callLog(msg, data) {
@@ -38114,7 +38221,7 @@ var CORE_VERSION;
38114
38221
  var init_version = __esm({
38115
38222
  "packages/core/dist/version.js"() {
38116
38223
  "use strict";
38117
- CORE_VERSION = "2.34.0";
38224
+ CORE_VERSION = "2.35.0";
38118
38225
  }
38119
38226
  });
38120
38227
 
@@ -38626,6 +38733,7 @@ __export(dist_exports, {
38626
38733
  strictBuildGate: () => strictBuildGate,
38627
38734
  stripAnsi: () => stripAnsi,
38628
38735
  stripClarificationProtocol: () => stripClarificationProtocol,
38736
+ stripQuestionBlocks: () => stripQuestionBlocks,
38629
38737
  swapMembers: () => swapMembers,
38630
38738
  systemMessagesFromSplit: () => systemMessagesFromSplit,
38631
38739
  taskMatchesNfrKeywords: () => taskMatchesNfrKeywords,
@@ -40571,9 +40679,9 @@ function spillToolOutput(fullText, meta3) {
40571
40679
  const rnd = randomBytes3(3).toString("hex");
40572
40680
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
40573
40681
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
40574
- const path99 = join14(dir, file2);
40575
- writeFileSync12(path99, fullText, "utf8");
40576
- return path99;
40682
+ const path100 = join14(dir, file2);
40683
+ writeFileSync12(path100, fullText, "utf8");
40684
+ return path100;
40577
40685
  } catch {
40578
40686
  return null;
40579
40687
  }
@@ -40619,10 +40727,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
40619
40727
  ${tail2}`;
40620
40728
  }
40621
40729
  if (doSpill) {
40622
- const path99 = spillToolOutput(text, { toolName: opts.toolName });
40623
- if (path99) {
40730
+ const path100 = spillToolOutput(text, { toolName: opts.toolName });
40731
+ if (path100) {
40624
40732
  const spillNote = `
40625
- \u2026 [full output spilled to: ${path99} \u2014 re-read with read_file if you need the complete text] \u2026`;
40733
+ \u2026 [full output spilled to: ${path100} \u2014 re-read with read_file if you need the complete text] \u2026`;
40626
40734
  if (preview.includes("] \u2026\n")) {
40627
40735
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
40628
40736
  `);
@@ -41393,28 +41501,28 @@ var init_storage = __esm({
41393
41501
  VALID_SCALARS = /^(true|false|null|~)$/i;
41394
41502
  Storage = class {
41395
41503
  /** Read a Markdown file with frontmatter. Throws if not found. */
41396
- read(path99) {
41397
- if (!existsSync19(path99)) {
41398
- throw new Error(`File not found: ${path99}`);
41504
+ read(path100) {
41505
+ if (!existsSync19(path100)) {
41506
+ throw new Error(`File not found: ${path100}`);
41399
41507
  }
41400
- const md = readFileSync17(path99, "utf8");
41508
+ const md = readFileSync17(path100, "utf8");
41401
41509
  return parseFrontmatter(md);
41402
41510
  }
41403
41511
  /** Read a Markdown file; returns null if not found. */
41404
- readIfExists(path99) {
41405
- if (!existsSync19(path99)) return null;
41406
- return this.read(path99);
41512
+ readIfExists(path100) {
41513
+ if (!existsSync19(path100)) return null;
41514
+ return this.read(path100);
41407
41515
  }
41408
41516
  /**
41409
41517
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
41410
41518
  * The meta object is serialized as YAML frontmatter; body as Markdown.
41411
41519
  */
41412
- write(path99, meta3, body) {
41413
- mkdirSync10(dirname2(path99), { recursive: true });
41414
- const tmp = path99 + ".tmp-" + process.pid;
41520
+ write(path100, meta3, body) {
41521
+ mkdirSync10(dirname2(path100), { recursive: true });
41522
+ const tmp = path100 + ".tmp-" + process.pid;
41415
41523
  const md = serializeFrontmatter(meta3, body);
41416
41524
  writeFileSync14(tmp, md, "utf8");
41417
- renameSync2(tmp, path99);
41525
+ renameSync2(tmp, path100);
41418
41526
  }
41419
41527
  /** List all .md files in a directory (non-recursive). */
41420
41528
  listMarkdown(dir) {
@@ -41489,8 +41597,8 @@ function nextPlanTaskId(store6) {
41489
41597
  return `t${store6.counter}`;
41490
41598
  }
41491
41599
  function writePlanTaskArtifact(rootDir, task) {
41492
- const path99 = join17(rootDir, "plan-tasks", `${task.id}.md`);
41493
- mkdirSync11(dirname3(path99), { recursive: true });
41600
+ const path100 = join17(rootDir, "plan-tasks", `${task.id}.md`);
41601
+ mkdirSync11(dirname3(path100), { recursive: true });
41494
41602
  const meta3 = {
41495
41603
  kind: "task",
41496
41604
  id: task.id,
@@ -41511,7 +41619,7 @@ function writePlanTaskArtifact(rootDir, task) {
41511
41619
  task.notes?.trim() ? task.notes.trim() : "_(no notes)_",
41512
41620
  ""
41513
41621
  ].filter((l) => l !== null).join("\n");
41514
- new Storage().write(path99, meta3, body);
41622
+ new Storage().write(path100, meta3, body);
41515
41623
  }
41516
41624
  function loadHandle(rootDir) {
41517
41625
  const jsonPath = join17(rootDir, "plan.json");
@@ -44116,6 +44224,7 @@ __export(krakenModel_exports, {
44116
44224
  inferModelFamily: () => inferModelFamily,
44117
44225
  isCheapModelId: () => isCheapModelId,
44118
44226
  isKrakenAutoModelEnabled: () => isKrakenAutoModelEnabled,
44227
+ isUnknownModelError: () => isUnknownModelError,
44119
44228
  parseQualifiedModelRef: () => parseQualifiedModelRef,
44120
44229
  pickCheapModel: () => pickCheapModel,
44121
44230
  pickDifferentFamily: () => pickDifferentFamily,
@@ -44227,6 +44336,12 @@ function resolveKrakenSubModel(agent, parentModel, env = process.env, opts = {})
44227
44336
  }
44228
44337
  return parentModel;
44229
44338
  }
44339
+ function isUnknownModelError(message) {
44340
+ if (!message) return false;
44341
+ const m = message.toLowerCase();
44342
+ if (!/model/.test(m)) return false;
44343
+ 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);
44344
+ }
44230
44345
  function resolveKrakenPlannerModel(parentModel, env = process.env) {
44231
44346
  const specific = env.ZELARI_KRAKEN_PLANNER_MODEL?.trim();
44232
44347
  if (specific) return specific;
@@ -46026,6 +46141,12 @@ ${failed.error ?? "unknown error"}`,
46026
46141
  // src/cli/tools/taskTool.ts
46027
46142
  import { existsSync as existsSync26 } from "node:fs";
46028
46143
  import { randomUUID as randomUUID4 } from "node:crypto";
46144
+ function permissionsForTaskAgent(agent) {
46145
+ const kind2 = agent ?? "explore";
46146
+ if (kind2 === "general") return ["read", "write", "execute", "network"];
46147
+ if (kind2 === "verify") return ["read", "execute", "network"];
46148
+ return ["read"];
46149
+ }
46029
46150
  function resetTaskSpawnCount() {
46030
46151
  const g = globalThis;
46031
46152
  g.__zelariTaskSpawnCount = 0;
@@ -46507,17 +46628,45 @@ ${taskUserContent}`,
46507
46628
  };
46508
46629
  }
46509
46630
  const startedTools = /* @__PURE__ */ new Map();
46510
- const { result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46631
+ const onHarnessEvent = (ev) => {
46632
+ if (ev.type === "tool_execution_start") {
46633
+ startedTools.set(ev.toolCallId, ev.toolName);
46634
+ emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: ev.toolName, status: "started", ...ev.args ? { summary: toolCommandHint(ev.args) } : {}, ts: Date.now() });
46635
+ } else if (ev.type === "tool_execution_end") {
46636
+ 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() });
46637
+ }
46638
+ };
46639
+ let { result, error: error51, aborted: aborted2, usage, toolTrace } = await runSubAgent(harness, {
46511
46640
  ...opts.signal ? { signal: opts.signal } : {},
46512
- onEvent: (ev) => {
46513
- if (ev.type === "tool_execution_start") {
46514
- startedTools.set(ev.toolCallId, ev.toolName);
46515
- emitActivity({ type: "agent_tool", agentId: liveId, toolCallId: ev.toolCallId, tool: ev.toolName, status: "started", ...ev.args ? { summary: toolCommandHint(ev.args) } : {}, ts: Date.now() });
46516
- } else if (ev.type === "tool_execution_end") {
46517
- 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() });
46641
+ onEvent: onHarnessEvent
46642
+ });
46643
+ if (!aborted2 && !result && sub.fallback && sub.fallback.model !== sub.model) {
46644
+ const { isUnknownModelError: isUnknownModelError2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
46645
+ if (isUnknownModelError2(error51)) {
46646
+ emitPhase(`model ${sub.model} unavailable \u2014 retrying with ${sub.fallback.model}`);
46647
+ const retryConfig = {
46648
+ ...config2,
46649
+ model: sub.fallback.model,
46650
+ provider: sub.fallback.provider,
46651
+ providerStream: sub.fallback.providerStream
46652
+ };
46653
+ try {
46654
+ harness = deps.harnessFactory ? deps.harnessFactory(retryConfig) : new (await Promise.resolve().then(() => (init_harness(), harness_exports))).AgentHarness(retryConfig);
46655
+ const retry = await runSubAgent(harness, {
46656
+ ...opts.signal ? { signal: opts.signal } : {},
46657
+ onEvent: onHarnessEvent
46658
+ });
46659
+ result = retry.result;
46660
+ error51 = retry.error;
46661
+ aborted2 = retry.aborted;
46662
+ usage = retry.usage;
46663
+ toolTrace = retry.toolTrace;
46664
+ sub = { ...sub, model: sub.fallback.model, provider: sub.fallback.provider };
46665
+ } catch (err) {
46666
+ error51 = err instanceof Error ? err.message : String(err);
46518
46667
  }
46519
46668
  }
46520
- });
46669
+ }
46521
46670
  const durationMs = Date.now() - started;
46522
46671
  if (aborted2) {
46523
46672
  if (worktree && !shouldKeepWorktree()) await cleanupKrakenWorktree(worktree);
@@ -50672,12 +50821,9 @@ function backoffDelay(attempt, retryAfterHeader) {
50672
50821
  }
50673
50822
  return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS);
50674
50823
  }
50675
- function modelSupportsVision(model) {
50824
+ function modelSupportsVision(_model) {
50676
50825
  const force = process.env.ZELARI_VISION;
50677
- if (force === "1" || force === "true" || force === "on") return true;
50678
- if (force === "0" || force === "false" || force === "off") return false;
50679
- const m = model.toLowerCase();
50680
- return VISION_MODEL_HINTS.some((hint) => m.includes(hint));
50826
+ return !(force === "0" || force === "false" || force === "off");
50681
50827
  }
50682
50828
  function dataUriFromImage(img) {
50683
50829
  return `data:${img.mime};base64,${img.dataBase64}`;
@@ -50772,6 +50918,22 @@ ${notes}
50772
50918
  }
50773
50919
  return { role: m.role, content: m.content };
50774
50920
  }
50921
+ function imagesFollowUpMessage(images) {
50922
+ const labels = images.map((img) => img.alt ?? img.mime).join(", ");
50923
+ return {
50924
+ role: "user",
50925
+ content: [
50926
+ {
50927
+ type: "text",
50928
+ text: `[Immagine(i) dal tool: ${labels} \u2014 usa questi pixel per l'analisi visiva.]`
50929
+ },
50930
+ ...images.map((img) => ({
50931
+ type: "image_url",
50932
+ image_url: { url: dataUriFromImage(img) }
50933
+ }))
50934
+ ]
50935
+ };
50936
+ }
50775
50937
  function positiveEnvInt(name) {
50776
50938
  const raw = process.env[name];
50777
50939
  if (!raw) return void 0;
@@ -50782,15 +50944,27 @@ function openaiCompatibleProvider(config2) {
50782
50944
  return async function* (params) {
50783
50945
  const capabilities = capabilitiesFor(params.model, config2.providerId);
50784
50946
  const vision = modelSupportsVision(params.model);
50785
- const messages = params.messages.map((m) => {
50947
+ const msgsIn = params.messages;
50948
+ let toolRunImages = [];
50949
+ const messages = msgsIn.flatMap((m, i) => {
50786
50950
  const cacheable = !(m.role === "user" && m.images && m.images.length > 0);
50787
50951
  if (cacheable) {
50788
50952
  const cached2 = messageMappingCache.get(m);
50789
- if (cached2) return cached2;
50953
+ if (cached2) return [cached2];
50790
50954
  }
50791
50955
  const mapped = mapAgentMessage(m, vision);
50792
50956
  if (cacheable) messageMappingCache.set(m, mapped);
50793
- return mapped;
50957
+ if (m.role === "tool") {
50958
+ if (m.images && m.images.length > 0) toolRunImages.push(...m.images);
50959
+ const next = msgsIn[i + 1];
50960
+ const runEnds = !next || next.role !== "tool";
50961
+ if (runEnds && vision && toolRunImages.length > 0) {
50962
+ const followUp = imagesFollowUpMessage(toolRunImages);
50963
+ toolRunImages = [];
50964
+ return [mapped, followUp];
50965
+ }
50966
+ }
50967
+ return [mapped];
50794
50968
  });
50795
50969
  const generation = params.generation;
50796
50970
  const body = {
@@ -51114,7 +51288,7 @@ async function providerConfigFor(providerId) {
51114
51288
  ...extraFromStored(providerId)
51115
51289
  };
51116
51290
  }
51117
- var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_STREAM_MAX_MS, VISION_MODEL_HINTS, PROVIDER_ENDPOINTS, messageMappingCache;
51291
+ var RETRYABLE_STATUSES, MAX_RETRIES, BACKOFF_BASE_MS, BACKOFF_CAP_MS, PROVIDER_CONNECT_TIMEOUT_MS, PROVIDER_STREAM_IDLE_MS, PROVIDER_STREAM_MAX_MS, PROVIDER_ENDPOINTS, messageMappingCache;
51118
51292
  var init_openai_compatible = __esm({
51119
51293
  "src/cli/provider/openai-compatible.ts"() {
51120
51294
  "use strict";
@@ -51145,33 +51319,6 @@ var init_openai_compatible = __esm({
51145
51319
  const n = raw ? Number.parseInt(raw, 10) : 18e5;
51146
51320
  return Number.isFinite(n) && n >= 6e4 ? n : 18e5;
51147
51321
  })();
51148
- VISION_MODEL_HINTS = [
51149
- "grok-4",
51150
- "grok-3",
51151
- "grok-2-vision",
51152
- "grok-vision",
51153
- "glm-4v",
51154
- "glm-4.5v",
51155
- "glm-4.1v",
51156
- "glm-5v",
51157
- "qwen-vl",
51158
- "qwen2-vl",
51159
- "qwen2.5-vl",
51160
- "qwen3-vl",
51161
- "gpt-4o",
51162
- "gpt-4.1",
51163
- "gpt-4.5",
51164
- "gpt-4-vision",
51165
- "gpt-5",
51166
- "claude-3",
51167
- "claude-4",
51168
- "gemini-",
51169
- "gemini/",
51170
- "minimax-m1",
51171
- "minimax-m2",
51172
- "minimax-m3",
51173
- "deepseek-vl"
51174
- ];
51175
51322
  PROVIDER_ENDPOINTS = {
51176
51323
  "openai-compatible": "https://api.x.ai/v1",
51177
51324
  "minimax": "https://api.minimax.io/v1",
@@ -51566,6 +51713,16 @@ var init_driver = __esm({
51566
51713
  // src/cli/browser/tools.ts
51567
51714
  import path56 from "node:path";
51568
51715
  import os3 from "node:os";
51716
+ import { readFile as readFile7 } from "node:fs/promises";
51717
+ async function loadImageBlock(filePath) {
51718
+ try {
51719
+ const buf = await readFile7(filePath);
51720
+ if (buf.byteLength > SCREENSHOT_MAX_BYTES) return void 0;
51721
+ return { mime: "image/png", dataBase64: buf.toString("base64"), alt: path56.basename(filePath) };
51722
+ } catch {
51723
+ return void 0;
51724
+ }
51725
+ }
51569
51726
  function createBrowserTool(deps = {}) {
51570
51727
  return {
51571
51728
  name: "browser_check",
@@ -51618,17 +51775,23 @@ function createBrowserTool(deps = {}) {
51618
51775
  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.",
51619
51776
  smokeStrength: "weak"
51620
51777
  } : { smokeStrength: "asserted" }
51621
- });
51778
+ }, void 0, await toolImages(result.screenshotPath));
51622
51779
  }
51623
51780
  };
51624
51781
  }
51625
- var ActionSchema;
51782
+ async function toolImages(screenshotPath) {
51783
+ if (!screenshotPath) return void 0;
51784
+ const image = await loadImageBlock(screenshotPath);
51785
+ return image ? [image] : void 0;
51786
+ }
51787
+ var SCREENSHOT_MAX_BYTES, ActionSchema;
51626
51788
  var init_tools5 = __esm({
51627
51789
  "src/cli/browser/tools.ts"() {
51628
51790
  "use strict";
51629
51791
  init_zod();
51630
51792
  init_toolTypes();
51631
51793
  init_driver();
51794
+ SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
51632
51795
  ActionSchema = external_exports.discriminatedUnion("type", [
51633
51796
  external_exports.object({ type: external_exports.literal("click"), selector: external_exports.string().min(1) }),
51634
51797
  external_exports.object({ type: external_exports.literal("fill"), selector: external_exports.string().min(1), value: external_exports.string() }),
@@ -51653,6 +51816,92 @@ var init_tools5 = __esm({
51653
51816
  }
51654
51817
  });
51655
51818
 
51819
+ // src/cli/tools/screenshotTool.ts
51820
+ import { execFile as execFile5 } from "node:child_process";
51821
+ import { mkdir as mkdir3, readFile as readFile8, stat as stat7 } from "node:fs/promises";
51822
+ import path57 from "node:path";
51823
+ import { promisify as promisify4 } from "node:util";
51824
+ async function captureWindows(target) {
51825
+ const script = `Add-Type -AssemblyName System.Windows.Forms,System.Drawing;$b=[System.Windows.Forms.SystemInformation]::VirtualScreen;$bmp=New-Object System.Drawing.Bitmap $b.Width,$b.Height;$g=[System.Drawing.Graphics]::FromImage($bmp);$g.CopyFromScreen($b.Left,$b.Top,0,0,$bmp.Size);$g.Dispose(); $bmp.Save('${target.replace(/'/g, "''")}'); $bmp.Dispose();`;
51826
+ await execFileAsync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
51827
+ timeout: 12e3,
51828
+ windowsHide: true
51829
+ });
51830
+ }
51831
+ async function captureUnixLike(target) {
51832
+ const attempts = process.platform === "darwin" ? [["screencapture", ["-x", target]]] : [
51833
+ ["gnome-screenshot", ["-f", target]],
51834
+ ["scrot", [target]],
51835
+ ["import", ["-window", "root", target]]
51836
+ ];
51837
+ let lastErr = null;
51838
+ for (const [bin, args] of attempts) {
51839
+ try {
51840
+ await execFileAsync4(bin, args, { timeout: 12e3 });
51841
+ return;
51842
+ } catch (e) {
51843
+ lastErr = e;
51844
+ }
51845
+ }
51846
+ throw lastErr ?? new Error("no screen-capture utility available");
51847
+ }
51848
+ function createScreenshotTool(deps = {}) {
51849
+ return {
51850
+ name: "screenshot",
51851
+ description: "Capture a screenshot of the user screen(s) as PNG. Use it when the user asks to see/check something on screen (running app, game, dialog, error window) or when you need to LOOK at the current UI state to debug it. The image is returned to you as pixels (vision) and saved to disk; give the path back to the user so they can open it.",
51852
+ permissions: ["ui"],
51853
+ timeoutMs: 2e4,
51854
+ inputSchema: external_exports.object({
51855
+ note: external_exports.string().max(200).optional().describe("Why you are capturing (shown to the user with the permission prompt).")
51856
+ }),
51857
+ execute: async (args, ctx) => {
51858
+ const a = args;
51859
+ const dir = deps.outDir ?? path57.join(ctx.cwd ?? process.cwd(), ".zelari", "screenshots");
51860
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
51861
+ const target = path57.join(dir, `screenshot-${stamp}.png`);
51862
+ const capture = deps.capture ?? (process.platform === "win32" ? captureWindows : captureUnixLike);
51863
+ try {
51864
+ await mkdir3(dir, { recursive: true });
51865
+ await capture(target);
51866
+ const meta3 = await stat7(target);
51867
+ if (meta3.size === 0) return typedErr(`capture produced an empty file: ${target}`);
51868
+ const buf = await readFile8(target);
51869
+ const image = {
51870
+ mime: "image/png",
51871
+ dataBase64: buf.toString("base64"),
51872
+ alt: path57.basename(target)
51873
+ };
51874
+ const attachable = meta3.size <= SCREENSHOT_MAX_BYTES2;
51875
+ return typedOk(
51876
+ {
51877
+ ok: true,
51878
+ path: target,
51879
+ bytes: meta3.size,
51880
+ ...a.note ? { note: a.note } : {},
51881
+ ...attachable ? {} : { warning: "PNG over 8MB: pixels not attached to the model context; open the path instead." }
51882
+ },
51883
+ void 0,
51884
+ attachable ? [image] : void 0
51885
+ );
51886
+ } catch (e) {
51887
+ return typedErr(
51888
+ `screen capture failed on ${process.platform}: ${e instanceof Error ? e.message : String(e)}`
51889
+ );
51890
+ }
51891
+ }
51892
+ };
51893
+ }
51894
+ var execFileAsync4, SCREENSHOT_MAX_BYTES2;
51895
+ var init_screenshotTool = __esm({
51896
+ "src/cli/tools/screenshotTool.ts"() {
51897
+ "use strict";
51898
+ init_zod();
51899
+ init_toolTypes();
51900
+ execFileAsync4 = promisify4(execFile5);
51901
+ SCREENSHOT_MAX_BYTES2 = 8 * 1024 * 1024;
51902
+ }
51903
+ });
51904
+
51656
51905
  // src/cli/ssh/targets.ts
51657
51906
  var targets_exports = {};
51658
51907
  __export(targets_exports, {
@@ -51694,21 +51943,21 @@ function normalizeAuth(auth) {
51694
51943
  return "agent";
51695
51944
  }
51696
51945
  function readSecrets() {
51697
- const path99 = getSshSecretsPath();
51698
- if (!existsSync30(path99)) return {};
51946
+ const path100 = getSshSecretsPath();
51947
+ if (!existsSync30(path100)) return {};
51699
51948
  try {
51700
- return JSON.parse(readFileSync22(path99, "utf8"));
51949
+ return JSON.parse(readFileSync22(path100, "utf8"));
51701
51950
  } catch {
51702
51951
  return {};
51703
51952
  }
51704
51953
  }
51705
51954
  function writeSecrets(data) {
51706
- const path99 = getSshSecretsPath();
51707
- mkdirSync13(dirname4(path99), { recursive: true });
51708
- writeFileSync16(path99, `${JSON.stringify(data, null, 2)}
51955
+ const path100 = getSshSecretsPath();
51956
+ mkdirSync13(dirname4(path100), { recursive: true });
51957
+ writeFileSync16(path100, `${JSON.stringify(data, null, 2)}
51709
51958
  `, "utf8");
51710
51959
  try {
51711
- chmodSync(path99, 384);
51960
+ chmodSync(path100, 384);
51712
51961
  } catch {
51713
51962
  }
51714
51963
  }
@@ -51737,10 +51986,10 @@ function deleteSshPassword(id3) {
51737
51986
  writeSecrets({ passwords });
51738
51987
  }
51739
51988
  function readStore2() {
51740
- const path99 = getSshTargetsPath();
51741
- if (!existsSync30(path99)) return [];
51989
+ const path100 = getSshTargetsPath();
51990
+ if (!existsSync30(path100)) return [];
51742
51991
  try {
51743
- const parsed = JSON.parse(readFileSync22(path99, "utf8"));
51992
+ const parsed = JSON.parse(readFileSync22(path100, "utf8"));
51744
51993
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
51745
51994
  return list.filter(
51746
51995
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -51755,11 +52004,11 @@ function readStore2() {
51755
52004
  }
51756
52005
  }
51757
52006
  function writeStore2(targets) {
51758
- const path99 = getSshTargetsPath();
51759
- mkdirSync13(dirname4(path99), { recursive: true });
52007
+ const path100 = getSshTargetsPath();
52008
+ mkdirSync13(dirname4(path100), { recursive: true });
51760
52009
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
51761
52010
  writeFileSync16(
51762
- path99,
52011
+ path100,
51763
52012
  `${JSON.stringify({ targets: clean }, null, 2)}
51764
52013
  `,
51765
52014
  "utf8"
@@ -52005,11 +52254,11 @@ function formatSshTargetsForPrompt() {
52005
52254
  ];
52006
52255
  for (const t of targets) {
52007
52256
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
52008
- const path99 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
52257
+ const path100 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
52009
52258
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
52010
52259
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
52011
52260
  lines.push(
52012
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path99}${tags}${allow}`
52261
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path100}${tags}${allow}`
52013
52262
  );
52014
52263
  }
52015
52264
  return lines.join("\n");
@@ -52995,12 +53244,12 @@ __export(folderTrust_exports, {
52995
53244
  untrustFolder: () => untrustFolder
52996
53245
  });
52997
53246
  import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "node:fs";
52998
- import path57 from "node:path";
53247
+ import path58 from "node:path";
52999
53248
  function trustStorePath() {
53000
53249
  return _overrideStorePath ?? trustConfigPath();
53001
53250
  }
53002
53251
  function normalize6(p3) {
53003
- const resolved = path57.resolve(p3);
53252
+ const resolved = path58.resolve(p3);
53004
53253
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
53005
53254
  }
53006
53255
  function readStore3() {
@@ -53016,7 +53265,7 @@ function readStore3() {
53016
53265
  function writeStore3(store6) {
53017
53266
  const p3 = trustStorePath();
53018
53267
  try {
53019
- mkdirSync14(path57.dirname(p3), { recursive: true });
53268
+ mkdirSync14(path58.dirname(p3), { recursive: true });
53020
53269
  writeFileSync17(p3, JSON.stringify(store6, null, 2), "utf8");
53021
53270
  } catch (err) {
53022
53271
  throw new Error(
@@ -53042,7 +53291,7 @@ function isFolderTrusted(folderPath) {
53042
53291
  }
53043
53292
  function trustFolder(folderPath) {
53044
53293
  const store6 = readStore3();
53045
- const normalized = path57.resolve(folderPath);
53294
+ const normalized = path58.resolve(folderPath);
53046
53295
  if (!store6.folders.some((f) => normalize6(f.path) === normalize6(normalized))) {
53047
53296
  store6.folders.push({ path: normalized, trustedAt: (/* @__PURE__ */ new Date()).toISOString() });
53048
53297
  writeStore3(store6);
@@ -53168,7 +53417,7 @@ var init_lifecycleHooks = __esm({
53168
53417
 
53169
53418
  // src/cli/safety/astGate.ts
53170
53419
  import { promises as fs23 } from "node:fs";
53171
- import path58 from "node:path";
53420
+ import path59 from "node:path";
53172
53421
  function astGateEnabled() {
53173
53422
  return process.env.ZELARI_AST_GATE !== "0";
53174
53423
  }
@@ -53203,9 +53452,9 @@ function wrapWithAstGate(original, opts) {
53203
53452
  const containedPathOf = (args) => {
53204
53453
  const raw = args["path"];
53205
53454
  if (typeof raw !== "string" || raw.length === 0) return null;
53206
- const absPath = path58.isAbsolute(raw) ? raw : path58.join(opts.root, raw);
53207
- const rel2 = path58.relative(opts.root, absPath);
53208
- if (rel2.startsWith("..") || path58.isAbsolute(rel2)) return null;
53455
+ const absPath = path59.isAbsolute(raw) ? raw : path59.join(opts.root, raw);
53456
+ const rel2 = path59.relative(opts.root, absPath);
53457
+ if (rel2.startsWith("..") || path59.isAbsolute(rel2)) return null;
53209
53458
  return absPath;
53210
53459
  };
53211
53460
  return {
@@ -53230,7 +53479,7 @@ function wrapWithAstGate(original, opts) {
53230
53479
  if (!result.ok) return result;
53231
53480
  if (gateTarget !== null && !isAstSupported(gateTarget)) {
53232
53481
  process.stderr.write(
53233
- `[ast_gate] LOUD SKIP (unsupported-extension): ${original.name} ${gateTarget} \u2014 ${path58.extname(gateTarget) || "(no extension)"} is outside the AST surface; write KEPT, NOT syntax-gated.
53482
+ `[ast_gate] LOUD SKIP (unsupported-extension): ${original.name} ${gateTarget} \u2014 ${path59.extname(gateTarget) || "(no extension)"} is outside the AST surface; write KEPT, NOT syntax-gated.
53234
53483
  `
53235
53484
  );
53236
53485
  return result;
@@ -53247,7 +53496,7 @@ function wrapWithAstGate(original, opts) {
53247
53496
  return result;
53248
53497
  }
53249
53498
  const post = await fs23.readFile(absPath, "utf8");
53250
- const syntaxError = firstSyntaxError(ts, path58.basename(absPath), post);
53499
+ const syntaxError = firstSyntaxError(ts, path59.basename(absPath), post);
53251
53500
  if (!syntaxError) return result;
53252
53501
  const parseError = `${syntaxError.message} (line ${syntaxError.line}, col ${syntaxError.character})`;
53253
53502
  let revertedTo;
@@ -53258,7 +53507,7 @@ function wrapWithAstGate(original, opts) {
53258
53507
  await fs23.writeFile(absPath, preContent, "utf8");
53259
53508
  revertedTo = snapshotIdOf(preContent);
53260
53509
  }
53261
- const relLabel = path58.relative(opts.root, absPath) || path58.basename(absPath);
53510
+ const relLabel = path59.relative(opts.root, absPath) || path59.basename(absPath);
53262
53511
  return typedErr(
53263
53512
  `[ast_gate_reverted] ${original.name}: ${absPath} written but the file no longer parses \u2014 write REVERTED (revertedTo=${revertedTo}). ${parseError}. Fix the syntax and re-apply (read_file first for a fresh snapshotId).`,
53264
53513
  {
@@ -53683,7 +53932,7 @@ var init_resourceClaims = __esm({
53683
53932
  // src/cli/toolResultCache.ts
53684
53933
  import { createHash as createHash15 } from "node:crypto";
53685
53934
  import { promises as fs24 } from "node:fs";
53686
- import path59 from "node:path";
53935
+ import path60 from "node:path";
53687
53936
  function isToolCacheEnabled() {
53688
53937
  const raw = process.env.ZELARI_TOOL_CACHE;
53689
53938
  return raw !== "0" && raw !== "false" && raw !== "off";
@@ -53768,7 +54017,7 @@ async function statKey(toolName, input, ctx) {
53768
54017
  if (!input || typeof input !== "object") return null;
53769
54018
  const rawPath = input.path;
53770
54019
  if (typeof rawPath !== "string" || rawPath.length === 0) return null;
53771
- const abs = path59.isAbsolute(rawPath) ? rawPath : path59.join(ctx.cwd, rawPath);
54020
+ const abs = path60.isAbsolute(rawPath) ? rawPath : path60.join(ctx.cwd, rawPath);
53772
54021
  try {
53773
54022
  const st = await fs24.stat(abs);
53774
54023
  return hashKey({
@@ -53824,7 +54073,7 @@ __export(toolRegistry_exports, {
53824
54073
  wrapWithSandbox: () => wrapWithSandbox
53825
54074
  });
53826
54075
  import { existsSync as existsSync32 } from "node:fs";
53827
- import path60 from "node:path";
54076
+ import path61 from "node:path";
53828
54077
  function createBuiltinToolRegistry(options = {}) {
53829
54078
  const root = options.root ?? process.cwd();
53830
54079
  const audit = options.audit ?? new AuditLogger();
@@ -54049,6 +54298,15 @@ function createBuiltinToolRegistry(options = {}) {
54049
54298
  permissions: browserTool.permissions ?? []
54050
54299
  });
54051
54300
  }
54301
+ if (!readOnly && !gauntletParent && process.env.ZELARI_SCREENSHOT !== "0") {
54302
+ const screenshotTool = createScreenshotTool();
54303
+ registry4.register(screenshotTool);
54304
+ tools.push({
54305
+ name: screenshotTool.name,
54306
+ description: screenshotTool.description,
54307
+ permissions: screenshotTool.permissions ?? []
54308
+ });
54309
+ }
54052
54310
  if (!readOnly && !gauntletParent && process.env.ZELARI_SSH !== "0") {
54053
54311
  for (const t of createSshTools()) {
54054
54312
  registry4.register(t);
@@ -54082,6 +54340,7 @@ function createBuiltinToolRegistry(options = {}) {
54082
54340
  // registry's own policy (permPolicy above) — they can never
54083
54341
  // exceed it.
54084
54342
  parentPolicy: permPolicy,
54343
+ ...options.onPermissionAsk ? { onPermissionAsk: options.onPermissionAsk } : {},
54085
54344
  ...options.subAgentProvider ? { provider: options.subAgentProvider } : {},
54086
54345
  ...options.subAgentModel ? { model: options.subAgentModel } : {}
54087
54346
  }),
@@ -54181,12 +54440,13 @@ function taskAgentToProfile(agent) {
54181
54440
  return "explore";
54182
54441
  }
54183
54442
  function createKrakenSubAgentContextFactory(opts) {
54184
- const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride, parentPolicy } = opts;
54443
+ const { root, audit, sessionId: sessionId2, provider: providerOverride, model: modelOverride, parentPolicy, onPermissionAsk } = opts;
54185
54444
  return async ({ agent, cwd: subCwd }) => {
54186
54445
  const cfg = providerOverride ? await providerConfigFor(providerOverride) : await providerFromEnv();
54187
54446
  if (!cfg) return null;
54188
54447
  const { resolveKrakenSubModel: resolveKrakenSubModel2, parseQualifiedModelRef: parseQualifiedModelRef2 } = await Promise.resolve().then(() => (init_krakenModel(), krakenModel_exports));
54189
- const resolvedModel = resolveKrakenSubModel2(agent, modelOverride || cfg.model);
54448
+ const parentModel = modelOverride || cfg.model;
54449
+ const resolvedModel = resolveKrakenSubModel2(agent, parentModel);
54190
54450
  let effCfg = cfg;
54191
54451
  let model = resolvedModel;
54192
54452
  const ref = parseQualifiedModelRef2(resolvedModel);
@@ -54215,6 +54475,7 @@ function createKrakenSubAgentContextFactory(opts) {
54215
54475
  diagnostics: false,
54216
54476
  lspProvider: null,
54217
54477
  permissionPolicy: effectiveSubPolicy,
54478
+ ...onPermissionAsk ? { onPermissionAsk } : {},
54218
54479
  // P0.5: the tentacle's agent identity drives per-agent policy rules.
54219
54480
  policyAgent: agent
54220
54481
  });
@@ -54222,6 +54483,13 @@ function createKrakenSubAgentContextFactory(opts) {
54222
54483
  providerStream: buildProviderStream(subCfg),
54223
54484
  model,
54224
54485
  provider: subCfg.providerId,
54486
+ ...model !== parentModel ? {
54487
+ fallback: {
54488
+ model: parentModel,
54489
+ provider: cfg.providerId,
54490
+ providerStream: buildProviderStream({ ...cfg, model: parentModel })
54491
+ }
54492
+ } : {},
54225
54493
  registry: subRegistry,
54226
54494
  tools: subRegistry.toOpenAITools().map((t) => ({
54227
54495
  name: t.function.name,
@@ -54241,11 +54509,14 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54241
54509
  return {
54242
54510
  ...original,
54243
54511
  execute: async (input, ctx) => {
54244
- const decision = resolveToolPermission(original.name, required2, policy);
54512
+ const requiredNow = original.name === "task" ? permissionsForTaskAgent(
54513
+ input?.agent
54514
+ ) : required2;
54515
+ const decision = resolveToolPermission(original.name, requiredNow, policy);
54245
54516
  const rule = agentLayers ? matchAgentPolicyRuleLayered(
54246
54517
  agentLayers,
54247
54518
  precedence,
54248
- required2,
54519
+ requiredNow,
54249
54520
  input ?? {},
54250
54521
  root ?? process.cwd()
54251
54522
  ) : null;
@@ -54257,25 +54528,25 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54257
54528
  root ?? process.cwd()
54258
54529
  ) : void 0;
54259
54530
  const contractRule = matchContractCapabilityRule(
54260
- required2,
54531
+ requiredNow,
54261
54532
  input ?? {},
54262
54533
  root ?? process.cwd()
54263
54534
  );
54264
54535
  let action = intersectEffects(mergeRuleEffect(decision.action, rule), claims?.effect, contractRule?.effect);
54265
54536
  let actionReason = decision.reason;
54266
- if (action !== "deny" && (required2.includes("write") || required2.includes("execute"))) {
54537
+ if (action !== "deny" && (requiredNow.includes("write") || requiredNow.includes("execute"))) {
54267
54538
  const provHit = provenanceMatchIn(JSON.stringify(input ?? {}));
54268
- if (provHit && provenanceAppliesTo(provHit.source, required2)) {
54539
+ if (provHit && provenanceAppliesTo(provHit.source, requiredNow)) {
54269
54540
  const provNote = `[provenance] args embed non-user ${provHit.source} content (via ${provHit.tool})`;
54270
54541
  if (action === "allow") {
54271
54542
  action = "ask";
54272
- actionReason = `${provNote} \u2014 confirm before ${required2.join("+")}`;
54543
+ actionReason = `${provNote} \u2014 confirm before ${requiredNow.join("+")}`;
54273
54544
  } else {
54274
54545
  actionReason = `${decision.reason} \xB7 ${provNote}`;
54275
54546
  }
54276
54547
  }
54277
54548
  }
54278
- if (action === "allow" && required2.includes("execute") && activePermissionPreset() !== "yolo" && !isSessionGranted(original.name, required2)) {
54549
+ if (action === "allow" && requiredNow.includes("execute") && activePermissionPreset() !== "yolo" && !isSessionGranted(original.name, requiredNow)) {
54279
54550
  const destructiveHit = destructiveCommandHit(input ?? {});
54280
54551
  if (destructiveHit) {
54281
54552
  action = "ask";
@@ -54318,7 +54589,7 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
54318
54589
  }
54319
54590
  }
54320
54591
  const outcome = await original.execute(input, ctx);
54321
- recordResultForProvenance(original.name, required2, outcome);
54592
+ recordResultForProvenance(original.name, requiredNow, outcome);
54322
54593
  return outcome;
54323
54594
  }
54324
54595
  };
@@ -54420,14 +54691,14 @@ function wrapWithDiagnostics(original, root, runner) {
54420
54691
  function claimedSourcePath(token, args, root) {
54421
54692
  const cleaned = token.replace(/^["']|["']$/g, "");
54422
54693
  if (!cleaned || cleaned.startsWith("-")) return null;
54423
- if (!DIAG_SOURCE_EXTENSIONS.has(path60.extname(cleaned).toLowerCase())) return null;
54694
+ if (!DIAG_SOURCE_EXTENSIONS.has(path61.extname(cleaned).toLowerCase())) return null;
54424
54695
  const bases = [root];
54425
54696
  const cwd = args["cwd"];
54426
54697
  if (typeof cwd === "string" && cwd.length > 0) {
54427
- bases.unshift(path60.isAbsolute(cwd) ? cwd : path60.resolve(root, cwd));
54698
+ bases.unshift(path61.isAbsolute(cwd) ? cwd : path61.resolve(root, cwd));
54428
54699
  }
54429
54700
  for (const base2 of bases) {
54430
- const candidate = path60.isAbsolute(cleaned) ? path60.normalize(cleaned) : path60.resolve(base2, cleaned);
54701
+ const candidate = path61.isAbsolute(cleaned) ? path61.normalize(cleaned) : path61.resolve(base2, cleaned);
54431
54702
  try {
54432
54703
  const contained = resolveSandboxedPath(candidate, { root });
54433
54704
  if (existsSync32(contained)) return contained;
@@ -54697,6 +54968,7 @@ var init_toolRegistry = __esm({
54697
54968
  init_tools3();
54698
54969
  init_tools4();
54699
54970
  init_tools5();
54971
+ init_screenshotTool();
54700
54972
  init_tools6();
54701
54973
  init_worldModel();
54702
54974
  init_openai_compatible();
@@ -54743,7 +55015,7 @@ var init_toolRegistry = __esm({
54743
55015
 
54744
55016
  // src/cli/metrics.ts
54745
55017
  import { promises as fs25, existsSync as existsSync33, statSync as statSync4, renameSync as renameSync4, appendFileSync as appendFileSync3, mkdirSync as mkdirSync15 } from "node:fs";
54746
- import path61 from "node:path";
55018
+ import path62 from "node:path";
54747
55019
  async function readMetrics(file2) {
54748
55020
  let raw = "";
54749
55021
  try {
@@ -54793,7 +55065,7 @@ var init_metrics3 = __esm({
54793
55065
  writeQueue = Promise.resolve();
54794
55066
  constructor(file2) {
54795
55067
  this.file = file2 ?? metricsPath();
54796
- mkdirSync15(path61.dirname(this.file), { recursive: true });
55068
+ mkdirSync15(path62.dirname(this.file), { recursive: true });
54797
55069
  }
54798
55070
  /** Metrics file path — doctor/summary readers use this. */
54799
55071
  get filePath() {
@@ -54830,8 +55102,8 @@ var init_metrics3 = __esm({
54830
55102
  maybeRotate() {
54831
55103
  if (!existsSync33(this.file)) return;
54832
55104
  try {
54833
- const stat7 = statSync4(this.file);
54834
- if (stat7.size >= METRICS_ROTATE_BYTES) {
55105
+ const stat8 = statSync4(this.file);
55106
+ if (stat8.size >= METRICS_ROTATE_BYTES) {
54835
55107
  const rotated = this.file.replace(/\.jsonl$/, ".1.jsonl");
54836
55108
  renameSync4(this.file, rotated);
54837
55109
  }
@@ -55125,15 +55397,15 @@ __export(completionProofProbe_exports, {
55125
55397
  gatherGitAttestation: () => gatherGitAttestation,
55126
55398
  harnessManifest: () => harnessManifest
55127
55399
  });
55128
- import { execFile as execFile5 } from "node:child_process";
55129
- import { promisify as promisify4 } from "node:util";
55400
+ import { execFile as execFile6 } from "node:child_process";
55401
+ import { promisify as promisify5 } from "node:util";
55130
55402
  async function readHarnessVersion() {
55131
55403
  if (cachedHarnessVersion !== null) return cachedHarnessVersion;
55132
55404
  try {
55133
- const { readFile: readFile9 } = await import("node:fs/promises");
55405
+ const { readFile: readFile11 } = await import("node:fs/promises");
55134
55406
  const { fileURLToPath: fileURLToPath4 } = await import("node:url");
55135
55407
  const pkgPath = fileURLToPath4(new URL("../../../package.json", import.meta.url));
55136
- const parsed = JSON.parse(await readFile9(pkgPath, "utf8"));
55408
+ const parsed = JSON.parse(await readFile11(pkgPath, "utf8"));
55137
55409
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
55138
55410
  return cachedHarnessVersion = parsed.version;
55139
55411
  }
@@ -55157,7 +55429,7 @@ async function harnessManifest(env = process.env) {
55157
55429
  }
55158
55430
  async function git5(cwd, args) {
55159
55431
  try {
55160
- const { stdout } = await execFileAsync4("git", ["-C", cwd, ...args], {
55432
+ const { stdout } = await execFileAsync5("git", ["-C", cwd, ...args], {
55161
55433
  maxBuffer: 32 * 1024 * 1024,
55162
55434
  windowsHide: true
55163
55435
  });
@@ -55199,7 +55471,7 @@ function activeTaskContractSnapshot() {
55199
55471
  return void 0;
55200
55472
  }
55201
55473
  }
55202
- var HARNESS_VERSION_FALLBACK, ADAPTER_IDS, cachedHarnessVersion, execFileAsync4;
55474
+ var HARNESS_VERSION_FALLBACK, ADAPTER_IDS, cachedHarnessVersion, execFileAsync5;
55203
55475
  var init_completionProofProbe = __esm({
55204
55476
  "src/cli/kraken/completionProofProbe.ts"() {
55205
55477
  "use strict";
@@ -55207,7 +55479,7 @@ var init_completionProofProbe = __esm({
55207
55479
  HARNESS_VERSION_FALLBACK = "2.13.0";
55208
55480
  ADAPTER_IDS = ["node", "python", "rust", "go", "java", "dotnet"];
55209
55481
  cachedHarnessVersion = null;
55210
- execFileAsync4 = promisify4(execFile5);
55482
+ execFileAsync5 = promisify5(execFile6);
55211
55483
  }
55212
55484
  });
55213
55485
 
@@ -55311,7 +55583,7 @@ var init_completionProofAttestation = __esm({
55311
55583
  // src/cli/kraken/completionProofPersist.ts
55312
55584
  import { open, rename as rename2, rm as rm2 } from "node:fs/promises";
55313
55585
  import { randomBytes as randomBytes5 } from "node:crypto";
55314
- import path62 from "node:path";
55586
+ import path63 from "node:path";
55315
55587
  function isTruthyFlag2(v) {
55316
55588
  const n = v?.trim().toLowerCase();
55317
55589
  return n === "1" || n === "true" || n === "yes" || n === "on";
@@ -55353,8 +55625,8 @@ function isWindowsRenameBlock(err) {
55353
55625
  return code === "EPERM" || code === "ENOTEMPTY" || code === "EEXIST";
55354
55626
  }
55355
55627
  async function writeFileAtomic(target, data) {
55356
- const dir = path62.dirname(target);
55357
- const tmp = path62.join(dir, `.${path62.basename(target)}.${randomBytes5(6).toString("hex")}.tmp`);
55628
+ const dir = path63.dirname(target);
55629
+ const tmp = path63.join(dir, `.${path63.basename(target)}.${randomBytes5(6).toString("hex")}.tmp`);
55358
55630
  let fh = null;
55359
55631
  try {
55360
55632
  fh = await open(tmp, "w");
@@ -55399,8 +55671,8 @@ var init_completionProofPersist = __esm({
55399
55671
  });
55400
55672
 
55401
55673
  // src/cli/kraken/completionProof.ts
55402
- import { mkdir as mkdir3 } from "node:fs/promises";
55403
- import path63 from "node:path";
55674
+ import { mkdir as mkdir4 } from "node:fs/promises";
55675
+ import path64 from "node:path";
55404
55676
  function verdictOf(evaluation) {
55405
55677
  return evaluation.evaluation?.verdict ?? (evaluation.blocked ? "BLOCKED" : "PASS");
55406
55678
  }
@@ -55545,8 +55817,8 @@ async function writeCompletionProofDetailed(evaluation, options = {}) {
55545
55817
  const mode = options.persistenceMode ?? activeProofPersistenceMode();
55546
55818
  try {
55547
55819
  const baseDir = options.baseDir ?? process.cwd();
55548
- const dir = path63.join(baseDir, ".zelari");
55549
- await mkdir3(dir, { recursive: true });
55820
+ const dir = path64.join(baseDir, ".zelari");
55821
+ await mkdir4(dir, { recursive: true });
55550
55822
  const requested = options.attestation ?? {};
55551
55823
  const plan = requested.skipProbes || requested.verificationPlan !== void 0 ? void 0 : await defaultVerificationPlanSnapshot(baseDir);
55552
55824
  const wrapper = await buildAttestedWrapper(
@@ -55565,8 +55837,8 @@ async function writeCompletionProofDetailed(evaluation, options = {}) {
55565
55837
  baseDir
55566
55838
  );
55567
55839
  const rendered = renderCompletionProof(evaluation, options.meta ?? {}, wrapper.attestation);
55568
- const markdownPath = path63.join(dir, "completion-proof.md");
55569
- const jsonPath = path63.join(dir, "completion-proof.json");
55840
+ const markdownPath = path64.join(dir, "completion-proof.md");
55841
+ const jsonPath = path64.join(dir, "completion-proof.json");
55570
55842
  await writeFileAtomic(markdownPath, rendered.markdown);
55571
55843
  await writeFileAtomic(jsonPath, rendered.json);
55572
55844
  return { paths: { markdownPath, jsonPath }, mode, requiredBlockReason: null };
@@ -55654,15 +55926,34 @@ var init_spineTelemetry = __esm({
55654
55926
  }
55655
55927
  });
55656
55928
 
55929
+ // src/cli/hooks/askUserTimeout.ts
55930
+ function askUserTimeoutMs() {
55931
+ const raw = process.env.ZELARI_ASK_USER_TIMEOUT_MS?.trim();
55932
+ if (!raw) return 3e5;
55933
+ const n = Number.parseInt(raw, 10);
55934
+ if (!Number.isFinite(n) || n < 0) return 3e5;
55935
+ return n;
55936
+ }
55937
+ function armPickerTimeout(onFire, ms) {
55938
+ if (ms <= 0) return () => void 0;
55939
+ const id3 = setTimeout(onFire, ms);
55940
+ return () => clearTimeout(id3);
55941
+ }
55942
+ var init_askUserTimeout = __esm({
55943
+ "src/cli/hooks/askUserTimeout.ts"() {
55944
+ "use strict";
55945
+ }
55946
+ });
55947
+
55657
55948
  // src/cli/state/fileStateStore.ts
55658
55949
  import { createHash as createHash17, randomUUID as randomUUID5 } from "node:crypto";
55659
55950
  import { promises as fs26 } from "node:fs";
55660
- import * as path64 from "node:path";
55951
+ import * as path65 from "node:path";
55661
55952
  function shortId() {
55662
55953
  return randomUUID5().replace(/-/g, "").slice(0, 12);
55663
55954
  }
55664
55955
  async function writeJsonAtomic(filePath, data) {
55665
- await fs26.mkdir(path64.dirname(filePath), { recursive: true });
55956
+ await fs26.mkdir(path65.dirname(filePath), { recursive: true });
55666
55957
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
55667
55958
  await fs26.writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
55668
55959
  await fs26.rename(tmp, filePath);
@@ -55723,11 +56014,11 @@ var init_fileStateStore = __esm({
55723
56014
  indexPath = "";
55724
56015
  async init(projectRoot) {
55725
56016
  this.root = projectRoot;
55726
- this.stateDir = path64.join(projectRoot, ".zelari", "state");
55727
- this.commitsDir = path64.join(this.stateDir, "commits");
55728
- this.artifactsDir = path64.join(this.stateDir, "artifacts");
55729
- this.headPath = path64.join(this.stateDir, "HEAD.json");
55730
- this.indexPath = path64.join(this.stateDir, "index.jsonl");
56017
+ this.stateDir = path65.join(projectRoot, ".zelari", "state");
56018
+ this.commitsDir = path65.join(this.stateDir, "commits");
56019
+ this.artifactsDir = path65.join(this.stateDir, "artifacts");
56020
+ this.headPath = path65.join(this.stateDir, "HEAD.json");
56021
+ this.indexPath = path65.join(this.stateDir, "index.jsonl");
55731
56022
  await fs26.mkdir(this.commitsDir, { recursive: true });
55732
56023
  await fs26.mkdir(this.artifactsDir, { recursive: true });
55733
56024
  }
@@ -55740,13 +56031,13 @@ var init_fileStateStore = __esm({
55740
56031
  const discoveries = input.discoveries ?? [];
55741
56032
  const parent = await this.head();
55742
56033
  const id3 = shortId();
55743
- const artifactRel = path64.join("artifacts", id3);
55744
- const artifactAbs = path64.join(this.artifactsDir, id3);
56034
+ const artifactRel = path65.join("artifacts", id3);
56035
+ const artifactAbs = path65.join(this.artifactsDir, id3);
55745
56036
  await fs26.mkdir(artifactAbs, { recursive: true });
55746
56037
  const summary = defaultSummary(input, discoveries);
55747
- await fs26.writeFile(path64.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
55748
- await writeJsonAtomic(path64.join(artifactAbs, "discoveries.json"), discoveries);
55749
- await writeJsonAtomic(path64.join(artifactAbs, "verification.json"), input.verification);
56038
+ await fs26.writeFile(path65.join(artifactAbs, "summary.md"), summary + "\n", "utf8");
56039
+ await writeJsonAtomic(path65.join(artifactAbs, "discoveries.json"), discoveries);
56040
+ await writeJsonAtomic(path65.join(artifactAbs, "verification.json"), input.verification);
55750
56041
  const meta3 = {
55751
56042
  id: id3,
55752
56043
  parentId: parent?.id ?? null,
@@ -55758,14 +56049,14 @@ var init_fileStateStore = __esm({
55758
56049
  workspaceCheckpointId: input.workspaceCheckpointId,
55759
56050
  verification: {
55760
56051
  ...input.verification,
55761
- reportPath: input.verification.reportPath ?? path64.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
56052
+ reportPath: input.verification.reportPath ?? path65.join(".zelari", "state", artifactRel, "verification.json").replace(/\\/g, "/")
55762
56053
  },
55763
56054
  changedPaths: input.changedPaths ?? [],
55764
56055
  stablePromptHash: input.stablePromptHash,
55765
56056
  discoveryCount: discoveries.length,
55766
56057
  artifactDir: artifactRel.replace(/\\/g, "/")
55767
56058
  };
55768
- await writeJsonAtomic(path64.join(this.commitsDir, `${id3}.json`), meta3);
56059
+ await writeJsonAtomic(path65.join(this.commitsDir, `${id3}.json`), meta3);
55769
56060
  await writeJsonAtomic(this.headPath, { id: id3, updatedAt: meta3.createdAt });
55770
56061
  await fs26.appendFile(this.indexPath, JSON.stringify({ id: id3, createdAt: meta3.createdAt, label: meta3.label }) + "\n", "utf8");
55771
56062
  return stripStored(meta3);
@@ -55776,7 +56067,7 @@ var init_fileStateStore = __esm({
55776
56067
  return this.get(head.id);
55777
56068
  }
55778
56069
  async get(id3) {
55779
- const stored = await readJsonFile(path64.join(this.commitsDir, `${id3}.json`));
56070
+ const stored = await readJsonFile(path65.join(this.commitsDir, `${id3}.json`));
55780
56071
  return stored ? stripStored(stored) : null;
55781
56072
  }
55782
56073
  async list(limit = 20) {
@@ -55815,9 +56106,9 @@ var init_fileStateStore = __esm({
55815
56106
  async loadDiscoveries(id3) {
55816
56107
  const meta3 = id3 ? await this.get(id3) : await this.head();
55817
56108
  if (!meta3) return [];
55818
- const stored = await readJsonFile(path64.join(this.commitsDir, `${meta3.id}.json`));
56109
+ const stored = await readJsonFile(path65.join(this.commitsDir, `${meta3.id}.json`));
55819
56110
  if (!stored?.artifactDir) return [];
55820
- const discPath = path64.join(this.stateDir, stored.artifactDir, "discoveries.json");
56111
+ const discPath = path65.join(this.stateDir, stored.artifactDir, "discoveries.json");
55821
56112
  return await readJsonFile(discPath) ?? [];
55822
56113
  }
55823
56114
  async materializeContext(id3, maxChars = DEFAULT_MATERIALIZE_CHARS) {
@@ -56420,10 +56711,10 @@ var init_mode = __esm({
56420
56711
 
56421
56712
  // src/cli/headless.ts
56422
56713
  import { readFileSync as readFileSync24 } from "node:fs";
56423
- import path65 from "node:path";
56714
+ import path66 from "node:path";
56424
56715
  function resolveHeadlessCwd(opts) {
56425
56716
  const raw = typeof opts.cwd === "string" ? opts.cwd.trim() : "";
56426
- return path65.resolve(raw.length > 0 ? raw : process.cwd());
56717
+ return path66.resolve(raw.length > 0 ? raw : process.cwd());
56427
56718
  }
56428
56719
  function defaultProfileForMode(mode) {
56429
56720
  switch (mode) {
@@ -57525,7 +57816,7 @@ var init_planDetect = __esm({
57525
57816
  // src/cli/memory/legacyImport.ts
57526
57817
  import { createHash as createHash18 } from "node:crypto";
57527
57818
  import { promises as fs27 } from "node:fs";
57528
- import * as path66 from "node:path";
57819
+ import * as path67 from "node:path";
57529
57820
  function sourceId(fact, line) {
57530
57821
  return `jsonl:${fact.id ?? createHash18("sha256").update(line).digest("hex")}`;
57531
57822
  }
@@ -57543,7 +57834,7 @@ function timestamp(value) {
57543
57834
  }
57544
57835
  async function importLegacyMemoryLog(backend, service) {
57545
57836
  const result = { found: 0, imported: 0, skipped: 0, corrupt: 0 };
57546
- const logPath = path66.join(path66.dirname(backend.databasePath), "log.jsonl");
57837
+ const logPath = path67.join(path67.dirname(backend.databasePath), "log.jsonl");
57547
57838
  let raw;
57548
57839
  try {
57549
57840
  raw = await fs27.readFile(logPath, "utf8");
@@ -57726,7 +58017,7 @@ var init_sqliteCodec = __esm({
57726
58017
  // src/cli/memory/sqliteRpc.ts
57727
58018
  import { existsSync as existsSync36 } from "node:fs";
57728
58019
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
57729
- import * as path67 from "node:path";
58020
+ import * as path68 from "node:path";
57730
58021
  import { Worker } from "node:worker_threads";
57731
58022
  function isBusy(error51) {
57732
58023
  const candidate = error51;
@@ -57735,10 +58026,10 @@ function isBusy(error51) {
57735
58026
  );
57736
58027
  }
57737
58028
  function resolveWorkerUrl() {
57738
- const here = path67.dirname(fileURLToPath2(import.meta.url));
57739
- const direct = path67.join(here, "sqliteWorker.mjs");
58029
+ const here = path68.dirname(fileURLToPath2(import.meta.url));
58030
+ const direct = path68.join(here, "sqliteWorker.mjs");
57740
58031
  if (existsSync36(direct)) return pathToFileURL2(direct);
57741
- return pathToFileURL2(path67.join(here, "memory", "sqliteWorker.mjs"));
58032
+ return pathToFileURL2(path68.join(here, "memory", "sqliteWorker.mjs"));
57742
58033
  }
57743
58034
  var SqliteWorkerRpc;
57744
58035
  var init_sqliteRpc = __esm({
@@ -58027,7 +58318,7 @@ WHERE NOT EXISTS (SELECT 1 FROM memory_fts f WHERE f.node_id = n.id);
58027
58318
  // src/cli/memory/sqliteBackend.ts
58028
58319
  import { createHash as createHash19, randomUUID as randomUUID6 } from "node:crypto";
58029
58320
  import { promises as fs28 } from "node:fs";
58030
- import * as path68 from "node:path";
58321
+ import * as path69 from "node:path";
58031
58322
  function boundedLimit(value, fallback = 50) {
58032
58323
  return Math.max(1, Math.min(Math.floor(value ?? fallback), 1e5));
58033
58324
  }
@@ -58089,20 +58380,20 @@ VALUES (${Array.from({ length: 19 }, () => "?").join(",")})`;
58089
58380
  try {
58090
58381
  resolved = await fs28.realpath(projectRoot);
58091
58382
  } catch {
58092
- resolved = path68.resolve(projectRoot);
58383
+ resolved = path69.resolve(projectRoot);
58093
58384
  }
58094
58385
  if (this.initialized && resolved === this.projectRoot) return;
58095
58386
  if (this.initialized) await this.close();
58096
58387
  const filename = this.options.filename ?? "memory.db";
58097
- if (path68.basename(filename) !== filename || filename === "." || filename === "..") {
58388
+ if (path69.basename(filename) !== filename || filename === "." || filename === "..") {
58098
58389
  throw new Error("SQLite memory filename must not contain a path.");
58099
58390
  }
58100
- const zelariDirectory = path68.join(resolved, ".zelari");
58101
- const directory = path68.join(zelariDirectory, "memory");
58391
+ const zelariDirectory = path69.join(resolved, ".zelari");
58392
+ const directory = path69.join(zelariDirectory, "memory");
58102
58393
  for (const candidate of [zelariDirectory, directory]) {
58103
- let stat7;
58394
+ let stat8;
58104
58395
  try {
58105
- stat7 = await fs28.lstat(candidate);
58396
+ stat8 = await fs28.lstat(candidate);
58106
58397
  } catch (error51) {
58107
58398
  if (error51.code !== "ENOENT") throw error51;
58108
58399
  try {
@@ -58110,19 +58401,19 @@ VALUES (${Array.from({ length: 19 }, () => "?").join(",")})`;
58110
58401
  } catch (mkdirError) {
58111
58402
  if (mkdirError.code !== "EEXIST") throw mkdirError;
58112
58403
  }
58113
- stat7 = await fs28.lstat(candidate);
58404
+ stat8 = await fs28.lstat(candidate);
58114
58405
  }
58115
- if (stat7.isSymbolicLink() || !stat7.isDirectory()) {
58406
+ if (stat8.isSymbolicLink() || !stat8.isDirectory()) {
58116
58407
  throw new Error(`Memory directory is not a real directory: ${candidate}`);
58117
58408
  }
58118
58409
  }
58119
58410
  const canonicalDirectory = await fs28.realpath(directory);
58120
- const relativeDirectory = path68.relative(resolved, canonicalDirectory);
58121
- if (relativeDirectory.startsWith("..") || path68.isAbsolute(relativeDirectory)) {
58411
+ const relativeDirectory = path69.relative(resolved, canonicalDirectory);
58412
+ if (relativeDirectory.startsWith("..") || path69.isAbsolute(relativeDirectory)) {
58122
58413
  throw new Error("SQLite memory directory resolves outside the active project.");
58123
58414
  }
58124
58415
  this.projectRoot = resolved;
58125
- this.databasePath = path68.join(canonicalDirectory, filename);
58416
+ this.databasePath = path69.join(canonicalDirectory, filename);
58126
58417
  const opened = await this.rpc.open({
58127
58418
  dbPath: this.databasePath,
58128
58419
  schemaSql: SQLITE_MEMORY_BASE_SCHEMA,
@@ -58655,7 +58946,7 @@ __export(serviceFactory_exports, {
58655
58946
  });
58656
58947
  import { createHash as createHash20 } from "node:crypto";
58657
58948
  import { promises as fs29 } from "node:fs";
58658
- import * as path69 from "node:path";
58949
+ import * as path70 from "node:path";
58659
58950
  function isMemoryV2Enabled(env = process.env) {
58660
58951
  if (env.ZELARI_MEMORY === "0") return false;
58661
58952
  if (env.ZELARI_MEMORY_BACKEND === "file" || env.ZELARI_MEMORY_BACKEND === "jsonl") return false;
@@ -58676,7 +58967,7 @@ async function canonicalProjectId(projectRoot) {
58676
58967
  try {
58677
58968
  canonical = await fs29.realpath(projectRoot);
58678
58969
  } catch {
58679
- canonical = path69.resolve(projectRoot);
58970
+ canonical = path70.resolve(projectRoot);
58680
58971
  }
58681
58972
  canonical = canonical.replace(/\\/g, "/").replace(/\/$/, "");
58682
58973
  if (process.platform === "win32") canonical = canonical.toLocaleLowerCase("en-US");
@@ -59378,8 +59669,8 @@ function readPlan(ctx) {
59378
59669
  } catch {
59379
59670
  }
59380
59671
  }
59381
- const path99 = workspaceFile(ctx.rootDir, "plan");
59382
- const doc = ctx.storage.readIfExists(path99);
59672
+ const path100 = workspaceFile(ctx.rootDir, "plan");
59673
+ const doc = ctx.storage.readIfExists(path100);
59383
59674
  if (!doc) return { phases: [], tasks: [], milestones: [] };
59384
59675
  const meta3 = doc.meta;
59385
59676
  return {
@@ -59560,7 +59851,7 @@ function addMilestoneRecord(ctx, summary, input) {
59560
59851
  dueDate: input.dueDate,
59561
59852
  targetVersion: version2
59562
59853
  });
59563
- const path99 = join31(ctx.rootDir, "milestones", `${id3}.md`);
59854
+ const path100 = join31(ctx.rootDir, "milestones", `${id3}.md`);
59564
59855
  const meta3 = {
59565
59856
  kind: "milestone",
59566
59857
  id: id3,
@@ -59577,7 +59868,7 @@ function addMilestoneRecord(ctx, summary, input) {
59577
59868
  `Target version: ${version2}`,
59578
59869
  ""
59579
59870
  ].join("\n");
59580
- ctx.storage.write(path99, meta3, body);
59871
+ ctx.storage.write(path100, meta3, body);
59581
59872
  return { id: id3, created: true };
59582
59873
  }
59583
59874
  function readPlanSummary(ctx) {
@@ -59781,7 +60072,7 @@ function addIdeaStub(ctx) {
59781
60072
  const tags = args["tags"] ?? [];
59782
60073
  const category = args["category"] ?? "General";
59783
60074
  const id3 = `${nextAdrId(ctx)}-${slugify3(title)}`;
59784
- const path99 = workspaceArtifact(ctx.rootDir, "decisions", id3);
60075
+ const path100 = workspaceArtifact(ctx.rootDir, "decisions", id3);
59785
60076
  const meta3 = {
59786
60077
  kind: "adr",
59787
60078
  status: "proposed",
@@ -59807,7 +60098,7 @@ function addIdeaStub(ctx) {
59807
60098
  ...consequences.map((c) => `- ${c}`),
59808
60099
  ""
59809
60100
  ].join("\n");
59810
- ctx.storage.write(path99, meta3, body);
60101
+ ctx.storage.write(path100, meta3, body);
59811
60102
  return `ADR ${id3} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
59812
60103
  });
59813
60104
  }
@@ -59889,14 +60180,14 @@ function createDocumentStub(ctx) {
59889
60180
  ctx.storage.write(risksPath, riskMeta, content);
59890
60181
  return `Document "${title}" created at risks.md (workspace root).`;
59891
60182
  }
59892
- const path99 = workspaceArtifact(ctx.rootDir, "docs", slug);
60183
+ const path100 = workspaceArtifact(ctx.rootDir, "docs", slug);
59893
60184
  const meta3 = {
59894
60185
  kind: "doc",
59895
60186
  id: slug,
59896
60187
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
59897
60188
  tags
59898
60189
  };
59899
- ctx.storage.write(path99, meta3, content);
60190
+ ctx.storage.write(path100, meta3, content);
59900
60191
  return `Document "${title}" created at docs/${slug}.md.`;
59901
60192
  });
59902
60193
  }
@@ -60521,10 +60812,10 @@ function getUserMcpPath() {
60521
60812
  function getProjectMcpPath(projectRoot) {
60522
60813
  return join32(projectRoot, ".zelari", "mcp.json");
60523
60814
  }
60524
- function readFile7(path99) {
60525
- if (!existsSync42(path99)) return {};
60815
+ function readFile9(path100) {
60816
+ if (!existsSync42(path100)) return {};
60526
60817
  try {
60527
- const parsed = JSON.parse(readFileSync30(path99, "utf8"));
60818
+ const parsed = JSON.parse(readFileSync30(path100, "utf8"));
60528
60819
  const out = {};
60529
60820
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
60530
60821
  const hasCommand = !!cfg && typeof cfg.command === "string" && !!cfg.command.trim();
@@ -60546,17 +60837,17 @@ function readFile7(path99) {
60546
60837
  return {};
60547
60838
  }
60548
60839
  }
60549
- function writeFile3(path99, servers) {
60550
- mkdirSync17(dirname9(path99), { recursive: true });
60840
+ function writeFile3(path100, servers) {
60841
+ mkdirSync17(dirname9(path100), { recursive: true });
60551
60842
  const body = { mcpServers: servers };
60552
- writeFileSync19(path99, `${JSON.stringify(body, null, 2)}
60843
+ writeFileSync19(path100, `${JSON.stringify(body, null, 2)}
60553
60844
  `, "utf8");
60554
60845
  }
60555
60846
  function listMcpServers(projectRoot) {
60556
60847
  const userPath = getUserMcpPath();
60557
- const userServers = readFile7(userPath);
60848
+ const userServers = readFile9(userPath);
60558
60849
  const projectPath = projectRoot && projectRoot.trim() ? getProjectMcpPath(projectRoot.trim()) : null;
60559
- const projectServers = projectPath ? readFile7(projectPath) : {};
60850
+ const projectServers = projectPath ? readFile9(projectPath) : {};
60560
60851
  const servers = [];
60561
60852
  for (const [name, cfg] of Object.entries(userServers)) {
60562
60853
  servers.push({ name, ...cfg, scope: "user", path: userPath });
@@ -60587,9 +60878,9 @@ function upsertMcpServer(opts) {
60587
60878
  error: "either command (stdio) or url (http) is required"
60588
60879
  };
60589
60880
  }
60590
- let path99;
60881
+ let path100;
60591
60882
  if (opts.scope === "user") {
60592
- path99 = getUserMcpPath();
60883
+ path100 = getUserMcpPath();
60593
60884
  } else {
60594
60885
  const root = opts.projectRoot?.trim();
60595
60886
  if (!root) {
@@ -60598,9 +60889,9 @@ function upsertMcpServer(opts) {
60598
60889
  error: "projectRoot required for project scope (Open Folder first)"
60599
60890
  };
60600
60891
  }
60601
- path99 = getProjectMcpPath(root);
60892
+ path100 = getProjectMcpPath(root);
60602
60893
  }
60603
- const current = readFile7(path99);
60894
+ const current = readFile9(path100);
60604
60895
  current[name] = {
60605
60896
  command: hasCommand ? opts.config.command.trim() : void 0,
60606
60897
  args: opts.config.args,
@@ -60611,21 +60902,21 @@ function upsertMcpServer(opts) {
60611
60902
  serial: opts.config.serial,
60612
60903
  enabled: opts.config.enabled !== false
60613
60904
  };
60614
- writeFile3(path99, current);
60615
- return { ok: true, path: path99 };
60905
+ writeFile3(path100, current);
60906
+ return { ok: true, path: path100 };
60616
60907
  }
60617
60908
  function removeMcpServer(opts) {
60618
- const path99 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
60619
- if (!path99) {
60909
+ const path100 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
60910
+ if (!path100) {
60620
60911
  return { ok: false, error: "projectRoot required for project scope" };
60621
60912
  }
60622
- const current = readFile7(path99);
60913
+ const current = readFile9(path100);
60623
60914
  if (!(opts.name in current)) {
60624
- return { ok: false, error: `Server "${opts.name}" not found in ${path99}` };
60915
+ return { ok: false, error: `Server "${opts.name}" not found in ${path100}` };
60625
60916
  }
60626
60917
  delete current[opts.name];
60627
- writeFile3(path99, current);
60628
- return { ok: true, path: path99 };
60918
+ writeFile3(path100, current);
60919
+ return { ok: true, path: path100 };
60629
60920
  }
60630
60921
  var init_mcpConfigIo = __esm({
60631
60922
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -61040,7 +61331,8 @@ async function* dispatchCouncil(userMessage, options) {
61040
61331
  maxToolLoopIterations: options.maxToolLoopIterations,
61041
61332
  maxToolLoopHardCap: options.maxToolLoopHardCap,
61042
61333
  skipSpecialists: options.skipSpecialists,
61043
- feedbackStore: options.feedbackStore
61334
+ feedbackStore: options.feedbackStore,
61335
+ signal: options.signal
61044
61336
  };
61045
61337
  if (!options.disableWorkspaceTools) {
61046
61338
  const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
@@ -61093,12 +61385,12 @@ __export(agentsMd_exports, {
61093
61385
  import { existsSync as existsSync44, readFileSync as readFileSync32, writeFileSync as writeFileSync20 } from "node:fs";
61094
61386
  import { createHash as createHash21 } from "node:crypto";
61095
61387
  import { join as join34 } from "node:path";
61096
- import { readFile as readFile8 } from "node:fs/promises";
61388
+ import { readFile as readFile10 } from "node:fs/promises";
61097
61389
  async function readPackageJson3(projectRoot) {
61098
- const path99 = join34(projectRoot, "package.json");
61099
- if (!existsSync44(path99)) return null;
61390
+ const path100 = join34(projectRoot, "package.json");
61391
+ if (!existsSync44(path100)) return null;
61100
61392
  try {
61101
- return JSON.parse(await readFile8(path99, "utf8"));
61393
+ return JSON.parse(await readFile10(path100, "utf8"));
61102
61394
  } catch {
61103
61395
  return null;
61104
61396
  }
@@ -61180,9 +61472,9 @@ async function genBuild(ctx) {
61180
61472
  ].join("\n");
61181
61473
  }
61182
61474
  async function genOpenQuestions(ctx) {
61183
- const path99 = join34(ctx.rootDir, "risks.md");
61184
- if (!existsSync44(path99)) return "_No open questions._";
61185
- const content = readFileSync32(path99, "utf8");
61475
+ const path100 = join34(ctx.rootDir, "risks.md");
61476
+ if (!existsSync44(path100)) return "_No open questions._";
61477
+ const content = readFileSync32(path100, "utf8");
61186
61478
  const lines = content.split("\n");
61187
61479
  const questions = [];
61188
61480
  let currentTitle = "";
@@ -61456,9 +61748,9 @@ function versionKey(value) {
61456
61748
  function firstString2(v) {
61457
61749
  return typeof v === "string" && v.trim().length > 0 ? v : null;
61458
61750
  }
61459
- function readFileSyncSafe(path99) {
61751
+ function readFileSyncSafe(path100) {
61460
61752
  try {
61461
- return readFileSync33(path99, "utf8");
61753
+ return readFileSync33(path100, "utf8");
61462
61754
  } catch {
61463
61755
  return null;
61464
61756
  }
@@ -61706,7 +61998,7 @@ __export(evidenceFromSpine_exports, {
61706
61998
  evidenceRefsFromEventLines: () => evidenceRefsFromEventLines
61707
61999
  });
61708
62000
  import { existsSync as existsSync47, readFileSync as readFileSync35 } from "node:fs";
61709
- import path70 from "node:path";
62001
+ import path71 from "node:path";
61710
62002
  function asRecord2(v) {
61711
62003
  return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
61712
62004
  }
@@ -61744,7 +62036,7 @@ function evidenceRefsFromEventLines(lines) {
61744
62036
  }
61745
62037
  function collectSessionEvidenceRefs(sessionId2) {
61746
62038
  try {
61747
- const file2 = path70.join(sessionsDir(), sessionId2, "events.jsonl");
62039
+ const file2 = path71.join(sessionsDir(), sessionId2, "events.jsonl");
61748
62040
  if (!existsSync47(file2)) return [];
61749
62041
  return evidenceRefsFromEventLines(readFileSync35(file2, "utf8").split("\n"));
61750
62042
  } catch {
@@ -61994,8 +62286,8 @@ async function runPostCouncilHook(ctx, options) {
61994
62286
  sources: scope.sources
61995
62287
  } : void 0
61996
62288
  });
61997
- const path99 = writeCouncilCompletion(ctx.rootDir, completion);
61998
- completionHook = { ran: true, path: path99, completion };
62289
+ const path100 = writeCouncilCompletion(ctx.rootDir, completion);
62290
+ completionHook = { ran: true, path: path100, completion };
61999
62291
  } catch (err) {
62000
62292
  completionHook = {
62001
62293
  ran: true,
@@ -62040,7 +62332,7 @@ import {
62040
62332
  writeFileSync as writeFileSync22,
62041
62333
  mkdirSync as mkdirSync18
62042
62334
  } from "node:fs";
62043
- import path71 from "node:path";
62335
+ import path72 from "node:path";
62044
62336
  var FeedbackStore;
62045
62337
  var init_councilFeedback = __esm({
62046
62338
  "src/cli/councilFeedback.ts"() {
@@ -62157,7 +62449,7 @@ var init_councilFeedback = __esm({
62157
62449
  }
62158
62450
  }
62159
62451
  save() {
62160
- mkdirSync18(path71.dirname(this.file), { recursive: true });
62452
+ mkdirSync18(path72.dirname(this.file), { recursive: true });
62161
62453
  writeFileSync22(
62162
62454
  this.file,
62163
62455
  JSON.stringify({ entries: this.entries }, null, 2),
@@ -62232,12 +62524,12 @@ __export(ledger_exports, {
62232
62524
  readLedger: () => readLedger
62233
62525
  });
62234
62526
  import { appendFileSync as appendFileSync4, existsSync as existsSync50, mkdirSync as mkdirSync19, readFileSync as readFileSync38 } from "node:fs";
62235
- import path72 from "node:path";
62527
+ import path73 from "node:path";
62236
62528
  function evolutionMode(env = process.env) {
62237
62529
  return env[EVOLUTION_ENV] === "shadow" ? "shadow" : "0";
62238
62530
  }
62239
62531
  function ledgerPath(cwd) {
62240
- return path72.join(cwd, LEDGER_REL);
62532
+ return path73.join(cwd, LEDGER_REL);
62241
62533
  }
62242
62534
  function appendLedgerEntry(cwd, entry) {
62243
62535
  if (evolutionMode() === "0") {
@@ -62245,7 +62537,7 @@ function appendLedgerEntry(cwd, entry) {
62245
62537
  }
62246
62538
  try {
62247
62539
  const file2 = ledgerPath(cwd);
62248
- mkdirSync19(path72.dirname(file2), { recursive: true });
62540
+ mkdirSync19(path73.dirname(file2), { recursive: true });
62249
62541
  appendFileSync4(file2, `${JSON.stringify(entry)}
62250
62542
  `, "utf8");
62251
62543
  return { written: true, path: file2 };
@@ -62346,7 +62638,7 @@ var init_ledger = __esm({
62346
62638
  "src/cli/evolution/ledger.ts"() {
62347
62639
  "use strict";
62348
62640
  EVOLUTION_ENV = "ZELARI_EVOLUTION";
62349
- LEDGER_REL = path72.join(".zelari", "evolution", "ledger.jsonl");
62641
+ LEDGER_REL = path73.join(".zelari", "evolution", "ledger.jsonl");
62350
62642
  TIER_WEIGHTS = {
62351
62643
  build: 1,
62352
62644
  "tool-output": 1,
@@ -62488,7 +62780,7 @@ __export(fileBackend_exports, {
62488
62780
  });
62489
62781
  import { randomUUID as randomUUID7 } from "node:crypto";
62490
62782
  import { promises as fs31 } from "node:fs";
62491
- import * as path73 from "node:path";
62783
+ import * as path74 from "node:path";
62492
62784
  function tokenize2(text) {
62493
62785
  return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 3);
62494
62786
  }
@@ -62541,8 +62833,8 @@ var init_fileBackend = __esm({
62541
62833
  logPath = "";
62542
62834
  memoryDir = "";
62543
62835
  async init(projectRoot) {
62544
- this.memoryDir = path73.join(projectRoot, ".zelari", "memory");
62545
- this.logPath = path73.join(this.memoryDir, "log.jsonl");
62836
+ this.memoryDir = path74.join(projectRoot, ".zelari", "memory");
62837
+ this.logPath = path74.join(this.memoryDir, "log.jsonl");
62546
62838
  await fs31.mkdir(this.memoryDir, { recursive: true });
62547
62839
  }
62548
62840
  async add(content, metadata2 = {}, graph) {
@@ -62615,12 +62907,12 @@ var init_fileBackend = __esm({
62615
62907
 
62616
62908
  // src/cli/traceStore.ts
62617
62909
  import { promises as fs32 } from "node:fs";
62618
- import * as path74 from "node:path";
62910
+ import * as path75 from "node:path";
62619
62911
  function traceDir(projectRoot) {
62620
- return path74.join(projectRoot, ".zelari", "trace");
62912
+ return path75.join(projectRoot, ".zelari", "trace");
62621
62913
  }
62622
62914
  function tracePath(projectRoot, missionId) {
62623
- return path74.join(traceDir(projectRoot), `${missionId}.json`);
62915
+ return path75.join(traceDir(projectRoot), `${missionId}.json`);
62624
62916
  }
62625
62917
  async function saveTrace(projectRoot, missionId, entries) {
62626
62918
  const dir = traceDir(projectRoot);
@@ -62657,7 +62949,7 @@ __export(zelariMission_exports, {
62657
62949
  });
62658
62950
  import { randomUUID as randomUUID8 } from "node:crypto";
62659
62951
  import { promises as fs33 } from "node:fs";
62660
- import * as path75 from "node:path";
62952
+ import * as path76 from "node:path";
62661
62953
  function resolveMaxIterations(env = process.env) {
62662
62954
  const raw = env.ZELARI_MISSION_MAX_ITER;
62663
62955
  const n = raw ? Number.parseInt(raw, 10) : DEFAULT_MAX_ITER;
@@ -62700,10 +62992,10 @@ function isMissionAutoStart(env = process.env) {
62700
62992
  return env.ZELARI_MISSION_AUTO === "1";
62701
62993
  }
62702
62994
  async function writeMissionState(projectRoot, state3) {
62703
- const dir = path75.join(projectRoot, ".zelari");
62995
+ const dir = path76.join(projectRoot, ".zelari");
62704
62996
  await fs33.mkdir(dir, { recursive: true });
62705
62997
  await fs33.writeFile(
62706
- path75.join(dir, "mission-state.json"),
62998
+ path76.join(dir, "mission-state.json"),
62707
62999
  JSON.stringify(state3, null, 2) + "\n",
62708
63000
  "utf8"
62709
63001
  );
@@ -62798,6 +63090,13 @@ async function runZelariMission(userMessage, brief, deps) {
62798
63090
  let forcePivot = false;
62799
63091
  const missionStartMs = now().getTime();
62800
63092
  while (true) {
63093
+ if (deps.signal?.aborted) {
63094
+ state3.status = "cancelled";
63095
+ state3.updatedAt = now().toISOString();
63096
+ await persist();
63097
+ deps.emit("[zelari] missione cancellata.");
63098
+ return state3;
63099
+ }
62801
63100
  const runMode = pendingDesign ? "design-phase" : "implementation";
62802
63101
  if (runMode === "implementation") {
62803
63102
  deps.onMissionPhase?.("build", `impl-${implStep + 1}`);
@@ -62854,6 +63153,13 @@ async function runZelariMission(userMessage, brief, deps) {
62854
63153
  );
62855
63154
  return state3;
62856
63155
  }
63156
+ if (deps.signal?.aborted) {
63157
+ state3.status = "cancelled";
63158
+ state3.updatedAt = now().toISOString();
63159
+ await persist();
63160
+ deps.emit("[zelari] missione cancellata.");
63161
+ return state3;
63162
+ }
62857
63163
  if (typeof result.costUsd === "number") cumulativeCostUsd += result.costUsd;
62858
63164
  if (typeof result.costTokens === "number") cumulativeTokens += result.costTokens;
62859
63165
  await deps.memory.add(
@@ -63378,7 +63684,7 @@ function safeSocketPath(socketPath) {
63378
63684
  return socketPath.trim();
63379
63685
  }
63380
63686
  function startPermissionBroker(socketPath, handlers, opts) {
63381
- const path99 = safeSocketPath(socketPath);
63687
+ const path100 = safeSocketPath(socketPath);
63382
63688
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
63383
63689
  const sockets = /* @__PURE__ */ new Set();
63384
63690
  const server = createServer2((socket) => {
@@ -63478,10 +63784,10 @@ function startPermissionBroker(socketPath, handlers, opts) {
63478
63784
  return new Promise((resolve9, reject) => {
63479
63785
  const onError = (err) => reject(err);
63480
63786
  server.once("error", onError);
63481
- server.listen(path99, () => {
63787
+ server.listen(path100, () => {
63482
63788
  server.removeListener("error", onError);
63483
63789
  resolve9({
63484
- socketPath: path99,
63790
+ socketPath: path100,
63485
63791
  stop: () => new Promise((res) => {
63486
63792
  for (const s of sockets) s.destroy();
63487
63793
  sockets.clear();
@@ -63492,7 +63798,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
63492
63798
  if (done) return;
63493
63799
  done = true;
63494
63800
  if (process.platform !== "win32") {
63495
- unlink(path99, () => res());
63801
+ unlink(path100, () => res());
63496
63802
  } else {
63497
63803
  res();
63498
63804
  }
@@ -63505,11 +63811,11 @@ function startPermissionBroker(socketPath, handlers, opts) {
63505
63811
  });
63506
63812
  }
63507
63813
  function requestBrokerAsk(socketPath, ask, opts) {
63508
- const path99 = safeSocketPath(socketPath);
63814
+ const path100 = safeSocketPath(socketPath);
63509
63815
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
63510
63816
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
63511
63817
  return new Promise((resolve9, reject) => {
63512
- const socket = connect(path99);
63818
+ const socket = connect(path100);
63513
63819
  let buffer = "";
63514
63820
  let settled = false;
63515
63821
  const settle = (fn) => {
@@ -63524,7 +63830,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
63524
63830
  settle(
63525
63831
  () => reject(
63526
63832
  new Error(
63527
- `permission broker unavailable at "${path99}" (connect timed out after ${connectTimeoutMs}ms)`
63833
+ `permission broker unavailable at "${path100}" (connect timed out after ${connectTimeoutMs}ms)`
63528
63834
  )
63529
63835
  )
63530
63836
  );
@@ -64713,7 +65019,7 @@ var init_prereqChecks = __esm({
64713
65019
 
64714
65020
  // src/cli/plugins/prefs.ts
64715
65021
  import { existsSync as existsSync53, readFileSync as readFileSync40, writeFileSync as writeFileSync23, mkdirSync as mkdirSync20 } from "node:fs";
64716
- import path81 from "node:path";
65022
+ import path82 from "node:path";
64717
65023
  function getPluginPrefsPath() {
64718
65024
  return pluginsPrefsPath();
64719
65025
  }
@@ -64736,7 +65042,7 @@ function getPluginPrefs() {
64736
65042
  }
64737
65043
  function writePluginPrefs(prefs) {
64738
65044
  const file2 = getPluginPrefsPath();
64739
- mkdirSync20(path81.dirname(file2), { recursive: true });
65045
+ mkdirSync20(path82.dirname(file2), { recursive: true });
64740
65046
  writeFileSync23(file2, JSON.stringify(prefs, null, 2), {
64741
65047
  encoding: "utf-8",
64742
65048
  mode: 384
@@ -64774,7 +65080,7 @@ __export(registry_exports, {
64774
65080
  isBinaryOnPath: () => isBinaryOnPath
64775
65081
  });
64776
65082
  import { existsSync as existsSync54 } from "node:fs";
64777
- import path82 from "node:path";
65083
+ import path83 from "node:path";
64778
65084
  function detectLocalBin(bin) {
64779
65085
  return (cwd) => {
64780
65086
  try {
@@ -64792,7 +65098,7 @@ function isBinaryOnPath(bin, opts = {}) {
64792
65098
  const platform = opts.platform ?? process.platform;
64793
65099
  const exists = opts.exists ?? existsSync54;
64794
65100
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
64795
- const pathMod = platform === "win32" ? path82.win32 : path82.posix;
65101
+ const pathMod = platform === "win32" ? path83.win32 : path83.posix;
64796
65102
  const sep4 = platform === "win32" ? ";" : ":";
64797
65103
  const dirs = pathEnv.split(sep4).filter((d) => d.length > 0);
64798
65104
  const candidates = [bin];
@@ -65887,7 +66193,7 @@ var init_policy = __esm({
65887
66193
 
65888
66194
  // src/cli/orchestration/facts.ts
65889
66195
  import { promises as fs43 } from "node:fs";
65890
- import path86 from "node:path";
66196
+ import path87 from "node:path";
65891
66197
  async function collectRepoFileCount(root = process.cwd()) {
65892
66198
  try {
65893
66199
  await fs43.readdir(root);
@@ -65907,7 +66213,7 @@ async function collectRepoFileCount(root = process.cwd()) {
65907
66213
  }
65908
66214
  for (const e of entries) {
65909
66215
  if (e.isDirectory()) {
65910
- if (!SKIP_DIRS.has(e.name)) queue.push(path86.join(dir, e.name));
66216
+ if (!SKIP_DIRS.has(e.name)) queue.push(path87.join(dir, e.name));
65911
66217
  } else if (e.isFile()) {
65912
66218
  count++;
65913
66219
  if (count > MAX_WALK_FILES) return count;
@@ -65972,11 +66278,12 @@ var init_facts = __esm({
65972
66278
  });
65973
66279
 
65974
66280
  // src/cli/utils/streamScrub.ts
65975
- function createStreamScrubber2() {
66281
+ function createStreamScrubber2(opts = {}) {
66282
+ const stripQuestion = opts.stripQuestion !== false;
65976
66283
  let rawBuf = "";
65977
66284
  let emittedLen = 0;
65978
66285
  const snapshot = () => {
65979
- const cleaned = cleanAgentContent(rawBuf);
66286
+ const cleaned = cleanAgentContent(rawBuf, { stripQuestion });
65980
66287
  if (cleaned.length <= emittedLen) return "";
65981
66288
  const delta = cleaned.slice(emittedLen);
65982
66289
  emittedLen = cleaned.length;
@@ -66004,7 +66311,7 @@ var init_streamScrub = __esm({
66004
66311
  });
66005
66312
 
66006
66313
  // src/cli/harnessState.ts
66007
- import path87 from "node:path";
66314
+ import path88 from "node:path";
66008
66315
  function asString4(v) {
66009
66316
  return typeof v === "string" ? v : "";
66010
66317
  }
@@ -66179,7 +66486,7 @@ function contractFor(t) {
66179
66486
  };
66180
66487
  }
66181
66488
  async function readHarnessState(sessionDir) {
66182
- const report = await readSessionLog(path87.join(sessionDir, "events.jsonl"));
66489
+ const report = await readSessionLog(path88.join(sessionDir, "events.jsonl"));
66183
66490
  return deriveHarnessState(report.events);
66184
66491
  }
66185
66492
  var init_harnessState = __esm({
@@ -66190,12 +66497,12 @@ var init_harnessState = __esm({
66190
66497
  });
66191
66498
 
66192
66499
  // src/cli/headless/harnessStateEmit.ts
66193
- import path88 from "node:path";
66500
+ import path89 from "node:path";
66194
66501
  async function emitHarnessStateEvent(opts) {
66195
66502
  if (opts.output !== "json") return;
66196
66503
  try {
66197
66504
  const sessionsDir2 = resolveSessionsDir({ workspaceRoot: opts.workspaceRoot });
66198
- const state3 = await readHarnessState(path88.join(sessionsDir2, opts.spine.sessionId));
66505
+ const state3 = await readHarnessState(path89.join(sessionsDir2, opts.spine.sessionId));
66199
66506
  opts.emitEvent({ type: "harness_state", ...state3 });
66200
66507
  } catch (err) {
66201
66508
  const msg = err instanceof Error ? err.message : String(err);
@@ -66214,215 +66521,39 @@ var init_harnessStateEmit = __esm({
66214
66521
  }
66215
66522
  });
66216
66523
 
66217
- // src/cli/headless/policyGate.ts
66218
- import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
66219
- import { randomUUID as randomUUID9 } from "node:crypto";
66220
- function checkStrictPolicyLoad(root, opts) {
66221
- if (isPolicyEngineDisabled()) return { blocked: false, warnings: [] };
66222
- try {
66223
- const set2 = loadPolicySet(root, opts);
66224
- return { blocked: false, warnings: set2.warnings };
66225
- } catch (err) {
66226
- if (!(err instanceof PolicyLoadError)) throw err;
66227
- const file2 = isAbsolute5(err.file) ? err.file : resolve7(root, err.file);
66228
- return {
66229
- blocked: true,
66230
- warnings: [],
66231
- block: {
66232
- reason: POLICY_LOAD_BLOCK_REASON,
66233
- exitCode: POLICY_LOAD_EXIT_CODE,
66234
- code: err.code,
66235
- file: file2,
66236
- ...err.message ? { detail: err.message } : {}
66237
- }
66238
- };
66239
- }
66240
- }
66241
- function reportPolicyLoadBlocked(block, output) {
66242
- const where = `${block.file}${block.detail ? ` \u2014 ${block.detail}` : ""}`;
66243
- process.stderr.write(`[zelari-code --headless] ${block.reason}: ${where}
66244
- `);
66245
- if (output === "json") {
66246
- emitEvent({
66247
- type: "error",
66248
- severity: "fatal",
66249
- message: `${block.reason}: ${where}`,
66250
- code: block.reason
66251
- });
66252
- }
66253
- }
66254
- async function recordPolicyLoadBlockedOnSpine(block, opts = {}) {
66255
- try {
66256
- const { openHeadlessSpine: openHeadlessSpine2 } = await Promise.resolve().then(() => (init_headlessSpine(), headlessSpine_exports));
66257
- const sessionId2 = opts.resumeSessionId ?? randomUUID9();
66258
- const spine = await openHeadlessSpine2({
66259
- sessionId: sessionId2,
66260
- ...opts.mode ? { mode: opts.mode } : {},
66261
- ...opts.profile ? { profile: opts.profile } : {},
66262
- workspace: opts.workspace ?? process.cwd()
66263
- });
66264
- if (opts.mode === "zelari") {
66265
- spine.missionPhase("dispatch", block.reason);
66266
- }
66267
- spine.note(block.reason, {
66268
- code: block.code,
66269
- file: block.file,
66270
- exitCode: block.exitCode,
66271
- ...block.detail ? { detail: block.detail } : {}
66272
- });
66273
- await spine.close("error");
66274
- } catch {
66275
- }
66276
- }
66277
- var init_policyGate = __esm({
66278
- "src/cli/headless/policyGate.ts"() {
66279
- "use strict";
66280
- init_policyEngine();
66281
- init_policyLoadMode();
66282
- init_headless();
66283
- }
66284
- });
66285
-
66286
- // src/cli/kraken/verifierResolution.ts
66287
- function verifierOverrideToModelSelection(override) {
66288
- if (override && typeof override.provider === "string" && typeof override.model === "string" && override.provider.trim().length > 0 && override.model.trim().length > 0) {
66289
- return {
66290
- mode: "fixed",
66291
- provider: override.provider.trim(),
66292
- model: override.model.trim()
66293
- };
66294
- }
66295
- return { mode: "inherit" };
66296
- }
66297
- function loadVerifierModelSelection() {
66298
- return verifierOverrideToModelSelection(getKrakenVerifierOverride());
66299
- }
66300
- var init_verifierResolution = __esm({
66301
- "src/cli/kraken/verifierResolution.ts"() {
66302
- "use strict";
66303
- init_providerConfig();
66304
- }
66305
- });
66306
-
66307
- // src/cli/kraken/verifierLifecycle.ts
66308
- function verifierReviewEnabled(selection = loadVerifierModelSelection(), env = process.env) {
66309
- const v = env.ZELARI_VERIFIER_REVIEW?.toLowerCase();
66310
- if (v === "0" || v === "false" || v === "off") return false;
66311
- if (v === "1" || v === "true" || v === "on") return true;
66312
- return selection.mode === "fixed";
66524
+ // src/cli/serve/sessionControl.ts
66525
+ import { AsyncLocalStorage } from "node:async_hooks";
66526
+ function runWithSession(sessionId2, fn) {
66527
+ return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
66313
66528
  }
66314
- function makeVerifierCallModel(loadStream, identity, timeoutMs2 = 12e4) {
66315
- return async ({ system, user }) => {
66316
- const stream = await loadStream(identity.provider, identity.model);
66317
- if (!stream) {
66318
- throw new Error(`no provider config for verifier "${identity.provider}"`);
66529
+ function registerLiveTurnControl(control) {
66530
+ const store6 = dispatchContext.getStore();
66531
+ if (!store6) return void 0;
66532
+ const registered = { ...control, token: store6.token };
66533
+ liveTurns.set(store6.sessionId, registered);
66534
+ return () => {
66535
+ if (liveTurns.get(store6.sessionId) === registered) {
66536
+ liveTurns.delete(store6.sessionId);
66319
66537
  }
66320
- const { text } = await collectProviderText(stream, {
66321
- messages: [
66322
- { role: "system", content: system },
66323
- { role: "user", content: user }
66324
- ],
66325
- model: identity.model,
66326
- provider: identity.provider,
66327
- tools: [],
66328
- signal: AbortSignal.timeout(timeoutMs2)
66329
- });
66330
- return { text, provider: identity.provider, model: identity.model };
66331
66538
  };
66332
66539
  }
66333
- function isTestEvidenceCriterion(criterionId2) {
66334
- const id3 = criterionId2.toLowerCase();
66335
- return ["test", "typecheck", "build", "lint"].some((k) => id3.includes(k));
66336
- }
66337
- function extractTestOutputExcerpt(results, maxChars = 4e3) {
66338
- const lines = [];
66339
- for (const r of results) {
66340
- if (!isTestEvidenceCriterion(r.criterionId)) continue;
66341
- lines.push([r.criterionId, r.status, r.detail].filter(Boolean).join(" \u2014 "));
66342
- }
66343
- if (lines.length === 0) return "";
66344
- return lines.join("\n").slice(0, maxChars);
66345
- }
66346
- async function buildBlindReviewInput(evaluation, deps) {
66347
- const results = evaluation.results ?? [];
66348
- const passed = results.filter((r) => r.status === "pass").length;
66349
- const verdict = evaluation.evaluation?.verdict ?? "UNKNOWN";
66350
- const summary = `Kraken BUILD turn \u2014 deterministic evidence: ${passed}/${results.length} criteria pass, completion verdict ${verdict}.`;
66351
- const task = deps.task?.trim();
66352
- const testOutputExcerpt = extractTestOutputExcerpt(results);
66353
- let diffSummary;
66354
- try {
66355
- const res = await (deps.getDiff ?? getWorkingDiff)({
66356
- cwd: deps.cwd ?? process.cwd(),
66357
- maxChars: 8e3,
66358
- staged: true
66359
- });
66360
- if (res && !res.empty && res.diff) diffSummary = res.diff;
66361
- } catch {
66362
- }
66363
- return {
66364
- ...task ? { task } : {},
66365
- summary,
66366
- ...diffSummary !== void 0 ? { diffSummary } : {},
66367
- ...testOutputExcerpt ? { testOutputExcerpt } : {},
66368
- results
66369
- };
66540
+ function getLiveTurnControl(sessionId2) {
66541
+ return liveTurns.get(sessionId2);
66370
66542
  }
66371
- async function runAdvisoryVerifierReview(evaluation, deps = {}) {
66372
- if (!evaluation.evaluation || !evaluation.results) return null;
66373
- const env = deps.env ?? process.env;
66374
- const selection = deps.selection ?? loadVerifierModelSelection();
66375
- if (!verifierReviewEnabled(selection, env)) return null;
66376
- const risk = deps.risk ?? activeRisk(env);
66377
- if (risk === "low") return null;
66378
- const route = resolveVerifierRouting(
66379
- selection.mode === "fixed" ? { provider: selection.provider, model: selection.model } : null,
66380
- risk,
66381
- {
66382
- selectionMode: selection.mode === "fixed" ? "fixed" : "inherit",
66383
- session: deps.session ?? null,
66384
- familyCandidates: deps.familyCandidates,
66385
- env
66386
- }
66387
- );
66388
- const reviewers = route.reviewers;
66389
- let callModel = deps.callModel;
66390
- if (reviewers.length === 0 || !callModel && !deps.loadStream) return null;
66391
- const blind = await buildBlindReviewInput(evaluation, deps);
66392
- const reviews = [];
66393
- for (const reviewer of reviewers) {
66394
- const call = callModel ?? makeVerifierCallModel(deps.loadStream, reviewer.identity, deps.timeoutMs);
66395
- const service = new VerifierService({
66396
- callModel: call,
66397
- config: {
66398
- enabled: true,
66399
- model: selection,
66400
- progressScoring: false,
66401
- bon: { enabled: false, n: 3 }
66402
- },
66403
- emit: deps.emit,
66404
- env
66405
- });
66406
- reviews.push(await service.reviewCompletion({ ...blind, session: deps.session }));
66407
- }
66408
- const review = reviews.length > 1 ? mergeVerifierVerdicts(reviews) : reviews[0];
66409
- evaluation.review = review;
66410
- if (reviews.length > 1 && risk === "critical") {
66411
- evaluation.reviewDivergence = divergenceFromReviews(
66412
- reviews,
66413
- reviewers.map((r) => ({ family: r.family, role: r.role }))
66414
- );
66543
+ function clearSessionTurnControl(sessionId2) {
66544
+ const store6 = dispatchContext.getStore();
66545
+ if (!store6 || store6.sessionId !== sessionId2) return;
66546
+ const registered = liveTurns.get(sessionId2);
66547
+ if (registered && registered.token === store6.token) {
66548
+ liveTurns.delete(sessionId2);
66415
66549
  }
66416
- return review;
66417
66550
  }
66418
- var init_verifierLifecycle = __esm({
66419
- "src/cli/kraken/verifierLifecycle.ts"() {
66551
+ var dispatchContext, liveTurns;
66552
+ var init_sessionControl = __esm({
66553
+ "src/cli/serve/sessionControl.ts"() {
66420
66554
  "use strict";
66421
- init_verification();
66422
- init_krakenSelectTool();
66423
- init_gitOps();
66424
- init_verifierResolution();
66425
- init_verifierRouting();
66555
+ dispatchContext = new AsyncLocalStorage();
66556
+ liveTurns = /* @__PURE__ */ new Map();
66426
66557
  }
66427
66558
  });
66428
66559
 
@@ -66652,51 +66783,289 @@ var init_controlBridge = __esm({
66652
66783
  }
66653
66784
  });
66654
66785
 
66655
- // src/cli/serve/sessionControl.ts
66656
- import { AsyncLocalStorage } from "node:async_hooks";
66657
- function runWithSession(sessionId2, fn) {
66658
- return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
66786
+ // src/cli/headless/liveTurnAbort.ts
66787
+ function attachHeadlessLiveCancel(opts) {
66788
+ const abort = new AbortController();
66789
+ const controlQueue = new RuntimeControlQueue();
66790
+ const cancel = () => {
66791
+ if (!abort.signal.aborted) abort.abort();
66792
+ return true;
66793
+ };
66794
+ const controlPlane = opts?.output === "json" && process.stdin.isTTY !== true && process.env.ZELARI_SERVE_HARNESS !== "1" ? (() => {
66795
+ emitEvent(protocolInfoEvent());
66796
+ return attachControlPlane({
66797
+ input: process.stdin,
66798
+ queue: controlQueue,
66799
+ emit: emitEvent,
66800
+ onCancel: () => {
66801
+ cancel();
66802
+ }
66803
+ });
66804
+ })() : void 0;
66805
+ const unregister = process.env.ZELARI_SERVE_HARNESS === "1" ? registerLiveTurnControl({
66806
+ queue: controlQueue,
66807
+ cancel
66808
+ }) : void 0;
66809
+ if (unregister) {
66810
+ const appliedBoundary = {
66811
+ steer: "turn-end",
66812
+ follow_up: "run-end",
66813
+ cancel: "cancel"
66814
+ };
66815
+ controlQueue.onDrained = (events) => {
66816
+ for (const event of events) {
66817
+ emitEvent(
66818
+ controlAppliedEvent(
66819
+ event.id,
66820
+ event.type,
66821
+ appliedBoundary[event.type] ?? "unknown"
66822
+ )
66823
+ );
66824
+ }
66825
+ };
66826
+ }
66827
+ return {
66828
+ signal: abort.signal,
66829
+ cancel,
66830
+ dispose() {
66831
+ controlPlane?.finalize();
66832
+ controlPlane?.dispose();
66833
+ unregister?.();
66834
+ }
66835
+ };
66659
66836
  }
66660
- function registerLiveTurnControl(control) {
66661
- const store6 = dispatchContext.getStore();
66662
- if (!store6) return void 0;
66663
- const registered = { ...control, token: store6.token };
66664
- liveTurns.set(store6.sessionId, registered);
66665
- return () => {
66666
- if (liveTurns.get(store6.sessionId) === registered) {
66667
- liveTurns.delete(store6.sessionId);
66837
+ var init_liveTurnAbort = __esm({
66838
+ "src/cli/headless/liveTurnAbort.ts"() {
66839
+ "use strict";
66840
+ init_runtime();
66841
+ init_sessionControl();
66842
+ init_controlBridge();
66843
+ init_protocol2();
66844
+ init_headless();
66845
+ }
66846
+ });
66847
+
66848
+ // src/cli/headless/policyGate.ts
66849
+ import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
66850
+ import { randomUUID as randomUUID9 } from "node:crypto";
66851
+ function checkStrictPolicyLoad(root, opts) {
66852
+ if (isPolicyEngineDisabled()) return { blocked: false, warnings: [] };
66853
+ try {
66854
+ const set2 = loadPolicySet(root, opts);
66855
+ return { blocked: false, warnings: set2.warnings };
66856
+ } catch (err) {
66857
+ if (!(err instanceof PolicyLoadError)) throw err;
66858
+ const file2 = isAbsolute5(err.file) ? err.file : resolve7(root, err.file);
66859
+ return {
66860
+ blocked: true,
66861
+ warnings: [],
66862
+ block: {
66863
+ reason: POLICY_LOAD_BLOCK_REASON,
66864
+ exitCode: POLICY_LOAD_EXIT_CODE,
66865
+ code: err.code,
66866
+ file: file2,
66867
+ ...err.message ? { detail: err.message } : {}
66868
+ }
66869
+ };
66870
+ }
66871
+ }
66872
+ function reportPolicyLoadBlocked(block, output) {
66873
+ const where = `${block.file}${block.detail ? ` \u2014 ${block.detail}` : ""}`;
66874
+ process.stderr.write(`[zelari-code --headless] ${block.reason}: ${where}
66875
+ `);
66876
+ if (output === "json") {
66877
+ emitEvent({
66878
+ type: "error",
66879
+ severity: "fatal",
66880
+ message: `${block.reason}: ${where}`,
66881
+ code: block.reason
66882
+ });
66883
+ }
66884
+ }
66885
+ async function recordPolicyLoadBlockedOnSpine(block, opts = {}) {
66886
+ try {
66887
+ const { openHeadlessSpine: openHeadlessSpine2 } = await Promise.resolve().then(() => (init_headlessSpine(), headlessSpine_exports));
66888
+ const sessionId2 = opts.resumeSessionId ?? randomUUID9();
66889
+ const spine = await openHeadlessSpine2({
66890
+ sessionId: sessionId2,
66891
+ ...opts.mode ? { mode: opts.mode } : {},
66892
+ ...opts.profile ? { profile: opts.profile } : {},
66893
+ workspace: opts.workspace ?? process.cwd()
66894
+ });
66895
+ if (opts.mode === "zelari") {
66896
+ spine.missionPhase("dispatch", block.reason);
66668
66897
  }
66898
+ spine.note(block.reason, {
66899
+ code: block.code,
66900
+ file: block.file,
66901
+ exitCode: block.exitCode,
66902
+ ...block.detail ? { detail: block.detail } : {}
66903
+ });
66904
+ await spine.close("error");
66905
+ } catch {
66906
+ }
66907
+ }
66908
+ var init_policyGate = __esm({
66909
+ "src/cli/headless/policyGate.ts"() {
66910
+ "use strict";
66911
+ init_policyEngine();
66912
+ init_policyLoadMode();
66913
+ init_headless();
66914
+ }
66915
+ });
66916
+
66917
+ // src/cli/kraken/verifierResolution.ts
66918
+ function verifierOverrideToModelSelection(override) {
66919
+ if (override && typeof override.provider === "string" && typeof override.model === "string" && override.provider.trim().length > 0 && override.model.trim().length > 0) {
66920
+ return {
66921
+ mode: "fixed",
66922
+ provider: override.provider.trim(),
66923
+ model: override.model.trim()
66924
+ };
66925
+ }
66926
+ return { mode: "inherit" };
66927
+ }
66928
+ function loadVerifierModelSelection() {
66929
+ return verifierOverrideToModelSelection(getKrakenVerifierOverride());
66930
+ }
66931
+ var init_verifierResolution = __esm({
66932
+ "src/cli/kraken/verifierResolution.ts"() {
66933
+ "use strict";
66934
+ init_providerConfig();
66935
+ }
66936
+ });
66937
+
66938
+ // src/cli/kraken/verifierLifecycle.ts
66939
+ function verifierReviewEnabled(selection = loadVerifierModelSelection(), env = process.env) {
66940
+ const v = env.ZELARI_VERIFIER_REVIEW?.toLowerCase();
66941
+ if (v === "0" || v === "false" || v === "off") return false;
66942
+ if (v === "1" || v === "true" || v === "on") return true;
66943
+ return selection.mode === "fixed";
66944
+ }
66945
+ function makeVerifierCallModel(loadStream, identity, timeoutMs2 = 12e4) {
66946
+ return async ({ system, user }) => {
66947
+ const stream = await loadStream(identity.provider, identity.model);
66948
+ if (!stream) {
66949
+ throw new Error(`no provider config for verifier "${identity.provider}"`);
66950
+ }
66951
+ const { text } = await collectProviderText(stream, {
66952
+ messages: [
66953
+ { role: "system", content: system },
66954
+ { role: "user", content: user }
66955
+ ],
66956
+ model: identity.model,
66957
+ provider: identity.provider,
66958
+ tools: [],
66959
+ signal: AbortSignal.timeout(timeoutMs2)
66960
+ });
66961
+ return { text, provider: identity.provider, model: identity.model };
66669
66962
  };
66670
66963
  }
66671
- function getLiveTurnControl(sessionId2) {
66672
- return liveTurns.get(sessionId2);
66964
+ function isTestEvidenceCriterion(criterionId2) {
66965
+ const id3 = criterionId2.toLowerCase();
66966
+ return ["test", "typecheck", "build", "lint"].some((k) => id3.includes(k));
66673
66967
  }
66674
- function clearSessionTurnControl(sessionId2) {
66675
- const store6 = dispatchContext.getStore();
66676
- if (!store6 || store6.sessionId !== sessionId2) return;
66677
- const registered = liveTurns.get(sessionId2);
66678
- if (registered && registered.token === store6.token) {
66679
- liveTurns.delete(sessionId2);
66968
+ function extractTestOutputExcerpt(results, maxChars = 4e3) {
66969
+ const lines = [];
66970
+ for (const r of results) {
66971
+ if (!isTestEvidenceCriterion(r.criterionId)) continue;
66972
+ lines.push([r.criterionId, r.status, r.detail].filter(Boolean).join(" \u2014 "));
66680
66973
  }
66974
+ if (lines.length === 0) return "";
66975
+ return lines.join("\n").slice(0, maxChars);
66681
66976
  }
66682
- var dispatchContext, liveTurns;
66683
- var init_sessionControl = __esm({
66684
- "src/cli/serve/sessionControl.ts"() {
66977
+ async function buildBlindReviewInput(evaluation, deps) {
66978
+ const results = evaluation.results ?? [];
66979
+ const passed = results.filter((r) => r.status === "pass").length;
66980
+ const verdict = evaluation.evaluation?.verdict ?? "UNKNOWN";
66981
+ const summary = `Kraken BUILD turn \u2014 deterministic evidence: ${passed}/${results.length} criteria pass, completion verdict ${verdict}.`;
66982
+ const task = deps.task?.trim();
66983
+ const testOutputExcerpt = extractTestOutputExcerpt(results);
66984
+ let diffSummary;
66985
+ try {
66986
+ const res = await (deps.getDiff ?? getWorkingDiff)({
66987
+ cwd: deps.cwd ?? process.cwd(),
66988
+ maxChars: 8e3,
66989
+ staged: true
66990
+ });
66991
+ if (res && !res.empty && res.diff) diffSummary = res.diff;
66992
+ } catch {
66993
+ }
66994
+ return {
66995
+ ...task ? { task } : {},
66996
+ summary,
66997
+ ...diffSummary !== void 0 ? { diffSummary } : {},
66998
+ ...testOutputExcerpt ? { testOutputExcerpt } : {},
66999
+ results
67000
+ };
67001
+ }
67002
+ async function runAdvisoryVerifierReview(evaluation, deps = {}) {
67003
+ if (!evaluation.evaluation || !evaluation.results) return null;
67004
+ const env = deps.env ?? process.env;
67005
+ const selection = deps.selection ?? loadVerifierModelSelection();
67006
+ if (!verifierReviewEnabled(selection, env)) return null;
67007
+ const risk = deps.risk ?? activeRisk(env);
67008
+ if (risk === "low") return null;
67009
+ const route = resolveVerifierRouting(
67010
+ selection.mode === "fixed" ? { provider: selection.provider, model: selection.model } : null,
67011
+ risk,
67012
+ {
67013
+ selectionMode: selection.mode === "fixed" ? "fixed" : "inherit",
67014
+ session: deps.session ?? null,
67015
+ familyCandidates: deps.familyCandidates,
67016
+ env
67017
+ }
67018
+ );
67019
+ const reviewers = route.reviewers;
67020
+ let callModel = deps.callModel;
67021
+ if (reviewers.length === 0 || !callModel && !deps.loadStream) return null;
67022
+ const blind = await buildBlindReviewInput(evaluation, deps);
67023
+ const reviews = [];
67024
+ for (const reviewer of reviewers) {
67025
+ const call = callModel ?? makeVerifierCallModel(deps.loadStream, reviewer.identity, deps.timeoutMs);
67026
+ const service = new VerifierService({
67027
+ callModel: call,
67028
+ config: {
67029
+ enabled: true,
67030
+ model: selection,
67031
+ progressScoring: false,
67032
+ bon: { enabled: false, n: 3 }
67033
+ },
67034
+ emit: deps.emit,
67035
+ env
67036
+ });
67037
+ reviews.push(await service.reviewCompletion({ ...blind, session: deps.session }));
67038
+ }
67039
+ const review = reviews.length > 1 ? mergeVerifierVerdicts(reviews) : reviews[0];
67040
+ evaluation.review = review;
67041
+ if (reviews.length > 1 && risk === "critical") {
67042
+ evaluation.reviewDivergence = divergenceFromReviews(
67043
+ reviews,
67044
+ reviewers.map((r) => ({ family: r.family, role: r.role }))
67045
+ );
67046
+ }
67047
+ return review;
67048
+ }
67049
+ var init_verifierLifecycle = __esm({
67050
+ "src/cli/kraken/verifierLifecycle.ts"() {
66685
67051
  "use strict";
66686
- dispatchContext = new AsyncLocalStorage();
66687
- liveTurns = /* @__PURE__ */ new Map();
67052
+ init_verification();
67053
+ init_krakenSelectTool();
67054
+ init_gitOps();
67055
+ init_verifierResolution();
67056
+ init_verifierRouting();
66688
67057
  }
66689
67058
  });
66690
67059
 
66691
67060
  // src/cli/extensions/sandboxedFs.ts
66692
67061
  import { promises as fsp } from "node:fs";
66693
- import path89 from "node:path";
67062
+ import path90 from "node:path";
66694
67063
  function errText(prefix, p3, err) {
66695
67064
  const msg = err instanceof Error ? err.message : String(err);
66696
67065
  return `[extension-fs] ${prefix} "${p3}": ${msg}`;
66697
67066
  }
66698
67067
  function bindSandboxedFs(root) {
66699
- const resolvedRoot = path89.resolve(root);
67068
+ const resolvedRoot = path90.resolve(root);
66700
67069
  return {
66701
67070
  root: resolvedRoot,
66702
67071
  async readFile(relativePath) {
@@ -66712,7 +67081,7 @@ function bindSandboxedFs(root) {
66712
67081
  try {
66713
67082
  const target = resolveSandboxedPath(relativePath, { root: resolvedRoot });
66714
67083
  verifyContainment(target, { root: resolvedRoot });
66715
- await fsp.mkdir(path89.dirname(target), { recursive: true });
67084
+ await fsp.mkdir(path90.dirname(target), { recursive: true });
66716
67085
  await fsp.writeFile(target, data, "utf8");
66717
67086
  return typedOk({ path: target });
66718
67087
  } catch (err) {
@@ -66897,7 +67266,7 @@ var init_loader = __esm({
66897
67266
 
66898
67267
  // src/cli/headless/runOneTurn.ts
66899
67268
  import { promises as fs44 } from "node:fs";
66900
- import path90 from "node:path";
67269
+ import path91 from "node:path";
66901
67270
  function planModeFromOpts(opts) {
66902
67271
  return (opts.phase ?? "build") === "plan";
66903
67272
  }
@@ -67046,6 +67415,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
67046
67415
  // an interactive approval (permission.request over NDJSON) instead of
67047
67416
  // the fail-closed typedErr. Absent handler ⇒ unchanged fail-closed.
67048
67417
  ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
67418
+ ...opts.onAskUser ? { onAskUser: opts.onAskUser } : {},
67049
67419
  permissionPolicy: defaultPermissionPolicy2(),
67050
67420
  ...nativeMemory ? { memoryService: nativeMemory } : {},
67051
67421
  memoryAutoWrite,
@@ -67272,7 +67642,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
67272
67642
  let finalReason = "completed";
67273
67643
  let exitCode = 0;
67274
67644
  const textBuffer = [];
67275
- const scrub = createStreamScrubber2();
67645
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
67276
67646
  try {
67277
67647
  for await (const event of harness.run()) {
67278
67648
  progressRuntime.observe(event);
@@ -67463,7 +67833,7 @@ async function runOneTurn(opts, provider, model, providerStream, extras) {
67463
67833
  if (json3) {
67464
67834
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
67465
67835
  else {
67466
- await fs44.mkdir(path90.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
67836
+ await fs44.mkdir(path91.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
67467
67837
  await fs44.writeFile(opts.exportSessionPath, json3, "utf8");
67468
67838
  }
67469
67839
  }
@@ -68442,9 +68812,9 @@ __export(triggerLock_exports, {
68442
68812
  releaseLock: () => releaseLock
68443
68813
  });
68444
68814
  import { promises as fs45 } from "node:fs";
68445
- import * as path91 from "node:path";
68815
+ import * as path92 from "node:path";
68446
68816
  function lockPath(projectRoot) {
68447
- return path91.join(projectRoot, ".zelari", "trigger.lock");
68817
+ return path92.join(projectRoot, ".zelari", "trigger.lock");
68448
68818
  }
68449
68819
  function isPidAlive(pid) {
68450
68820
  try {
@@ -68457,7 +68827,7 @@ function isPidAlive(pid) {
68457
68827
  }
68458
68828
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
68459
68829
  const lp = lockPath(projectRoot);
68460
- const dir = path91.dirname(lp);
68830
+ const dir = path92.dirname(lp);
68461
68831
  await fs45.mkdir(dir, { recursive: true });
68462
68832
  try {
68463
68833
  const raw = await fs45.readFile(lp, "utf8");
@@ -68489,7 +68859,7 @@ var init_triggerLock = __esm({
68489
68859
 
68490
68860
  // src/cli/runHeadless.ts
68491
68861
  import { promises as fs46 } from "node:fs";
68492
- import path92 from "node:path";
68862
+ import path93 from "node:path";
68493
68863
  import { randomUUID as randomUUID10 } from "node:crypto";
68494
68864
  async function runHeadless(opts) {
68495
68865
  resetTaskSpawnCount();
@@ -68733,7 +69103,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68733
69103
  try {
68734
69104
  let preflightGraph;
68735
69105
  if (opts.runPlan && opts.runPlan.trim() !== "") {
68736
- const planPath = path92.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
69106
+ const planPath = path93.join(cwd, ".zelari", "radio", `plan-${opts.runPlan}.json`);
68737
69107
  log(`loading pre-flight plan: ${planPath}`);
68738
69108
  let raw;
68739
69109
  try {
@@ -68776,8 +69146,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68776
69146
  log(formatKrakenGraphAscii2(graph));
68777
69147
  if (opts.planOnly) {
68778
69148
  const planId = randomUUID10();
68779
- const planDir = path92.join(cwd, ".zelari", "radio");
68780
- const planPath = path92.join(planDir, `plan-${planId}.json`);
69149
+ const planDir = path93.join(cwd, ".zelari", "radio");
69150
+ const planPath = path93.join(planDir, `plan-${planId}.json`);
68781
69151
  await fs46.mkdir(planDir, { recursive: true });
68782
69152
  await fs46.writeFile(
68783
69153
  planPath,
@@ -68814,6 +69184,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
68814
69184
  // ask without a UI fails closed), so this intersection actually
68815
69185
  // bites: tentacles can never exceed the preset.
68816
69186
  parentPolicy: defaultPermissionPolicy2(),
69187
+ ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
68817
69188
  // Anchor every tentacle to the SAME provider/model this run
68818
69189
  // resolved (Desktop's selector, or --provider/--model), instead
68819
69190
  // of the persisted provider.json default the factory falls back
@@ -68930,6 +69301,8 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
68930
69301
  planMode,
68931
69302
  ...extras?.lspProvider ? { lspProvider: extras.lspProvider } : {},
68932
69303
  permissionPolicy: defaultPermissionPolicy2(),
69304
+ ...opts?.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
69305
+ ...opts?.onAskUser ? { onAskUser: opts.onAskUser } : {},
68933
69306
  ...memoryService ? { memoryService } : {},
68934
69307
  memoryAutoWrite
68935
69308
  });
@@ -68949,6 +69322,14 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
68949
69322
  return { toolRegistry, workspaceCtx: realCtx };
68950
69323
  }
68951
69324
  async function runHeadlessCouncil(opts, provider, model, providerStream, extras) {
69325
+ const live = attachHeadlessLiveCancel({ output: opts.output });
69326
+ try {
69327
+ return await runHeadlessCouncilBody(opts, provider, model, providerStream, extras, live.signal);
69328
+ } finally {
69329
+ live.dispose();
69330
+ }
69331
+ }
69332
+ async function runHeadlessCouncilBody(opts, provider, model, providerStream, extras, signal) {
68952
69333
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
68953
69334
  const sessionId2 = crypto.randomUUID();
68954
69335
  const cwd = resolveHeadlessCwd(opts);
@@ -69024,7 +69405,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69024
69405
  const effectiveTask = buildCouncilTaskWithHistory(opts.task, historySeed);
69025
69406
  if (opts.task) spine.userMessage(effectiveTask);
69026
69407
  let exitCode = 0;
69027
- const scrub = createStreamScrubber2();
69408
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
69028
69409
  let lastAssistantText = "";
69029
69410
  let currentAssistantText = "";
69030
69411
  try {
@@ -69059,6 +69440,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69059
69440
  tools: toolRegistry,
69060
69441
  feedbackStore,
69061
69442
  runMode: councilRunMode,
69443
+ signal,
69062
69444
  // t23: an auto-SELECTED council runs the LITE tier (3 members) unless
69063
69445
  // ZELARI_COUNCIL_TIER / ZELARI_COUNCIL_SIZE explicitly opt into full.
69064
69446
  ...opts.orchestrationDecision?.strategy === "council" && process.env["ZELARI_COUNCIL_TIER"] === void 0 && process.env["ZELARI_COUNCIL_SIZE"] === void 0 ? { councilSize: COUNCIL_TIER_SIZES.lite } : {},
@@ -69117,7 +69499,9 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69117
69499
  return 2;
69118
69500
  }
69119
69501
  try {
69120
- await spine.close(exitCode === 0 ? "completed" : "error");
69502
+ await spine.close(
69503
+ signal.aborted ? "stopped" : exitCode === 0 ? "completed" : "error"
69504
+ );
69121
69505
  } catch {
69122
69506
  }
69123
69507
  try {
@@ -69129,7 +69513,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69129
69513
  at: (/* @__PURE__ */ new Date()).toISOString(),
69130
69514
  mode: "shadow",
69131
69515
  taskClass: classifyTask2({ prompt: effectiveTask }).taskClass,
69132
- verdict: exitCode === 0 ? "PASS" : exitCode === 3 ? "FAIL" : "UNKNOWN"
69516
+ verdict: signal.aborted ? "UNKNOWN" : exitCode === 0 ? "PASS" : exitCode === 3 ? "FAIL" : "UNKNOWN"
69133
69517
  });
69134
69518
  }
69135
69519
  } catch {
@@ -69141,14 +69525,14 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69141
69525
  if (json3) {
69142
69526
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
69143
69527
  else {
69144
- await fs46.mkdir(path92.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
69528
+ await fs46.mkdir(path93.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
69145
69529
  await fs46.writeFile(opts.exportSessionPath, json3, "utf8");
69146
69530
  }
69147
69531
  }
69148
69532
  } catch {
69149
69533
  }
69150
69534
  }
69151
- if (nativeMemory && memoryAutoWrite && lastAssistantText) {
69535
+ if (nativeMemory && memoryAutoWrite && lastAssistantText && !signal.aborted) {
69152
69536
  try {
69153
69537
  await nativeMemory.remember({
69154
69538
  kind: councilRunMode === "design-phase" ? "decision" : "outcome",
@@ -69175,6 +69559,14 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
69175
69559
  return exitCode;
69176
69560
  }
69177
69561
  async function runHeadlessZelari(opts, provider, model, providerStream, extras) {
69562
+ const live = attachHeadlessLiveCancel({ output: opts.output });
69563
+ try {
69564
+ return await runHeadlessZelariBody(opts, provider, model, providerStream, extras, live.signal);
69565
+ } finally {
69566
+ live.dispose();
69567
+ }
69568
+ }
69569
+ async function runHeadlessZelariBody(opts, provider, model, providerStream, extras, signal) {
69178
69570
  const projectRoot = resolveHeadlessCwd(opts);
69179
69571
  const sessionId2 = opts.resumeSessionId ?? crypto.randomUUID();
69180
69572
  const spine = await openHeadlessSpine({
@@ -69285,6 +69677,7 @@ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMv
69285
69677
  memory,
69286
69678
  emit,
69287
69679
  buildViaAgent,
69680
+ signal,
69288
69681
  onMissionPhase: (phase2, note) => spine.missionPhase(phase2, note),
69289
69682
  onMissionProgress: (advice, iteration) => spine.missionProgress({
69290
69683
  recommendation: advice.recommendation,
@@ -69310,7 +69703,7 @@ ${ragContext}` : slicePrompt;
69310
69703
  let writeCount = 0;
69311
69704
  let chairmanErrored = false;
69312
69705
  let membersCompleted = 0;
69313
- const scrub = createStreamScrubber2();
69706
+ const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
69314
69707
  const { composeProjectContext: composeProjectContext3 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
69315
69708
  const { loadDurableContext: loadDurableContext3 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
69316
69709
  const memOnly = ragContext?.trim() ? ragContext : void 0;
@@ -69333,6 +69726,7 @@ ${ragContext}` : slicePrompt;
69333
69726
  tools: toolRegistry,
69334
69727
  feedbackStore,
69335
69728
  runMode: effectiveRunMode,
69729
+ signal,
69336
69730
  maxToolCallsChairman: chairmanBudget,
69337
69731
  ...implementerRetry ? { skipSpecialists: true } : {},
69338
69732
  workspaceContext: composed2.workspaceContext,
@@ -69380,11 +69774,20 @@ ${ragContext}` : slicePrompt;
69380
69774
  }
69381
69775
  let completionOk = false;
69382
69776
  let degraded = false;
69777
+ if (signal.aborted) {
69778
+ return {
69779
+ completionOk: false,
69780
+ ran: membersCompleted > 0 || synthesisText.length > 0,
69781
+ synthesisText: synthesisText || void 0,
69782
+ writeCount,
69783
+ degraded: true
69784
+ };
69785
+ }
69383
69786
  try {
69384
69787
  const { detectDegradedRun: detectDegradedRun3 } = await Promise.resolve().then(() => (init_council(), council_exports));
69385
69788
  const d = detectDegradedRun3({
69386
69789
  chairmanErrored,
69387
- councilAborted: false,
69790
+ councilAborted: signal.aborted,
69388
69791
  luciferWriteCount: writeCount,
69389
69792
  synthesisText,
69390
69793
  runMode: effectiveRunMode
@@ -69451,7 +69854,9 @@ ${ragContext}` : slicePrompt;
69451
69854
  const { registry: agentRegistry } = createBuiltinToolRegistry2({
69452
69855
  root: projectRoot,
69453
69856
  planMode: false,
69454
- permissionPolicy: defaultPermissionPolicy2()
69857
+ permissionPolicy: defaultPermissionPolicy2(),
69858
+ ...opts.onPermissionAsk ? { onPermissionAsk: opts.onPermissionAsk } : {},
69859
+ ...opts.onAskUser ? { onAskUser: opts.onAskUser } : {}
69455
69860
  });
69456
69861
  await registerHeadlessMcp(agentRegistry, opts);
69457
69862
  const durableState = await loadDurableContext2(projectRoot);
@@ -69518,7 +69923,10 @@ ${ragContext}` : slicePrompt;
69518
69923
  }
69519
69924
  });
69520
69925
  if (state3.status === "error") exitCode = exitCode || 3;
69521
- else if (state3.status === "success") {
69926
+ else if (state3.status === "cancelled") {
69927
+ exitCode = 0;
69928
+ spine.missionPhase("verification", "mission-cancelled");
69929
+ } else if (state3.status === "success") {
69522
69930
  const missionGate = await evaluateStrictBuildGate("build", {
69523
69931
  emit: (input) => spine.appendEvent(input),
69524
69932
  surface: "mission",
@@ -69556,7 +69964,8 @@ ${ragContext}` : slicePrompt;
69556
69964
  }
69557
69965
  await memory.close().catch(() => void 0);
69558
69966
  try {
69559
- if (exitCode === 0) await spine.close("completed");
69967
+ if (signal.aborted) await spine.close("stopped");
69968
+ else if (exitCode === 0) await spine.close("completed");
69560
69969
  else await spine.close(exitCode === 2 ? "error" : "stopped");
69561
69970
  } catch {
69562
69971
  }
@@ -69567,7 +69976,7 @@ ${ragContext}` : slicePrompt;
69567
69976
  if (json3) {
69568
69977
  if (opts.exportSessionPath === "-") process.stdout.write(json3 + "\n");
69569
69978
  else {
69570
- await fs46.mkdir(path92.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
69979
+ await fs46.mkdir(path93.dirname(opts.exportSessionPath), { recursive: true }).catch(() => void 0);
69571
69980
  await fs46.writeFile(opts.exportSessionPath, json3, "utf8");
69572
69981
  }
69573
69982
  }
@@ -69603,6 +70012,7 @@ var init_runHeadless = __esm({
69603
70012
  init_metrics3();
69604
70013
  init_headlessSpine();
69605
70014
  init_harnessStateEmit();
70015
+ init_liveTurnAbort();
69606
70016
  init_policyGate();
69607
70017
  init_policyLoadMode();
69608
70018
  init_runOneTurn();
@@ -70179,7 +70589,7 @@ function upsertSkill(opts) {
70179
70589
  }
70180
70590
  dir = getProjectSkillsDir(root);
70181
70591
  }
70182
- const path99 = skillFilePath(dir, name);
70592
+ const path100 = skillFilePath(dir, name);
70183
70593
  const content = serializeSkillMd({
70184
70594
  name,
70185
70595
  description,
@@ -70188,13 +70598,13 @@ function upsertSkill(opts) {
70188
70598
  tools: opts.tools,
70189
70599
  cost: opts.cost
70190
70600
  });
70191
- const parsed = parseSkillMd(content, path99);
70601
+ const parsed = parseSkillMd(content, path100);
70192
70602
  if (!parsed) {
70193
70603
  return { ok: false, error: "Generated SKILL.md failed validation" };
70194
70604
  }
70195
- mkdirSync23(dirname13(path99), { recursive: true });
70196
- writeFileSync25(path99, content, "utf8");
70197
- return { ok: true, path: path99 };
70605
+ mkdirSync23(dirname13(path100), { recursive: true });
70606
+ writeFileSync25(path100, content, "utf8");
70607
+ return { ok: true, path: path100 };
70198
70608
  }
70199
70609
  function removeSkill(opts) {
70200
70610
  const name = opts.name.trim().toLowerCase();
@@ -70212,8 +70622,8 @@ function removeSkill(opts) {
70212
70622
  dir = getProjectSkillsDir(root);
70213
70623
  }
70214
70624
  const skillDir = join46(dir, name);
70215
- const path99 = skillFilePath(dir, name);
70216
- if (!existsSync59(path99) && !existsSync59(skillDir)) {
70625
+ const path100 = skillFilePath(dir, name);
70626
+ if (!existsSync59(path100) && !existsSync59(skillDir)) {
70217
70627
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
70218
70628
  }
70219
70629
  try {
@@ -70224,7 +70634,7 @@ function removeSkill(opts) {
70224
70634
  error: err instanceof Error ? err.message : String(err)
70225
70635
  };
70226
70636
  }
70227
- return { ok: true, path: path99 };
70637
+ return { ok: true, path: path100 };
70228
70638
  }
70229
70639
  var NAME_RE, BUILTIN_SKILL_MODULES, builtinsLoaded;
70230
70640
  var init_skillConfigIo = __esm({
@@ -70345,7 +70755,7 @@ var init_jsonApi = __esm({
70345
70755
  });
70346
70756
 
70347
70757
  // src/cli/memory/mcpAdapter.ts
70348
- import * as path93 from "node:path";
70758
+ import * as path94 from "node:path";
70349
70759
  var id2, projectId, source, SearchSchema, AddSchema, LinkSchema, RetractSchema, MEMORY_MCP_TOOLS, MemoryMcpAdapter;
70350
70760
  var init_mcpAdapter = __esm({
70351
70761
  "src/cli/memory/mcpAdapter.ts"() {
@@ -70515,8 +70925,8 @@ var init_mcpAdapter = __esm({
70515
70925
  this.takeWrite();
70516
70926
  const externalFile = args.source?.file;
70517
70927
  if (externalFile) {
70518
- const normalized = path93.normalize(externalFile);
70519
- if (path93.isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${path93.sep}`)) {
70928
+ const normalized = path94.normalize(externalFile);
70929
+ if (path94.isAbsolute(normalized) || normalized === ".." || normalized.startsWith(`..${path94.sep}`)) {
70520
70930
  throw new Error("source.file must be project-relative and cannot escape the project");
70521
70931
  }
70522
70932
  }
@@ -71043,12 +71453,12 @@ function ensureHome() {
71043
71453
  }
71044
71454
  }
71045
71455
  function loadCompanionConfig() {
71046
- const path99 = getCompanionConfigPath();
71047
- if (!existsSync60(path99)) {
71456
+ const path100 = getCompanionConfigPath();
71457
+ if (!existsSync60(path100)) {
71048
71458
  return { projects: [] };
71049
71459
  }
71050
71460
  try {
71051
- const raw = JSON.parse(readFileSync45(path99, "utf8"));
71461
+ const raw = JSON.parse(readFileSync45(path100, "utf8"));
71052
71462
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
71053
71463
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
71054
71464
  ).map((p3) => ({
@@ -71086,16 +71496,16 @@ function loadOrCreateToken(explicit) {
71086
71496
  return { token: explicit.trim(), created: false };
71087
71497
  }
71088
71498
  ensureHome();
71089
- const path99 = getCompanionTokenPath();
71090
- if (existsSync60(path99)) {
71091
- const t = readFileSync45(path99, "utf8").trim();
71499
+ const path100 = getCompanionTokenPath();
71500
+ if (existsSync60(path100)) {
71501
+ const t = readFileSync45(path100, "utf8").trim();
71092
71502
  if (t) return { token: t, created: false };
71093
71503
  }
71094
71504
  const token = randomBytes7(24).toString("base64url");
71095
- writeFileSync26(path99, token + "\n", "utf8");
71505
+ writeFileSync26(path100, token + "\n", "utf8");
71096
71506
  try {
71097
71507
  const fs48 = __require("node:fs");
71098
- fs48.chmodSync?.(path99, 384);
71508
+ fs48.chmodSync?.(path100, 384);
71099
71509
  } catch {
71100
71510
  }
71101
71511
  return { token, created: true };
@@ -71120,17 +71530,17 @@ function mergeProjects(cfg, extraPaths) {
71120
71530
  byId.set(p3.id, p3);
71121
71531
  }
71122
71532
  for (const raw of extraPaths) {
71123
- const path99 = raw.trim();
71124
- if (!path99) continue;
71125
- let id3 = slugFromPath(path99);
71533
+ const path100 = raw.trim();
71534
+ if (!path100) continue;
71535
+ let id3 = slugFromPath(path100);
71126
71536
  let n = 2;
71127
- while (byId.has(id3) && byId.get(id3).path !== path99) {
71128
- id3 = `${slugFromPath(path99)}-${n++}`;
71537
+ while (byId.has(id3) && byId.get(id3).path !== path100) {
71538
+ id3 = `${slugFromPath(path100)}-${n++}`;
71129
71539
  }
71130
71540
  byId.set(id3, {
71131
71541
  id: id3,
71132
- name: slugFromPath(path99),
71133
- path: path99
71542
+ name: slugFromPath(path100),
71543
+ path: path100
71134
71544
  });
71135
71545
  }
71136
71546
  return [...byId.values()];
@@ -71190,38 +71600,61 @@ function applyTurnPermissionPreset(input) {
71190
71600
  process.env[PRESET_ENV] = value;
71191
71601
  return true;
71192
71602
  }
71603
+ function isPermissionDecision(value) {
71604
+ return typeof value === "string" && PERMISSION_DECISIONS.includes(value);
71605
+ }
71193
71606
  function createServePermissionBridge(write, timeoutMs2 = 12e4) {
71194
71607
  const pending = /* @__PURE__ */ new Map();
71195
71608
  let seq = 0;
71196
- const settle = (requestId, decision) => {
71609
+ const settle = (requestId, decision, timedOut = false) => {
71197
71610
  const entry = pending.get(requestId);
71198
71611
  if (!entry) return false;
71199
71612
  pending.delete(requestId);
71200
71613
  clearTimeout(entry.timer);
71614
+ write(
71615
+ JSON.stringify({
71616
+ type: "permission.settled",
71617
+ requestId,
71618
+ decision,
71619
+ ...timedOut ? { timedOut: true } : {}
71620
+ })
71621
+ );
71201
71622
  entry.resolve(decision);
71202
71623
  return true;
71203
71624
  };
71204
71625
  return {
71205
71626
  onPermissionAsk(payload) {
71206
71627
  const requestId = `perm-${Date.now()}-${++seq}`;
71628
+ const categories = payload.categories && payload.categories.length > 0 ? payload.categories : payload.category ? payload.category.split(",").map((c) => c.trim()).filter(Boolean) : [];
71207
71629
  return new Promise((resolve9) => {
71208
71630
  const timer = setTimeout(() => {
71209
- settle(requestId, "deny");
71631
+ settle(requestId, "deny", true);
71210
71632
  }, timeoutMs2);
71211
- pending.set(requestId, { resolve: resolve9, timer });
71633
+ pending.set(requestId, { resolve: resolve9, timer, payload });
71212
71634
  write(
71213
71635
  JSON.stringify({
71214
71636
  type: "permission.request",
71215
71637
  requestId,
71216
71638
  tool: payload.tool,
71217
71639
  category: payload.category,
71640
+ categories,
71218
71641
  ...payload.inputPreview !== void 0 ? { inputPreview: payload.inputPreview } : {},
71219
71642
  ...payload.reason !== void 0 ? { reason: payload.reason } : {}
71220
71643
  })
71221
71644
  );
71222
71645
  });
71223
71646
  },
71224
- respond: settle,
71647
+ respond: (requestId, decision) => settle(requestId, decision, false),
71648
+ releaseGranted() {
71649
+ let n = 0;
71650
+ for (const [id3, entry] of [...pending]) {
71651
+ 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) : [];
71652
+ if (isSessionGranted(entry.payload.tool, cats)) {
71653
+ if (settle(id3, "allow", false)) n += 1;
71654
+ }
71655
+ }
71656
+ return n;
71657
+ },
71225
71658
  pendingCount: () => pending.size
71226
71659
  };
71227
71660
  }
@@ -71233,8 +71666,11 @@ function servePermissionRespond(bridge, params) {
71233
71666
  if (typeof requestId !== "string" || requestId.length === 0) {
71234
71667
  return { accepted: false, reason: "permission.respond requires a non-empty string requestId" };
71235
71668
  }
71236
- if (decision !== "allow" && decision !== "deny") {
71237
- return { accepted: false, reason: "permission.respond decision must be 'allow' or 'deny'" };
71669
+ if (!isPermissionDecision(decision)) {
71670
+ return {
71671
+ accepted: false,
71672
+ reason: "permission.respond decision must be 'allow' | 'deny' | 'always-tool' | 'always-category'"
71673
+ };
71238
71674
  }
71239
71675
  return { accepted: bridge.respond(requestId, decision) };
71240
71676
  }
@@ -71244,24 +71680,110 @@ function asRegistryAskHandler(bridge) {
71244
71680
  const decision = await bridge.onPermissionAsk({
71245
71681
  tool: req.toolName,
71246
71682
  category: req.categories.join(",") || "other",
71683
+ categories: req.categories,
71247
71684
  reason,
71248
71685
  ...req.claims && req.claims.length > 0 ? { inputPreview: req.claims.map((c) => c.summary).join(" \xB7 ") } : {}
71249
71686
  });
71250
- return decision === "allow";
71687
+ if (decision === "deny") return false;
71688
+ if (decision === "always-tool") {
71689
+ grantSessionTool(req.toolName);
71690
+ bridge.releaseGranted();
71691
+ } else if (decision === "always-category") {
71692
+ for (const cat of req.categories) {
71693
+ grantSessionCategory(cat);
71694
+ }
71695
+ grantSessionTool(req.toolName);
71696
+ bridge.releaseGranted();
71697
+ }
71698
+ return true;
71251
71699
  };
71252
71700
  }
71253
- var SERVE_PERMISSION_PRESETS, PRESET_ENV;
71701
+ var SERVE_PERMISSION_PRESETS, PRESET_ENV, PERMISSION_DECISIONS;
71254
71702
  var init_permissionBridge = __esm({
71255
71703
  "src/cli/serve/permissionBridge.ts"() {
71256
71704
  "use strict";
71705
+ init_toolPermissions();
71257
71706
  SERVE_PERMISSION_PRESETS = ["standard", "strict", "yolo"];
71258
71707
  PRESET_ENV = "ZELARI_PERMISSION_PRESET";
71708
+ PERMISSION_DECISIONS = [
71709
+ "allow",
71710
+ "deny",
71711
+ "always-tool",
71712
+ "always-category"
71713
+ ];
71714
+ }
71715
+ });
71716
+
71717
+ // src/cli/serve/askUserBridge.ts
71718
+ function createServeAskUserBridge(write, timeoutMs2 = askUserTimeoutMs()) {
71719
+ const pending = /* @__PURE__ */ new Map();
71720
+ let seq = 0;
71721
+ const settle = (requestId, answer, timedOut = false) => {
71722
+ const entry = pending.get(requestId);
71723
+ if (!entry) return false;
71724
+ pending.delete(requestId);
71725
+ clearTimeout(entry.timer);
71726
+ write(
71727
+ JSON.stringify({
71728
+ type: "ask_user.settled",
71729
+ requestId,
71730
+ answer,
71731
+ ...timedOut ? { timedOut: true } : {}
71732
+ })
71733
+ );
71734
+ entry.resolve(answer);
71735
+ return true;
71736
+ };
71737
+ return {
71738
+ onAskUser(req) {
71739
+ const question = req.question.trim();
71740
+ const choices = req.choices.map((c) => c.trim()).filter(Boolean);
71741
+ if (choices.length < 2) return Promise.resolve(null);
71742
+ const requestId = `ask-${Date.now()}-${++seq}`;
71743
+ return new Promise((resolve9) => {
71744
+ const timer = setTimeout(() => {
71745
+ settle(requestId, null, true);
71746
+ }, timeoutMs2);
71747
+ pending.set(requestId, { resolve: resolve9, timer });
71748
+ write(
71749
+ JSON.stringify({
71750
+ type: "ask_user.request",
71751
+ requestId,
71752
+ question,
71753
+ choices,
71754
+ ...req.context ? { context: req.context } : {}
71755
+ })
71756
+ );
71757
+ });
71758
+ },
71759
+ respond: (requestId, answer) => settle(requestId, answer, false),
71760
+ pendingCount: () => pending.size
71761
+ };
71762
+ }
71763
+ function serveAskUserRespond(bridge, params) {
71764
+ if (!params || typeof params !== "object") {
71765
+ return { accepted: false, reason: "ask_user.respond requires an object params" };
71766
+ }
71767
+ const { requestId, answer } = params;
71768
+ if (typeof requestId !== "string" || requestId.length === 0) {
71769
+ return { accepted: false, reason: "ask_user.respond requires a non-empty string requestId" };
71770
+ }
71771
+ if (answer !== null && typeof answer !== "string") {
71772
+ return { accepted: false, reason: "ask_user.respond answer must be a string or null" };
71773
+ }
71774
+ const text = typeof answer === "string" ? answer.trim() : null;
71775
+ return { accepted: bridge.respond(requestId, text && text.length > 0 ? text : null) };
71776
+ }
71777
+ var init_askUserBridge = __esm({
71778
+ "src/cli/serve/askUserBridge.ts"() {
71779
+ "use strict";
71780
+ init_askUserTimeout();
71259
71781
  }
71260
71782
  });
71261
71783
 
71262
71784
  // src/cli/serve/spineLockSweep.ts
71263
71785
  import { promises as fs47 } from "node:fs";
71264
- import path94 from "node:path";
71786
+ import path95 from "node:path";
71265
71787
  async function sweepOrphanSpineLocks(sessionsDir2, options = {}) {
71266
71788
  const dir = sessionsDir2 ?? resolveSessionsDir();
71267
71789
  const onSwept = options.onSwept ?? ((sessionId2, reason) => {
@@ -71279,7 +71801,7 @@ async function sweepOrphanSpineLocks(sessionsDir2, options = {}) {
71279
71801
  return result;
71280
71802
  }
71281
71803
  for (const sessionId2 of entries) {
71282
- const lockPath2 = path94.join(dir, sessionId2, "writer.lock");
71804
+ const lockPath2 = path95.join(dir, sessionId2, "writer.lock");
71283
71805
  try {
71284
71806
  const raw = await fs47.readFile(lockPath2, "utf-8");
71285
71807
  let lockInfo = {};
@@ -71372,7 +71894,7 @@ function resolveTurnLspProvider(services) {
71372
71894
  const candidate = services?.lspManager;
71373
71895
  return candidate instanceof LspManager ? candidate : void 0;
71374
71896
  }
71375
- function createCliRunTurn(onPermissionAsk) {
71897
+ function createCliRunTurn(onPermissionAsk, onAskUser) {
71376
71898
  let streamPromise = null;
71377
71899
  const ensureStream = () => {
71378
71900
  if (!streamPromise) {
@@ -71399,6 +71921,7 @@ function createCliRunTurn(onPermissionAsk) {
71399
71921
  const { provider, model, stream } = await ensureStream();
71400
71922
  const opts = bindHarnessTurnOptions(input, deps.session.workspaceRoot);
71401
71923
  if (onPermissionAsk) opts.onPermissionAsk = onPermissionAsk;
71924
+ if (onAskUser) opts.onAskUser = onAskUser;
71402
71925
  applyTurnPermissionPreset(input);
71403
71926
  const lspProvider = resolveTurnLspProvider(deps.services);
71404
71927
  const exitCode = await dispatchHeadlessTurn(
@@ -71434,6 +71957,7 @@ function startHarnessServer(options = {}) {
71434
71957
  write(JSON.stringify(envelope));
71435
71958
  };
71436
71959
  const permissionBridge = createServePermissionBridge(write);
71960
+ const askUserBridge = createServeAskUserBridge(write);
71437
71961
  const dispatch = async (req) => {
71438
71962
  if (typeof req.method !== "string") {
71439
71963
  return { id: req.id ?? null, ok: false, error: { code: "bad_request", message: "missing method" } };
@@ -71447,11 +71971,21 @@ function startHarnessServer(options = {}) {
71447
71971
  result: servePermissionRespond(permissionBridge, params)
71448
71972
  };
71449
71973
  }
71974
+ case "ask_user.respond": {
71975
+ return {
71976
+ id: req.id ?? null,
71977
+ ok: true,
71978
+ result: serveAskUserRespond(askUserBridge, params)
71979
+ };
71980
+ }
71450
71981
  case "session.create": {
71451
71982
  const root = typeof params.workspaceRoot === "string" ? params.workspaceRoot : process.cwd();
71452
71983
  const session = server.createSession({
71453
71984
  workspaceRoot: root,
71454
- runTurn: options.runTurn ?? createCliRunTurn(asRegistryAskHandler(permissionBridge))
71985
+ runTurn: options.runTurn ?? createCliRunTurn(
71986
+ asRegistryAskHandler(permissionBridge),
71987
+ askUserBridge.onAskUser
71988
+ )
71455
71989
  });
71456
71990
  return { id: req.id ?? null, ok: true, result: { sessionId: session.id, workspaceRoot: session.workspaceRoot } };
71457
71991
  }
@@ -71605,6 +72139,7 @@ var init_harnessServer = __esm({
71605
72139
  init_policyGate();
71606
72140
  init_policyLoadMode();
71607
72141
  init_permissionBridge();
72142
+ init_askUserBridge();
71608
72143
  init_spineLockSweep();
71609
72144
  }
71610
72145
  });
@@ -72263,9 +72798,9 @@ async function runCompanionServe(opts = {}) {
72263
72798
  return;
72264
72799
  }
72265
72800
  const url2 = parseUrl(req);
72266
- const path99 = url2.pathname.replace(/\/+$/, "") || "/";
72801
+ const path100 = url2.pathname.replace(/\/+$/, "") || "/";
72267
72802
  try {
72268
- if (req.method === "GET" && (path99 === "/health" || path99 === "/v1/health")) {
72803
+ if (req.method === "GET" && (path100 === "/health" || path100 === "/v1/health")) {
72269
72804
  sendJson2(res, 200, {
72270
72805
  ok: true,
72271
72806
  service: "zelari-companion",
@@ -72277,18 +72812,18 @@ async function runCompanionServe(opts = {}) {
72277
72812
  });
72278
72813
  return;
72279
72814
  }
72280
- if (path99.startsWith("/v1")) {
72815
+ if (path100.startsWith("/v1")) {
72281
72816
  if (!tokenMatches(token, getBearer(req))) {
72282
72817
  sendJson2(res, 401, { ok: false, error: "unauthorized" });
72283
72818
  return;
72284
72819
  }
72285
72820
  }
72286
- if (req.method === "GET" && path99 === "/v1/config") {
72821
+ if (req.method === "GET" && path100 === "/v1/config") {
72287
72822
  const snap = buildDesktopConfigSnapshot();
72288
72823
  sendJson2(res, 200, { ok: true, ...snap });
72289
72824
  return;
72290
72825
  }
72291
- if (req.method === "GET" && path99 === "/v1/projects") {
72826
+ if (req.method === "GET" && path100 === "/v1/projects") {
72292
72827
  sendJson2(res, 200, {
72293
72828
  ok: true,
72294
72829
  projects: projects.map((p3) => ({
@@ -72299,7 +72834,7 @@ async function runCompanionServe(opts = {}) {
72299
72834
  });
72300
72835
  return;
72301
72836
  }
72302
- if (req.method === "GET" && path99 === "/v1/runs") {
72837
+ if (req.method === "GET" && path100 === "/v1/runs") {
72303
72838
  sendJson2(res, 200, {
72304
72839
  ok: true,
72305
72840
  active: runs.getActive(),
@@ -72317,7 +72852,7 @@ async function runCompanionServe(opts = {}) {
72317
72852
  });
72318
72853
  return;
72319
72854
  }
72320
- if (req.method === "POST" && path99 === "/v1/runs") {
72855
+ if (req.method === "POST" && path100 === "/v1/runs") {
72321
72856
  const raw = await readBody(req);
72322
72857
  let body = {};
72323
72858
  try {
@@ -72365,7 +72900,7 @@ async function runCompanionServe(opts = {}) {
72365
72900
  });
72366
72901
  return;
72367
72902
  }
72368
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path99);
72903
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path100);
72369
72904
  if (req.method === "GET" && eventsMatch) {
72370
72905
  const runId = eventsMatch[1];
72371
72906
  const run = runs.getRun(runId);
@@ -72430,7 +72965,7 @@ async function runCompanionServe(opts = {}) {
72430
72965
  }, 500);
72431
72966
  return;
72432
72967
  }
72433
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path99);
72968
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path100);
72434
72969
  if (req.method === "POST" && cancelMatch) {
72435
72970
  const runId = cancelMatch[1];
72436
72971
  const result = runs.cancel(runId);
@@ -72441,7 +72976,7 @@ async function runCompanionServe(opts = {}) {
72441
72976
  sendJson2(res, 200, { ok: true, cancelled: runId });
72442
72977
  return;
72443
72978
  }
72444
- const steerMatch = /^\/v1\/runs\/([^/]+)\/steer$/.exec(path99);
72979
+ const steerMatch = /^\/v1\/runs\/([^/]+)\/steer$/.exec(path100);
72445
72980
  if (req.method === "POST" && steerMatch) {
72446
72981
  const runId = steerMatch[1];
72447
72982
  const raw = await readBody(req);
@@ -72610,11 +73145,11 @@ import { execSync as execSync2 } from "node:child_process";
72610
73145
  import { existsSync as existsSync62, readFileSync as readFileSync46, readlinkSync, statSync as statSync10 } from "node:fs";
72611
73146
  import { createRequire as createRequire3 } from "node:module";
72612
73147
  import { fileURLToPath as fileURLToPath3 } from "node:url";
72613
- import path95 from "node:path";
73148
+ import path96 from "node:path";
72614
73149
  function findPackageRoot(start) {
72615
73150
  let dir = start;
72616
73151
  for (let i = 0; i < 6; i += 1) {
72617
- const candidate = path95.join(dir, "package.json");
73152
+ const candidate = path96.join(dir, "package.json");
72618
73153
  if (existsSync62(candidate)) {
72619
73154
  try {
72620
73155
  const pkg = JSON.parse(readFileSync46(candidate, "utf8"));
@@ -72622,11 +73157,11 @@ function findPackageRoot(start) {
72622
73157
  } catch {
72623
73158
  }
72624
73159
  }
72625
- const parent = path95.dirname(dir);
73160
+ const parent = path96.dirname(dir);
72626
73161
  if (parent === dir) break;
72627
73162
  dir = parent;
72628
73163
  }
72629
- return path95.resolve(__dirname3, "..", "..", "..");
73164
+ return path96.resolve(__dirname3, "..", "..", "..");
72630
73165
  }
72631
73166
  function tryExec(cmd) {
72632
73167
  try {
@@ -72640,14 +73175,19 @@ function tryExec(cmd) {
72640
73175
  }
72641
73176
  function readPackageJson4() {
72642
73177
  try {
72643
- const pkgPath = path95.join(packageRoot, "package.json");
73178
+ const pkgPath = path96.join(packageRoot, "package.json");
72644
73179
  return JSON.parse(readFileSync46(pkgPath, "utf8"));
72645
73180
  } catch {
72646
73181
  return null;
72647
73182
  }
72648
73183
  }
72649
73184
  function getGlobalPrefix() {
72650
- return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || tryExec("npm prefix -g");
73185
+ const fromNpm = tryExec("npm prefix -g");
73186
+ if (fromNpm) return fromNpm;
73187
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim();
73188
+ }
73189
+ function isSourceCheckout() {
73190
+ return existsSync62(path96.join(packageRoot, "src", "cli", "main.ts")) && existsSync62(path96.join(packageRoot, "apps", "desktop", "package.json"));
72651
73191
  }
72652
73192
  function checkShim(pkgName) {
72653
73193
  const prefix = getGlobalPrefix();
@@ -72656,8 +73196,16 @@ function checkShim(pkgName) {
72656
73196
  }
72657
73197
  const isWin = process.platform === "win32";
72658
73198
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
72659
- const shimPath = path95.join(prefix, shimName);
73199
+ const shimPath = path96.join(prefix, shimName);
72660
73200
  if (!existsSync62(shimPath)) {
73201
+ const localBin = path96.join(packageRoot, "bin", "zelari-code.js");
73202
+ if (isSourceCheckout() && existsSync62(localBin)) {
73203
+ return WARN(
73204
+ `global shim not found at ${shimPath}
73205
+ source checkout \u2014 using ${localBin}
73206
+ optional: npm install -g ${pkgName}@latest --force`
73207
+ );
73208
+ }
72661
73209
  return FAIL(
72662
73210
  `shim not found at ${shimPath}
72663
73211
  fix: npm install -g ${pkgName}@latest --force`
@@ -72684,8 +73232,8 @@ function checkShim(pkgName) {
72684
73232
  fix: npm install -g ${pkgName}@latest --force`
72685
73233
  );
72686
73234
  }
72687
- const resolved = path95.resolve(path95.dirname(shimPath), target);
72688
- const expected = path95.join(
73235
+ const resolved = path96.resolve(path96.dirname(shimPath), target);
73236
+ const expected = path96.join(
72689
73237
  prefix,
72690
73238
  "node_modules",
72691
73239
  pkgName,
@@ -72727,7 +73275,7 @@ function checkNode(pkg) {
72727
73275
  return OK(`node ${raw} (engines.node ${enginesNode ?? ">= 20.0.0"})`);
72728
73276
  }
72729
73277
  function checkBundle() {
72730
- const bundle = path95.join(packageRoot, "dist", "cli", "main.bundled.js");
73278
+ const bundle = path96.join(packageRoot, "dist", "cli", "main.bundled.js");
72731
73279
  if (!existsSync62(bundle)) {
72732
73280
  return FAIL(
72733
73281
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -72748,7 +73296,7 @@ function checkRuntimeDeps() {
72748
73296
  const missing = [];
72749
73297
  for (const dep of required2) {
72750
73298
  try {
72751
- const localReq = createRequire3(path95.join(packageRoot, "package.json"));
73299
+ const localReq = createRequire3(path96.join(packageRoot, "package.json"));
72752
73300
  localReq.resolve(dep);
72753
73301
  } catch {
72754
73302
  missing.push(dep);
@@ -73025,7 +73573,7 @@ var init_doctor = __esm({
73025
73573
  init_metrics3();
73026
73574
  init_contextGrowthSummary();
73027
73575
  require3 = createRequire3(import.meta.url);
73028
- __dirname3 = path95.dirname(fileURLToPath3(import.meta.url));
73576
+ __dirname3 = path96.dirname(fileURLToPath3(import.meta.url));
73029
73577
  packageRoot = findPackageRoot(__dirname3);
73030
73578
  OK = (message) => ({
73031
73579
  ok: true,
@@ -73052,16 +73600,15 @@ __export(fixPath_exports, {
73052
73600
  });
73053
73601
  import { spawnSync as spawnSync3 } from "node:child_process";
73054
73602
  function getGlobalPrefix2() {
73055
- return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim() || (() => {
73056
- try {
73057
- return spawnSync3("npm", ["prefix", "-g"], {
73058
- encoding: "utf8",
73059
- stdio: ["ignore", "pipe", "ignore"]
73060
- }).stdout?.trim() ?? "";
73061
- } catch {
73062
- return "";
73063
- }
73064
- })();
73603
+ try {
73604
+ const fromNpm = spawnSync3("npm", ["prefix", "-g"], {
73605
+ encoding: "utf8",
73606
+ stdio: ["ignore", "pipe", "ignore"]
73607
+ }).stdout?.trim() ?? "";
73608
+ if (fromNpm) return fromNpm;
73609
+ } catch {
73610
+ }
73611
+ return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim();
73065
73612
  }
73066
73613
  function powershell(script) {
73067
73614
  try {
@@ -73135,7 +73682,7 @@ __export(userSettings_exports, {
73135
73682
  settingsOverrides: () => settingsOverrides
73136
73683
  });
73137
73684
  import { existsSync as existsSync63, readFileSync as readFileSync47 } from "node:fs";
73138
- import path96 from "node:path";
73685
+ import path97 from "node:path";
73139
73686
  function parseBool(raw) {
73140
73687
  const v = raw.trim().toLowerCase();
73141
73688
  if (["1", "true", "yes", "on"].includes(v)) return true;
@@ -73202,8 +73749,8 @@ function envValueFor(key, env) {
73202
73749
  function resolveUserSettings(opts = {}) {
73203
73750
  const cwd = opts.cwd ?? process.cwd();
73204
73751
  const env = opts.env ?? process.env;
73205
- const userPath = path96.join(zelariHome(), SETTINGS_FILE_NAME);
73206
- const projectPath = path96.join(cwd, ".zelari", SETTINGS_FILE_NAME);
73752
+ const userPath = path97.join(zelariHome(), SETTINGS_FILE_NAME);
73753
+ const projectPath = path97.join(cwd, ".zelari", SETTINGS_FILE_NAME);
73207
73754
  const warnings = [];
73208
73755
  const userLayer = loadFileLayer(userPath, "user", warnings);
73209
73756
  const projectLayer = loadFileLayer(projectPath, "project", warnings);
@@ -73417,7 +73964,7 @@ __export(inspectSession_exports, {
73417
73964
  renderInspectReport: () => renderInspectReport,
73418
73965
  runInspectSession: () => runInspectSession
73419
73966
  });
73420
- import path97 from "node:path";
73967
+ import path98 from "node:path";
73421
73968
  import { existsSync as existsSync64 } from "node:fs";
73422
73969
  function formatLimit(limit) {
73423
73970
  return `${Math.round(limit / 1e3)}k`;
@@ -73451,8 +73998,8 @@ function renderInspectReport(state3) {
73451
73998
  }
73452
73999
  async function runInspectSession(opts) {
73453
74000
  const sessionsDir2 = resolveSessionsDir({ workspaceRoot: opts.cwd ?? process.cwd() });
73454
- const sessionDir = path97.join(sessionsDir2, opts.sessionId);
73455
- const eventsPath = path97.join(sessionDir, "events.jsonl");
74001
+ const sessionDir = path98.join(sessionsDir2, opts.sessionId);
74002
+ const eventsPath = path98.join(sessionDir, "events.jsonl");
73456
74003
  if (!existsSync64(sessionDir)) {
73457
74004
  console.error(`zelari-code inspect: no session directory at ${sessionDir}`);
73458
74005
  return 1;
@@ -73486,14 +74033,14 @@ __export(inspect_exports, {
73486
74033
  collectInspectReport: () => collectInspectReport,
73487
74034
  runInspect: () => runInspect
73488
74035
  });
73489
- import path98 from "node:path";
74036
+ import path99 from "node:path";
73490
74037
  import { existsSync as existsSync65, readFileSync as readFileSync48, readdirSync as readdirSync13 } from "node:fs";
73491
74038
  async function collectInspectReport(cwd = process.cwd()) {
73492
74039
  ensureBuiltinSkillsLoadedSync();
73493
74040
  const snap = listSkillsSnapshot(cwd);
73494
74041
  const mcp = listMcpServers(cwd);
73495
- const userMcpPath = path98.join(zelariHome(), "mcp.json");
73496
- const projectMcpPath = path98.join(cwd, ".zelari", "mcp.json");
74042
+ const userMcpPath = path99.join(zelariHome(), "mcp.json");
74043
+ const projectMcpPath = path99.join(cwd, ".zelari", "mcp.json");
73497
74044
  const globalHooks = globalHooksDir();
73498
74045
  const projectHooks = projectHooksDir(cwd);
73499
74046
  const projectTrusted = isFolderTrusted(cwd);
@@ -73521,9 +74068,9 @@ async function collectInspectReport(cwd = process.cwd()) {
73521
74068
  configSources: [
73522
74069
  { path: userMcpPath, exists: existsSync65(userMcpPath) },
73523
74070
  { path: projectMcpPath, exists: existsSync65(projectMcpPath) },
73524
- { path: path98.join(zelariHome(), "provider.json"), exists: existsSync65(path98.join(zelariHome(), "provider.json")) },
73525
- { path: path98.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync65(path98.join(cwd, ".zelari", "AGENTS.md")) },
73526
- { path: path98.join(cwd, "AGENTS.md"), exists: existsSync65(path98.join(cwd, "AGENTS.md")) }
74071
+ { path: path99.join(zelariHome(), "provider.json"), exists: existsSync65(path99.join(zelariHome(), "provider.json")) },
74072
+ { path: path99.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync65(path99.join(cwd, ".zelari", "AGENTS.md")) },
74073
+ { path: path99.join(cwd, "AGENTS.md"), exists: existsSync65(path99.join(cwd, "AGENTS.md")) }
73527
74074
  ],
73528
74075
  skills: {
73529
74076
  total: snap.skills.length,
@@ -73563,8 +74110,8 @@ function listJsonFiles(dir) {
73563
74110
  }
73564
74111
  function findAgentsMd(cwd) {
73565
74112
  const candidates = [
73566
- path98.join(cwd, "AGENTS.md"),
73567
- path98.join(cwd, ".zelari", "AGENTS.md")
74113
+ path99.join(cwd, "AGENTS.md"),
74114
+ path99.join(cwd, ".zelari", "AGENTS.md")
73568
74115
  ];
73569
74116
  const found = [];
73570
74117
  for (const c of candidates) {
@@ -76700,22 +77247,7 @@ init_spineTelemetry();
76700
77247
 
76701
77248
  // src/cli/hooks/permissionPicker.ts
76702
77249
  init_toolPermissions();
76703
-
76704
- // src/cli/hooks/askUserTimeout.ts
76705
- function askUserTimeoutMs() {
76706
- const raw = process.env.ZELARI_ASK_USER_TIMEOUT_MS?.trim();
76707
- if (!raw) return 3e5;
76708
- const n = Number.parseInt(raw, 10);
76709
- if (!Number.isFinite(n) || n < 0) return 3e5;
76710
- return n;
76711
- }
76712
- function armPickerTimeout(onFire, ms) {
76713
- if (ms <= 0) return () => void 0;
76714
- const id3 = setTimeout(onFire, ms);
76715
- return () => clearTimeout(id3);
76716
- }
76717
-
76718
- // src/cli/hooks/permissionPicker.ts
77250
+ init_askUserTimeout();
76719
77251
  function createPermissionAskHandler(opts) {
76720
77252
  const { setPicker: setPicker2, appendSystem: appendSystem2 } = opts;
76721
77253
  return (req) => new Promise((resolve9) => {
@@ -76808,6 +77340,7 @@ ${detail}${note}${claimsBlock}
76808
77340
  }
76809
77341
 
76810
77342
  // src/cli/hooks/useChatTurn.ts
77343
+ init_askUserTimeout();
76811
77344
  init_toolPermissions();
76812
77345
  init_skills2();
76813
77346
  init_fileStateStore();
@@ -78737,6 +79270,7 @@ init_permissionBroker();
78737
79270
  init_brokerHandlers();
78738
79271
  import { useEffect as useEffect5, useRef as useRef5 } from "react";
78739
79272
  init_messageHelpers();
79273
+ init_askUserTimeout();
78740
79274
  function usePermissionBroker(opts) {
78741
79275
  const { setPicker: setPicker2, setMessages } = opts;
78742
79276
  const handleRef = useRef5(null);
@@ -78832,10 +79366,10 @@ init_ledger();
78832
79366
 
78833
79367
  // src/cli/evolution/proposals.ts
78834
79368
  import { existsSync as existsSync51, readFileSync as readFileSync39 } from "node:fs";
78835
- import path76 from "node:path";
78836
- var PROPOSALS_REL = path76.join(".zelari", "evolution", "proposals.jsonl");
79369
+ import path77 from "node:path";
79370
+ var PROPOSALS_REL = path77.join(".zelari", "evolution", "proposals.jsonl");
78837
79371
  function proposalsPath(cwd) {
78838
- return path76.join(cwd, PROPOSALS_REL);
79372
+ return path77.join(cwd, PROPOSALS_REL);
78839
79373
  }
78840
79374
  function readProposalStore(cwd) {
78841
79375
  const file2 = proposalsPath(cwd);
@@ -79927,11 +80461,11 @@ function handleCacheStats(ctx) {
79927
80461
  init_messageHelpers();
79928
80462
  init_serviceFactory();
79929
80463
  import { promises as fs35 } from "node:fs";
79930
- import * as path78 from "node:path";
80464
+ import * as path79 from "node:path";
79931
80465
 
79932
80466
  // src/cli/memory/promotion.ts
79933
80467
  import { promises as fs34 } from "node:fs";
79934
- import * as path77 from "node:path";
80468
+ import * as path78 from "node:path";
79935
80469
  var START = "<!-- zelari:memory-promotions:start -->";
79936
80470
  var END = "<!-- zelari:memory-promotions:end -->";
79937
80471
  var DURABLE_KINDS = /* @__PURE__ */ new Set(["fact", "decision", "constraint", "preference", "procedure"]);
@@ -79942,16 +80476,16 @@ function lineFor(node) {
79942
80476
  }
79943
80477
  async function promoteMemoryToAgentsMd(projectRoot, node) {
79944
80478
  if (node.status !== "active") {
79945
- return { added: false, path: path77.join(projectRoot, "AGENTS.md"), reason: `memory is ${node.status}` };
80479
+ return { added: false, path: path78.join(projectRoot, "AGENTS.md"), reason: `memory is ${node.status}` };
79946
80480
  }
79947
80481
  if (!DURABLE_KINDS.has(node.kind)) {
79948
- return { added: false, path: path77.join(projectRoot, "AGENTS.md"), reason: `${node.kind} is not a durable instruction kind` };
80482
+ return { added: false, path: path78.join(projectRoot, "AGENTS.md"), reason: `${node.kind} is not a durable instruction kind` };
79949
80483
  }
79950
- const root = await fs34.realpath(projectRoot).catch(() => path77.resolve(projectRoot));
79951
- const target = path77.join(root, "AGENTS.md");
80484
+ const root = await fs34.realpath(projectRoot).catch(() => path78.resolve(projectRoot));
80485
+ const target = path78.join(root, "AGENTS.md");
79952
80486
  try {
79953
- const stat7 = await fs34.lstat(target);
79954
- if (stat7.isSymbolicLink() || !stat7.isFile()) throw new Error("AGENTS.md must be a regular project file.");
80487
+ const stat8 = await fs34.lstat(target);
80488
+ if (stat8.isSymbolicLink() || !stat8.isFile()) throw new Error("AGENTS.md must be a regular project file.");
79955
80489
  } catch (error51) {
79956
80490
  if (error51.code !== "ENOENT") throw error51;
79957
80491
  }
@@ -80013,25 +80547,25 @@ function sourceLine(source2) {
80013
80547
  return entries.length ? entries.map(([key, value]) => `${key}=${value}`).join(" \xB7 ") : "unknown";
80014
80548
  }
80015
80549
  function isInside(root, target) {
80016
- const relative6 = path78.relative(root, target);
80017
- return relative6 === "" || !relative6.startsWith("..") && !path78.isAbsolute(relative6);
80550
+ const relative6 = path79.relative(root, target);
80551
+ return relative6 === "" || !relative6.startsWith("..") && !path79.isAbsolute(relative6);
80018
80552
  }
80019
80553
  async function safeExportPath(cwd, requested) {
80020
- const lexicalRoot = path78.resolve(cwd);
80554
+ const lexicalRoot = path79.resolve(cwd);
80021
80555
  const root = await fs35.realpath(lexicalRoot).catch(() => lexicalRoot);
80022
- const fallback = path78.join(root, ".zelari", "memory", `export-${Date.now()}.json`);
80023
- const target = requested?.trim() ? path78.resolve(root, requested.trim()) : fallback;
80556
+ const fallback = path79.join(root, ".zelari", "memory", `export-${Date.now()}.json`);
80557
+ const target = requested?.trim() ? path79.resolve(root, requested.trim()) : fallback;
80024
80558
  if (!isInside(root, target)) {
80025
80559
  throw new Error("Export path must stay inside the active project.");
80026
80560
  }
80027
- const parent = path78.dirname(target);
80028
- const relativeParent = path78.relative(root, parent);
80561
+ const parent = path79.dirname(target);
80562
+ const relativeParent = path79.relative(root, parent);
80029
80563
  let cursor = root;
80030
- for (const segment of relativeParent.split(path78.sep).filter(Boolean)) {
80031
- cursor = path78.join(cursor, segment);
80564
+ for (const segment of relativeParent.split(path79.sep).filter(Boolean)) {
80565
+ cursor = path79.join(cursor, segment);
80032
80566
  try {
80033
- const stat7 = await fs35.lstat(cursor);
80034
- if (stat7.isSymbolicLink()) {
80567
+ const stat8 = await fs35.lstat(cursor);
80568
+ if (stat8.isSymbolicLink()) {
80035
80569
  throw new Error("Export path must not traverse a symbolic link.");
80036
80570
  }
80037
80571
  } catch (error51) {
@@ -80200,9 +80734,9 @@ ${message}` : message
80200
80734
  }
80201
80735
  case "export": {
80202
80736
  const target = await safeExportPath(ctx.cwd, args.join(" ").trim() || void 0);
80203
- await fs35.mkdir(path78.dirname(target), { recursive: true });
80204
- const root = await fs35.realpath(ctx.cwd).catch(() => path78.resolve(ctx.cwd));
80205
- const realParent = await fs35.realpath(path78.dirname(target));
80737
+ await fs35.mkdir(path79.dirname(target), { recursive: true });
80738
+ const root = await fs35.realpath(ctx.cwd).catch(() => path79.resolve(ctx.cwd));
80739
+ const realParent = await fs35.realpath(path79.dirname(target));
80206
80740
  if (!isInside(root, realParent)) {
80207
80741
  throw new Error("Export path resolves outside the active project.");
80208
80742
  }
@@ -80417,7 +80951,7 @@ import { promises as fs37 } from "node:fs";
80417
80951
  init_zod();
80418
80952
  init_taskTool();
80419
80953
  import { promises as fs36 } from "node:fs";
80420
- import path79 from "node:path";
80954
+ import path80 from "node:path";
80421
80955
  import { randomBytes as randomBytes6 } from "node:crypto";
80422
80956
  var CsvFanoutArgsSchema = external_exports.object({
80423
80957
  csv_path: external_exports.string().min(1),
@@ -80511,8 +81045,8 @@ function resolveMaxConcurrency(env = process.env) {
80511
81045
  }
80512
81046
  async function runCsvFanout(args, deps, opts) {
80513
81047
  const start = Date.now();
80514
- const absCsv = path79.isAbsolute(args.csv_path) ? args.csv_path : path79.join(opts.parentCwd, args.csv_path);
80515
- const absOut = path79.isAbsolute(args.output_csv_path) ? args.output_csv_path : path79.join(opts.parentCwd, args.output_csv_path);
81048
+ const absCsv = path80.isAbsolute(args.csv_path) ? args.csv_path : path80.join(opts.parentCwd, args.csv_path);
81049
+ const absOut = path80.isAbsolute(args.output_csv_path) ? args.output_csv_path : path80.join(opts.parentCwd, args.output_csv_path);
80516
81050
  const { headers: headers2, rows } = await readCsv(absCsv);
80517
81051
  if (headers2.length === 0) {
80518
81052
  throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
@@ -80568,7 +81102,7 @@ async function runCsvFanout(args, deps, opts) {
80568
81102
  errored += 1;
80569
81103
  errors.push(`${row[args.id_column] ?? i}: ${res.error}`);
80570
81104
  }
80571
- await fs36.mkdir(path79.dirname(absOut), { recursive: true });
81105
+ await fs36.mkdir(path80.dirname(absOut), { recursive: true });
80572
81106
  await queueWrite(serializeCsv(outHeaders, outputRecords));
80573
81107
  }
80574
81108
  }
@@ -80763,7 +81297,7 @@ function splitArgs(s) {
80763
81297
  // src/cli/slashHandlers/krakenWorkbench.ts
80764
81298
  init_messageHelpers();
80765
81299
  import { promises as fs38 } from "node:fs";
80766
- import path80 from "node:path";
81300
+ import path81 from "node:path";
80767
81301
 
80768
81302
  // src/cli/kraken/workbenchView.ts
80769
81303
  var EMPTY = {
@@ -80880,17 +81414,17 @@ function formatWorkbenchForTerminal(p3) {
80880
81414
 
80881
81415
  // src/cli/slashHandlers/krakenWorkbench.ts
80882
81416
  async function handleKrakenWorkbench(ctx) {
80883
- const dir = path80.join(ctx.cwd, ".zelari", "radio");
81417
+ const dir = path81.join(ctx.cwd, ".zelari", "radio");
80884
81418
  let latest = null;
80885
81419
  let latestMtime = 0;
80886
81420
  try {
80887
81421
  const files = await fs38.readdir(dir);
80888
81422
  for (const f of files) {
80889
81423
  if (!f.startsWith("workbench-") || !f.endsWith(".md")) continue;
80890
- const full = path80.join(dir, f);
80891
- const stat7 = await fs38.stat(full);
80892
- if (stat7.mtimeMs > latestMtime) {
80893
- latestMtime = stat7.mtimeMs;
81424
+ const full = path81.join(dir, f);
81425
+ const stat8 = await fs38.stat(full);
81426
+ if (stat8.mtimeMs > latestMtime) {
81427
+ latestMtime = stat8.mtimeMs;
80894
81428
  latest = full;
80895
81429
  }
80896
81430
  }
@@ -80904,10 +81438,10 @@ async function handleKrakenWorkbench(ctx) {
80904
81438
  const parsed = parseWorkbench(content);
80905
81439
  const rendered = formatWorkbenchForTerminal(parsed);
80906
81440
  if (!rendered.trim()) {
80907
- appendSystem(ctx.setMessages, `[kraken workbench] ${path80.basename(latest)}: (no nodes / no events yet)`);
81441
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path81.basename(latest)}: (no nodes / no events yet)`);
80908
81442
  return;
80909
81443
  }
80910
- appendSystem(ctx.setMessages, `[kraken workbench] ${path80.basename(latest)}:
81444
+ appendSystem(ctx.setMessages, `[kraken workbench] ${path81.basename(latest)}:
80911
81445
  ${rendered}`);
80912
81446
  }
80913
81447
 
@@ -81215,14 +81749,14 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
81215
81749
  init_messageHelpers();
81216
81750
  init_paths();
81217
81751
  import { promises as fs39 } from "node:fs";
81218
- import path83 from "node:path";
81752
+ import path84 from "node:path";
81219
81753
  async function handlePromoteMember(ctx, memberId) {
81220
81754
  try {
81221
81755
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
81222
81756
  const { skill, markdown } = promoteMember2(memberId);
81223
81757
  const skillDir = skillsDir();
81224
81758
  await fs39.mkdir(skillDir, { recursive: true });
81225
- const filePath = path83.join(skillDir, `${skill.id}.md`);
81759
+ const filePath = path84.join(skillDir, `${skill.id}.md`);
81226
81760
  const previous = await fs39.readFile(filePath, "utf8").catch(() => null);
81227
81761
  const { createHash: createHash24 } = await import("node:crypto");
81228
81762
  const sha = (s) => createHash24("sha256").update(s, "utf8").digest("hex");
@@ -81248,7 +81782,7 @@ ${lineage}
81248
81782
  // src/cli/branchManager.ts
81249
81783
  init_paths();
81250
81784
  import { promises as fs40, existsSync as existsSync55, readFileSync as readFileSync41, writeFileSync as writeFileSync24, mkdirSync as mkdirSync21, statSync as statSync7, rmSync as rmSync3 } from "node:fs";
81251
- import path84 from "node:path";
81785
+ import path85 from "node:path";
81252
81786
  var META_FILENAME = "meta.json";
81253
81787
  var SESSIONS_SUBDIR = "sessions";
81254
81788
  function getBranchesBaseDir() {
@@ -81258,13 +81792,13 @@ function getSessionsBaseDir() {
81258
81792
  return sessionsDir();
81259
81793
  }
81260
81794
  function branchPathFor(name, baseDir) {
81261
- return path84.join(baseDir, name);
81795
+ return path85.join(baseDir, name);
81262
81796
  }
81263
81797
  function metaPathFor(name, baseDir) {
81264
- return path84.join(baseDir, name, META_FILENAME);
81798
+ return path85.join(baseDir, name, META_FILENAME);
81265
81799
  }
81266
81800
  function sessionsPathFor(name, baseDir) {
81267
- return path84.join(baseDir, name, SESSIONS_SUBDIR);
81801
+ return path85.join(baseDir, name, SESSIONS_SUBDIR);
81268
81802
  }
81269
81803
  function readBranchMeta(name, baseDir) {
81270
81804
  const metaPath = metaPathFor(name, baseDir);
@@ -81289,7 +81823,7 @@ function readBranchMeta(name, baseDir) {
81289
81823
  }
81290
81824
  function writeBranchMeta(name, baseDir, meta3) {
81291
81825
  const metaPath = metaPathFor(name, baseDir);
81292
- mkdirSync21(path84.dirname(metaPath), { recursive: true });
81826
+ mkdirSync21(path85.dirname(metaPath), { recursive: true });
81293
81827
  writeFileSync24(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
81294
81828
  }
81295
81829
  async function countSessions(name, baseDir) {
@@ -81340,14 +81874,14 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
81340
81874
  if (branchExists(name, baseDir)) {
81341
81875
  throw new BranchAlreadyExistsError(name);
81342
81876
  }
81343
- const sourcePath = path84.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
81877
+ const sourcePath = path85.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
81344
81878
  if (!existsSync55(sourcePath)) {
81345
81879
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
81346
81880
  }
81347
81881
  const branchPath = branchPathFor(name, baseDir);
81348
81882
  const branchSessionsPath = sessionsPathFor(name, baseDir);
81349
81883
  mkdirSync21(branchSessionsPath, { recursive: true });
81350
- const destPath = path84.join(branchSessionsPath, `${fromSessionId}.jsonl`);
81884
+ const destPath = path85.join(branchSessionsPath, `${fromSessionId}.jsonl`);
81351
81885
  await fs40.copyFile(sourcePath, destPath);
81352
81886
  const meta3 = {
81353
81887
  name,
@@ -81451,14 +81985,14 @@ async function handleBranchCheckout(ctx, branchName) {
81451
81985
  // src/cli/slashHandlers/workspace.ts
81452
81986
  init_messageHelpers();
81453
81987
  import { promises as fs41 } from "node:fs";
81454
- import path85 from "node:path";
81988
+ import path86 from "node:path";
81455
81989
  async function handleWorkspaceShow(ctx, what) {
81456
81990
  try {
81457
- const zelari = path85.join(process.cwd(), ".zelari");
81991
+ const zelari = path86.join(process.cwd(), ".zelari");
81458
81992
  let content;
81459
81993
  switch (what) {
81460
81994
  case "plan": {
81461
- const planPath = path85.join(zelari, "plan.md");
81995
+ const planPath = path86.join(zelari, "plan.md");
81462
81996
  try {
81463
81997
  content = await fs41.readFile(planPath, "utf-8");
81464
81998
  } catch {
@@ -81467,7 +82001,7 @@ async function handleWorkspaceShow(ctx, what) {
81467
82001
  break;
81468
82002
  }
81469
82003
  case "decisions": {
81470
- const decisionsDir = path85.join(zelari, "decisions");
82004
+ const decisionsDir = path86.join(zelari, "decisions");
81471
82005
  try {
81472
82006
  const files = (await fs41.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
81473
82007
  if (files.length === 0) {
@@ -81477,7 +82011,7 @@ async function handleWorkspaceShow(ctx, what) {
81477
82011
  `];
81478
82012
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
81479
82013
  for (const f of files) {
81480
- const raw = await fs41.readFile(path85.join(decisionsDir, f), "utf-8");
82014
+ const raw = await fs41.readFile(path86.join(decisionsDir, f), "utf-8");
81481
82015
  const { meta: meta3, body } = parseFrontmatter2(raw);
81482
82016
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
81483
82017
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -81490,7 +82024,7 @@ async function handleWorkspaceShow(ctx, what) {
81490
82024
  break;
81491
82025
  }
81492
82026
  case "risks": {
81493
- const risksPath = path85.join(zelari, "risks.md");
82027
+ const risksPath = path86.join(zelari, "risks.md");
81494
82028
  try {
81495
82029
  content = await fs41.readFile(risksPath, "utf-8");
81496
82030
  } catch {
@@ -81499,7 +82033,7 @@ async function handleWorkspaceShow(ctx, what) {
81499
82033
  break;
81500
82034
  }
81501
82035
  case "agents": {
81502
- const agentsPath = path85.join(process.cwd(), "AGENTS.MD");
82036
+ const agentsPath = path86.join(process.cwd(), "AGENTS.MD");
81503
82037
  try {
81504
82038
  content = await fs41.readFile(agentsPath, "utf-8");
81505
82039
  } catch {
@@ -81508,7 +82042,7 @@ async function handleWorkspaceShow(ctx, what) {
81508
82042
  break;
81509
82043
  }
81510
82044
  case "docs": {
81511
- const docsDir = path85.join(zelari, "docs");
82045
+ const docsDir = path86.join(zelari, "docs");
81512
82046
  try {
81513
82047
  const files = (await fs41.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
81514
82048
  content = files.length ? `# Docs (${files.length})
@@ -81550,7 +82084,7 @@ async function handleWorkspaceReset(ctx, force) {
81550
82084
  return;
81551
82085
  }
81552
82086
  try {
81553
- const target = path85.join(process.cwd(), ".zelari");
82087
+ const target = path86.join(process.cwd(), ".zelari");
81554
82088
  await fs41.rm(target, { recursive: true, force: true });
81555
82089
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
81556
82090
  } catch (err) {
@@ -83433,8 +83967,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
83433
83967
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
83434
83968
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
83435
83969
  try {
83436
- const path99 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
83437
- name = path99 && /^[a-z0-9]/.test(path99) ? path99 : "imported-skill";
83970
+ const path100 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
83971
+ name = path100 && /^[a-z0-9]/.test(path100) ? path100 : "imported-skill";
83438
83972
  } catch {
83439
83973
  name = "imported-skill";
83440
83974
  }