zelari-code 1.27.2 → 1.28.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.
@@ -1829,10 +1829,10 @@ function mergeDefs(...defs) {
1829
1829
  function cloneDef(schema) {
1830
1830
  return mergeDefs(schema._zod.def);
1831
1831
  }
1832
- function getElementAtPath(obj, path43) {
1833
- if (!path43)
1832
+ function getElementAtPath(obj, path44) {
1833
+ if (!path44)
1834
1834
  return obj;
1835
- return path43.reduce((acc, key) => acc?.[key], obj);
1835
+ return path44.reduce((acc, key) => acc?.[key], obj);
1836
1836
  }
1837
1837
  function promiseAllObject(promisesObj) {
1838
1838
  const keys = Object.keys(promisesObj);
@@ -2160,11 +2160,11 @@ function explicitlyAborted(x, startIndex = 0) {
2160
2160
  }
2161
2161
  return false;
2162
2162
  }
2163
- function prefixIssues(path43, issues) {
2163
+ function prefixIssues(path44, issues) {
2164
2164
  return issues.map((iss) => {
2165
2165
  var _a3;
2166
2166
  (_a3 = iss).path ?? (_a3.path = []);
2167
- iss.path.unshift(path43);
2167
+ iss.path.unshift(path44);
2168
2168
  return iss;
2169
2169
  });
2170
2170
  }
@@ -2382,16 +2382,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2382
2382
  }
2383
2383
  function formatError(error51, mapper = (issue2) => issue2.message) {
2384
2384
  const fieldErrors = { _errors: [] };
2385
- const processError = (error52, path43 = []) => {
2385
+ const processError = (error52, path44 = []) => {
2386
2386
  for (const issue2 of error52.issues) {
2387
2387
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2388
- issue2.errors.map((issues) => processError({ issues }, [...path43, ...issue2.path]));
2388
+ issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
2389
2389
  } else if (issue2.code === "invalid_key") {
2390
- processError({ issues: issue2.issues }, [...path43, ...issue2.path]);
2390
+ processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2391
2391
  } else if (issue2.code === "invalid_element") {
2392
- processError({ issues: issue2.issues }, [...path43, ...issue2.path]);
2392
+ processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2393
2393
  } else {
2394
- const fullpath = [...path43, ...issue2.path];
2394
+ const fullpath = [...path44, ...issue2.path];
2395
2395
  if (fullpath.length === 0) {
2396
2396
  fieldErrors._errors.push(mapper(issue2));
2397
2397
  } else {
@@ -2418,17 +2418,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2418
2418
  }
2419
2419
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2420
2420
  const result = { errors: [] };
2421
- const processError = (error52, path43 = []) => {
2421
+ const processError = (error52, path44 = []) => {
2422
2422
  var _a3, _b;
2423
2423
  for (const issue2 of error52.issues) {
2424
2424
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2425
- issue2.errors.map((issues) => processError({ issues }, [...path43, ...issue2.path]));
2425
+ issue2.errors.map((issues) => processError({ issues }, [...path44, ...issue2.path]));
2426
2426
  } else if (issue2.code === "invalid_key") {
2427
- processError({ issues: issue2.issues }, [...path43, ...issue2.path]);
2427
+ processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2428
2428
  } else if (issue2.code === "invalid_element") {
2429
- processError({ issues: issue2.issues }, [...path43, ...issue2.path]);
2429
+ processError({ issues: issue2.issues }, [...path44, ...issue2.path]);
2430
2430
  } else {
2431
- const fullpath = [...path43, ...issue2.path];
2431
+ const fullpath = [...path44, ...issue2.path];
2432
2432
  if (fullpath.length === 0) {
2433
2433
  result.errors.push(mapper(issue2));
2434
2434
  continue;
@@ -2460,8 +2460,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2460
2460
  }
2461
2461
  function toDotPath(_path) {
2462
2462
  const segs = [];
2463
- const path43 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
- for (const seg of path43) {
2463
+ const path44 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2464
+ for (const seg of path44) {
2465
2465
  if (typeof seg === "number")
2466
2466
  segs.push(`[${seg}]`);
2467
2467
  else if (typeof seg === "symbol")
@@ -15964,13 +15964,13 @@ function resolveRef(ref, ctx) {
15964
15964
  if (!ref.startsWith("#")) {
15965
15965
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15966
15966
  }
15967
- const path43 = ref.slice(1).split("/").filter(Boolean);
15968
- if (path43.length === 0) {
15967
+ const path44 = ref.slice(1).split("/").filter(Boolean);
15968
+ if (path44.length === 0) {
15969
15969
  return ctx.rootSchema;
15970
15970
  }
15971
15971
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15972
- if (path43[0] === defsKey) {
15973
- const key = path43[1];
15972
+ if (path44[0] === defsKey) {
15973
+ const key = path44[1];
15974
15974
  if (!key || !ctx.defs[key]) {
15975
15975
  throw new Error(`Reference not found: ${ref}`);
15976
15976
  }
@@ -18134,11 +18134,11 @@ var init_tools = __esm({
18134
18134
  if (!ctx.addDocument)
18135
18135
  return "Knowledge vault tool not available.";
18136
18136
  const title = args["title"] || "New Document";
18137
- const path43 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18137
+ const path44 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
18138
18138
  const content = args["content"] || "";
18139
18139
  const tags = args["tags"] || [];
18140
18140
  ctx.addDocument({
18141
- path: path43,
18141
+ path: path44,
18142
18142
  title,
18143
18143
  content,
18144
18144
  format: "markdown",
@@ -18147,7 +18147,7 @@ var init_tools = __esm({
18147
18147
  workspaceId: ctx.workspaceId
18148
18148
  });
18149
18149
  ctx.addActivity("vault", "created document", title);
18150
- return `Document "${title}" created at "${path43}".`;
18150
+ return `Document "${title}" created at "${path44}".`;
18151
18151
  }
18152
18152
  }
18153
18153
  ];
@@ -21839,11 +21839,11 @@ var init_synthesisAudit = __esm({
21839
21839
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
21840
21840
  import { join as join2 } from "node:path";
21841
21841
  function loadNfrSpec(zelariRoot) {
21842
- const path43 = join2(zelariRoot, "nfr-spec.json");
21843
- if (!existsSync6(path43))
21842
+ const path44 = join2(zelariRoot, "nfr-spec.json");
21843
+ if (!existsSync6(path44))
21844
21844
  return null;
21845
21845
  try {
21846
- const raw = JSON.parse(readFileSync5(path43, "utf8"));
21846
+ const raw = JSON.parse(readFileSync5(path44, "utf8"));
21847
21847
  if (raw.version !== 1 || !Array.isArray(raw.targets))
21848
21848
  return null;
21849
21849
  return raw;
@@ -24149,9 +24149,9 @@ var init_types3 = __esm({
24149
24149
  import { readFileSync as readFileSync10 } from "node:fs";
24150
24150
  import { join as join8 } from "node:path";
24151
24151
  function readLessonsDeduped(zelariRoot) {
24152
- const path43 = join8(zelariRoot, LESSONS_FILE);
24152
+ const path44 = join8(zelariRoot, LESSONS_FILE);
24153
24153
  try {
24154
- const raw = readFileSync10(path43, "utf8");
24154
+ const raw = readFileSync10(path44, "utf8");
24155
24155
  const byId = /* @__PURE__ */ new Map();
24156
24156
  for (const line of raw.split(/\r?\n/)) {
24157
24157
  if (!line.trim())
@@ -24252,8 +24252,8 @@ function keywordsFrom(check2, signature) {
24252
24252
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
24253
24253
  }
24254
24254
  function writeLesson(zelariRoot, lesson) {
24255
- const path43 = join9(zelariRoot, LESSONS_FILE);
24256
- appendFileSync(path43, `${JSON.stringify(lesson)}
24255
+ const path44 = join9(zelariRoot, LESSONS_FILE);
24256
+ appendFileSync(path44, `${JSON.stringify(lesson)}
24257
24257
  `, "utf8");
24258
24258
  }
24259
24259
  function findSimilar(lessons, signature) {
@@ -24866,9 +24866,9 @@ function findCycle(nodes) {
24866
24866
  if (color.get(start) !== WHITE)
24867
24867
  continue;
24868
24868
  const stack = [[start, 0]];
24869
- const path43 = [];
24869
+ const path44 = [];
24870
24870
  color.set(start, GRAY);
24871
- path43.push(start);
24871
+ path44.push(start);
24872
24872
  while (stack.length > 0) {
24873
24873
  const top = stack[stack.length - 1];
24874
24874
  const [id, idx] = top;
@@ -24881,17 +24881,17 @@ function findCycle(nodes) {
24881
24881
  continue;
24882
24882
  const c = color.get(dep);
24883
24883
  if (c === GRAY) {
24884
- const at = path43.indexOf(dep);
24885
- return [...path43.slice(at), dep];
24884
+ const at = path44.indexOf(dep);
24885
+ return [...path44.slice(at), dep];
24886
24886
  }
24887
24887
  if (c === WHITE) {
24888
24888
  color.set(dep, GRAY);
24889
- path43.push(dep);
24889
+ path44.push(dep);
24890
24890
  stack.push([dep, 0]);
24891
24891
  }
24892
24892
  } else {
24893
24893
  color.set(id, BLACK);
24894
- path43.pop();
24894
+ path44.pop();
24895
24895
  stack.pop();
24896
24896
  }
24897
24897
  }
@@ -25700,9 +25700,9 @@ function spillToolOutput(fullText, meta3) {
25700
25700
  const rnd = randomBytes(3).toString("hex");
25701
25701
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
25702
25702
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
25703
- const path43 = join11(dir, file2);
25704
- writeFileSync10(path43, fullText, "utf8");
25705
- return path43;
25703
+ const path44 = join11(dir, file2);
25704
+ writeFileSync10(path44, fullText, "utf8");
25705
+ return path44;
25706
25706
  } catch {
25707
25707
  return null;
25708
25708
  }
@@ -25748,10 +25748,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
25748
25748
  ${tail}`;
25749
25749
  }
25750
25750
  if (doSpill) {
25751
- const path43 = spillToolOutput(text, { toolName: opts.toolName });
25752
- if (path43) {
25751
+ const path44 = spillToolOutput(text, { toolName: opts.toolName });
25752
+ if (path44) {
25753
25753
  const spillNote = `
25754
- \u2026 [full output spilled to: ${path43} \u2014 re-read with read_file if you need the complete text] \u2026`;
25754
+ \u2026 [full output spilled to: ${path44} \u2014 re-read with read_file if you need the complete text] \u2026`;
25755
25755
  if (preview.includes("] \u2026\n")) {
25756
25756
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
25757
25757
  `);
@@ -26581,11 +26581,20 @@ function maxToolCallsForThoroughness(thoroughness, agent) {
26581
26581
  if (thoroughness === "deep") return 12;
26582
26582
  return 6;
26583
26583
  }
26584
- async function runSubAgent(harness) {
26584
+ async function runSubAgent(harness, opts = {}) {
26585
+ const { signal } = opts;
26585
26586
  let current = "";
26586
26587
  let lastCompleted = "";
26587
26588
  let error51;
26589
+ if (signal?.aborted) return { result: "", aborted: true };
26588
26590
  for await (const ev of harness.run()) {
26591
+ if (signal?.aborted) {
26592
+ return {
26593
+ result: (lastCompleted || current).trim(),
26594
+ ...error51 ? { error: error51 } : {},
26595
+ aborted: true
26596
+ };
26597
+ }
26589
26598
  switch (ev.type) {
26590
26599
  case "message_start":
26591
26600
  current = "";
@@ -26715,8 +26724,31 @@ async function runTentacle(opts) {
26715
26724
  error: `task: failed to start sub-agent \u2014 ${err instanceof Error ? err.message : String(err)}`
26716
26725
  };
26717
26726
  }
26718
- const { result, error: error51 } = await runSubAgent(harness);
26727
+ const { result, error: error51, aborted: aborted2 } = await runSubAgent(harness, {
26728
+ ...opts.signal ? { signal: opts.signal } : {}
26729
+ });
26719
26730
  const durationMs = Date.now() - started;
26731
+ if (aborted2) {
26732
+ if (worktree && !shouldKeepWorktree()) await cleanupKrakenWorktree(worktree);
26733
+ appendKrakenRadio(parentCwd, sessionId, {
26734
+ kind: "error",
26735
+ agent,
26736
+ thoroughness,
26737
+ description: args.description,
26738
+ detail: "cancelled: node timeout",
26739
+ model: sub.model,
26740
+ worktree: worktree?.path ?? null,
26741
+ durationMs,
26742
+ ok: false
26743
+ });
26744
+ krakenTentacleEnd(liveId, {
26745
+ ok: false,
26746
+ model: sub.model,
26747
+ detail: "cancelled",
26748
+ durationMs
26749
+ });
26750
+ return { ok: false, agent, error: "task: sub-agent cancelled (node timeout)", cancelled: true };
26751
+ }
26720
26752
  if (!result) {
26721
26753
  if (worktree) await cleanupKrakenWorktree(worktree);
26722
26754
  appendKrakenRadio(parentCwd, sessionId, {
@@ -28660,21 +28692,21 @@ function normalizeAuth(auth) {
28660
28692
  return "agent";
28661
28693
  }
28662
28694
  function readSecrets() {
28663
- const path43 = getSshSecretsPath();
28664
- if (!existsSync17(path43)) return {};
28695
+ const path44 = getSshSecretsPath();
28696
+ if (!existsSync17(path44)) return {};
28665
28697
  try {
28666
- return JSON.parse(readFileSync16(path43, "utf8"));
28698
+ return JSON.parse(readFileSync16(path44, "utf8"));
28667
28699
  } catch {
28668
28700
  return {};
28669
28701
  }
28670
28702
  }
28671
28703
  function writeSecrets(data) {
28672
- const path43 = getSshSecretsPath();
28673
- mkdirSync9(dirname2(path43), { recursive: true });
28674
- writeFileSync11(path43, `${JSON.stringify(data, null, 2)}
28704
+ const path44 = getSshSecretsPath();
28705
+ mkdirSync9(dirname2(path44), { recursive: true });
28706
+ writeFileSync11(path44, `${JSON.stringify(data, null, 2)}
28675
28707
  `, "utf8");
28676
28708
  try {
28677
- chmodSync(path43, 384);
28709
+ chmodSync(path44, 384);
28678
28710
  } catch {
28679
28711
  }
28680
28712
  }
@@ -28703,10 +28735,10 @@ function deleteSshPassword(id) {
28703
28735
  writeSecrets({ passwords });
28704
28736
  }
28705
28737
  function readStore2() {
28706
- const path43 = getSshTargetsPath();
28707
- if (!existsSync17(path43)) return [];
28738
+ const path44 = getSshTargetsPath();
28739
+ if (!existsSync17(path44)) return [];
28708
28740
  try {
28709
- const parsed = JSON.parse(readFileSync16(path43, "utf8"));
28741
+ const parsed = JSON.parse(readFileSync16(path44, "utf8"));
28710
28742
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
28711
28743
  return list.filter(
28712
28744
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -28721,11 +28753,11 @@ function readStore2() {
28721
28753
  }
28722
28754
  }
28723
28755
  function writeStore2(targets) {
28724
- const path43 = getSshTargetsPath();
28725
- mkdirSync9(dirname2(path43), { recursive: true });
28756
+ const path44 = getSshTargetsPath();
28757
+ mkdirSync9(dirname2(path44), { recursive: true });
28726
28758
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
28727
28759
  writeFileSync11(
28728
- path43,
28760
+ path44,
28729
28761
  `${JSON.stringify({ targets: clean }, null, 2)}
28730
28762
  `,
28731
28763
  "utf8"
@@ -28971,11 +29003,11 @@ function formatSshTargetsForPrompt() {
28971
29003
  ];
28972
29004
  for (const t of targets) {
28973
29005
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
28974
- const path43 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
29006
+ const path44 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
28975
29007
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
28976
29008
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
28977
29009
  lines.push(
28978
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path43}${tags}${allow}`
29010
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path44}${tags}${allow}`
28979
29011
  );
28980
29012
  }
28981
29013
  return lines.join("\n");
@@ -31827,28 +31859,28 @@ var init_storage = __esm({
31827
31859
  VALID_SCALARS = /^(true|false|null|~)$/i;
31828
31860
  Storage = class {
31829
31861
  /** Read a Markdown file with frontmatter. Throws if not found. */
31830
- read(path43) {
31831
- if (!existsSync25(path43)) {
31832
- throw new Error(`File not found: ${path43}`);
31862
+ read(path44) {
31863
+ if (!existsSync25(path44)) {
31864
+ throw new Error(`File not found: ${path44}`);
31833
31865
  }
31834
- const md = readFileSync21(path43, "utf8");
31866
+ const md = readFileSync21(path44, "utf8");
31835
31867
  return parseFrontmatter(md);
31836
31868
  }
31837
31869
  /** Read a Markdown file; returns null if not found. */
31838
- readIfExists(path43) {
31839
- if (!existsSync25(path43)) return null;
31840
- return this.read(path43);
31870
+ readIfExists(path44) {
31871
+ if (!existsSync25(path44)) return null;
31872
+ return this.read(path44);
31841
31873
  }
31842
31874
  /**
31843
31875
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
31844
31876
  * The meta object is serialized as YAML frontmatter; body as Markdown.
31845
31877
  */
31846
- write(path43, meta3, body) {
31847
- mkdirSync11(dirname4(path43), { recursive: true });
31848
- const tmp = path43 + ".tmp-" + process.pid;
31878
+ write(path44, meta3, body) {
31879
+ mkdirSync11(dirname4(path44), { recursive: true });
31880
+ const tmp = path44 + ".tmp-" + process.pid;
31849
31881
  const md = serializeFrontmatter(meta3, body);
31850
31882
  writeFileSync13(tmp, md, "utf8");
31851
- renameSync2(tmp, path43);
31883
+ renameSync2(tmp, path44);
31852
31884
  }
31853
31885
  /** List all .md files in a directory (non-recursive). */
31854
31886
  listMarkdown(dir) {
@@ -31926,8 +31958,8 @@ function readPlan(ctx) {
31926
31958
  } catch {
31927
31959
  }
31928
31960
  }
31929
- const path43 = workspaceFile(ctx.rootDir, "plan");
31930
- const doc = ctx.storage.readIfExists(path43);
31961
+ const path44 = workspaceFile(ctx.rootDir, "plan");
31962
+ const doc = ctx.storage.readIfExists(path44);
31931
31963
  if (!doc) return { phases: [], tasks: [], milestones: [] };
31932
31964
  const meta3 = doc.meta;
31933
31965
  return {
@@ -32092,7 +32124,7 @@ function addMilestoneRecord(ctx, summary, input) {
32092
32124
  dueDate: input.dueDate,
32093
32125
  targetVersion: version2
32094
32126
  });
32095
- const path43 = join23(ctx.rootDir, "milestones", `${id}.md`);
32127
+ const path44 = join23(ctx.rootDir, "milestones", `${id}.md`);
32096
32128
  const meta3 = {
32097
32129
  kind: "milestone",
32098
32130
  id,
@@ -32109,7 +32141,7 @@ function addMilestoneRecord(ctx, summary, input) {
32109
32141
  `Target version: ${version2}`,
32110
32142
  ""
32111
32143
  ].join("\n");
32112
- ctx.storage.write(path43, meta3, body);
32144
+ ctx.storage.write(path44, meta3, body);
32113
32145
  return { id, created: true };
32114
32146
  }
32115
32147
  function readPlanSummary(ctx) {
@@ -32313,7 +32345,7 @@ function addIdeaStub(ctx) {
32313
32345
  const tags = args["tags"] ?? [];
32314
32346
  const category = args["category"] ?? "General";
32315
32347
  const id = `${nextAdrId(ctx)}-${slugify3(title)}`;
32316
- const path43 = workspaceArtifact(ctx.rootDir, "decisions", id);
32348
+ const path44 = workspaceArtifact(ctx.rootDir, "decisions", id);
32317
32349
  const meta3 = {
32318
32350
  kind: "adr",
32319
32351
  status: "proposed",
@@ -32339,7 +32371,7 @@ function addIdeaStub(ctx) {
32339
32371
  ...consequences.map((c) => `- ${c}`),
32340
32372
  ""
32341
32373
  ].join("\n");
32342
- ctx.storage.write(path43, meta3, body);
32374
+ ctx.storage.write(path44, meta3, body);
32343
32375
  return `ADR ${id} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
32344
32376
  });
32345
32377
  }
@@ -32421,14 +32453,14 @@ function createDocumentStub(ctx) {
32421
32453
  ctx.storage.write(risksPath, riskMeta, content);
32422
32454
  return `Document "${title}" created at risks.md (workspace root).`;
32423
32455
  }
32424
- const path43 = workspaceArtifact(ctx.rootDir, "docs", slug);
32456
+ const path44 = workspaceArtifact(ctx.rootDir, "docs", slug);
32425
32457
  const meta3 = {
32426
32458
  kind: "doc",
32427
32459
  id: slug,
32428
32460
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
32429
32461
  tags
32430
32462
  };
32431
- ctx.storage.write(path43, meta3, content);
32463
+ ctx.storage.write(path44, meta3, content);
32432
32464
  return `Document "${title}" created at docs/${slug}.md.`;
32433
32465
  });
32434
32466
  }
@@ -32972,10 +33004,10 @@ function getUserMcpPath() {
32972
33004
  function getProjectMcpPath(projectRoot) {
32973
33005
  return join24(projectRoot, ".zelari", "mcp.json");
32974
33006
  }
32975
- function readFile2(path43) {
32976
- if (!existsSync28(path43)) return {};
33007
+ function readFile2(path44) {
33008
+ if (!existsSync28(path44)) return {};
32977
33009
  try {
32978
- const parsed = JSON.parse(readFileSync23(path43, "utf8"));
33010
+ const parsed = JSON.parse(readFileSync23(path44, "utf8"));
32979
33011
  const out = {};
32980
33012
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
32981
33013
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -32991,10 +33023,10 @@ function readFile2(path43) {
32991
33023
  return {};
32992
33024
  }
32993
33025
  }
32994
- function writeFile(path43, servers) {
32995
- mkdirSync13(dirname6(path43), { recursive: true });
33026
+ function writeFile(path44, servers) {
33027
+ mkdirSync13(dirname6(path44), { recursive: true });
32996
33028
  const body = { mcpServers: servers };
32997
- writeFileSync15(path43, `${JSON.stringify(body, null, 2)}
33029
+ writeFileSync15(path44, `${JSON.stringify(body, null, 2)}
32998
33030
  `, "utf8");
32999
33031
  }
33000
33032
  function listMcpServers(projectRoot) {
@@ -33027,9 +33059,9 @@ function upsertMcpServer(opts) {
33027
33059
  if (!opts.config.command?.trim()) {
33028
33060
  return { ok: false, error: "command is required" };
33029
33061
  }
33030
- let path43;
33062
+ let path44;
33031
33063
  if (opts.scope === "user") {
33032
- path43 = getUserMcpPath();
33064
+ path44 = getUserMcpPath();
33033
33065
  } else {
33034
33066
  const root = opts.projectRoot?.trim();
33035
33067
  if (!root) {
@@ -33038,30 +33070,30 @@ function upsertMcpServer(opts) {
33038
33070
  error: "projectRoot required for project scope (Open Folder first)"
33039
33071
  };
33040
33072
  }
33041
- path43 = getProjectMcpPath(root);
33073
+ path44 = getProjectMcpPath(root);
33042
33074
  }
33043
- const current = readFile2(path43);
33075
+ const current = readFile2(path44);
33044
33076
  current[name] = {
33045
33077
  command: opts.config.command.trim(),
33046
33078
  args: opts.config.args,
33047
33079
  env: opts.config.env,
33048
33080
  enabled: opts.config.enabled !== false
33049
33081
  };
33050
- writeFile(path43, current);
33051
- return { ok: true, path: path43 };
33082
+ writeFile(path44, current);
33083
+ return { ok: true, path: path44 };
33052
33084
  }
33053
33085
  function removeMcpServer(opts) {
33054
- const path43 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
33055
- if (!path43) {
33086
+ const path44 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
33087
+ if (!path44) {
33056
33088
  return { ok: false, error: "projectRoot required for project scope" };
33057
33089
  }
33058
- const current = readFile2(path43);
33090
+ const current = readFile2(path44);
33059
33091
  if (!(opts.name in current)) {
33060
- return { ok: false, error: `Server "${opts.name}" not found in ${path43}` };
33092
+ return { ok: false, error: `Server "${opts.name}" not found in ${path44}` };
33061
33093
  }
33062
33094
  delete current[opts.name];
33063
- writeFile(path43, current);
33064
- return { ok: true, path: path43 };
33095
+ writeFile(path44, current);
33096
+ return { ok: true, path: path44 };
33065
33097
  }
33066
33098
  var init_mcpConfigIo = __esm({
33067
33099
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -33398,10 +33430,10 @@ import { createHash as createHash5 } from "node:crypto";
33398
33430
  import { join as join26 } from "node:path";
33399
33431
  import { readFile as readFile3 } from "node:fs/promises";
33400
33432
  async function readPackageJson2(projectRoot) {
33401
- const path43 = join26(projectRoot, "package.json");
33402
- if (!existsSync30(path43)) return null;
33433
+ const path44 = join26(projectRoot, "package.json");
33434
+ if (!existsSync30(path44)) return null;
33403
33435
  try {
33404
- return JSON.parse(await readFile3(path43, "utf8"));
33436
+ return JSON.parse(await readFile3(path44, "utf8"));
33405
33437
  } catch {
33406
33438
  return null;
33407
33439
  }
@@ -33483,9 +33515,9 @@ async function genBuild(ctx) {
33483
33515
  ].join("\n");
33484
33516
  }
33485
33517
  async function genOpenQuestions(ctx) {
33486
- const path43 = join26(ctx.rootDir, "risks.md");
33487
- if (!existsSync30(path43)) return "_No open questions._";
33488
- const content = readFileSync25(path43, "utf8");
33518
+ const path44 = join26(ctx.rootDir, "risks.md");
33519
+ if (!existsSync30(path44)) return "_No open questions._";
33520
+ const content = readFileSync25(path44, "utf8");
33489
33521
  const lines = content.split("\n");
33490
33522
  const questions = [];
33491
33523
  let currentTitle = "";
@@ -34052,8 +34084,8 @@ async function runPostCouncilHook(ctx, options) {
34052
34084
  sources: scope.sources
34053
34085
  } : void 0
34054
34086
  });
34055
- const path43 = writeCouncilCompletion(ctx.rootDir, completion);
34056
- completionHook = { ran: true, path: path43, completion };
34087
+ const path44 = writeCouncilCompletion(ctx.rootDir, completion);
34088
+ completionHook = { ran: true, path: path44, completion };
34057
34089
  } catch (err) {
34058
34090
  completionHook = {
34059
34091
  ran: true,
@@ -35272,7 +35304,8 @@ __export(planner_exports, {
35272
35304
  PlannerTransportError: () => PlannerTransportError,
35273
35305
  buildGraphFromPlan: () => buildGraphFromPlan,
35274
35306
  extractJsonObject: () => extractJsonObject,
35275
- planTaskGraph: () => planTaskGraph
35307
+ planTaskGraph: () => planTaskGraph,
35308
+ stripReasoningBlocks: () => stripReasoningBlocks
35276
35309
  });
35277
35310
  function resolvePlannerTimeoutMs(env = process.env) {
35278
35311
  const raw = env.ZELARI_KRAKEN_PLANNER_TIMEOUT_MS;
@@ -35286,22 +35319,77 @@ function resolvePlannerMaxTokens(env = process.env) {
35286
35319
  const n = Number.parseInt(raw, 10);
35287
35320
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_LLM_MAX_TOKENS;
35288
35321
  }
35289
- function extractJsonObject(text) {
35290
- const trimmed = text.trim();
35291
- const fenced = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(trimmed);
35292
- const body = fenced ? fenced[1].trim() : trimmed;
35293
- const balanced = extractBalancedJsonObject2(body);
35294
- const candidate = balanced ?? body;
35322
+ function extractJsonObject(text, opts = {}) {
35323
+ const stripped = stripReasoningBlocks(text);
35324
+ const usable = findJsonIn(stripped, opts);
35325
+ if (usable) return usable.value;
35326
+ const raw = findJsonIn(text, opts);
35327
+ if (raw) return raw.value;
35328
+ const body = bodyOf(stripped) || bodyOf(text);
35295
35329
  try {
35296
- return JSON.parse(candidate);
35330
+ return JSON.parse(body);
35297
35331
  } catch (err) {
35298
35332
  try {
35299
- return JSON.parse(repairLooseJson(candidate));
35333
+ return JSON.parse(repairLooseJson(body));
35300
35334
  } catch {
35301
35335
  throw err instanceof Error ? err : new Error(String(err));
35302
35336
  }
35303
35337
  }
35304
35338
  }
35339
+ function bodyOf(text) {
35340
+ const trimmed = text.trim();
35341
+ const fenced = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(trimmed);
35342
+ return fenced ? fenced[1].trim() : trimmed;
35343
+ }
35344
+ function findJsonIn(text, opts) {
35345
+ let firstParsed = null;
35346
+ for (const candidate of balancedJsonObjects(bodyOf(text))) {
35347
+ let value;
35348
+ try {
35349
+ value = JSON.parse(candidate);
35350
+ } catch {
35351
+ try {
35352
+ value = JSON.parse(repairLooseJson(candidate));
35353
+ } catch {
35354
+ continue;
35355
+ }
35356
+ }
35357
+ if (!opts.requireKey) return { value };
35358
+ if (value && typeof value === "object" && opts.requireKey in value) {
35359
+ return { value };
35360
+ }
35361
+ const unwrapped = unwrapKey(value, opts.requireKey);
35362
+ if (unwrapped) return { value: unwrapped };
35363
+ if (!firstParsed) firstParsed = { value };
35364
+ }
35365
+ return firstParsed;
35366
+ }
35367
+ function unwrapKey(value, key) {
35368
+ if (!value || typeof value !== "object") return null;
35369
+ for (const child of Object.values(value)) {
35370
+ if (child && typeof child === "object" && key in child) {
35371
+ return child;
35372
+ }
35373
+ }
35374
+ return null;
35375
+ }
35376
+ function stripReasoningBlocks(text) {
35377
+ let out = text.replace(/<(think|thinking|reasoning)>[\s\S]*?<\/\1>/gi, "");
35378
+ const openIdx = out.search(/<(think|thinking|reasoning)>/i);
35379
+ if (openIdx >= 0) out = out.slice(0, openIdx);
35380
+ return out;
35381
+ }
35382
+ function* balancedJsonObjects(s) {
35383
+ let from = 0;
35384
+ while (from < s.length) {
35385
+ const next = extractBalancedJsonObject2(s.slice(from));
35386
+ if (!next) return;
35387
+ yield next;
35388
+ const idx = s.indexOf(next, from);
35389
+ if (idx < 0) return;
35390
+ from = idx + next.length;
35391
+ }
35392
+ }
35305
35393
  function extractBalancedJsonObject2(s) {
35306
35394
  const start = s.indexOf("{");
35307
35395
  if (start < 0) return null;
@@ -35527,10 +35615,13 @@ async function createDefaultLlmClient(opts) {
35527
35615
  }
35528
35616
  };
35529
35617
  }
35530
- function buildPlannerUserPrompt(prompt) {
35618
+ function buildPlannerUserPrompt(prompt, previousAttempt) {
35619
+ const prev2 = previousAttempt?.trim() ? `
35620
+ ${previousAttempt.trim()}
35621
+ ` : "";
35531
35622
  return `Goal:
35532
35623
  ${prompt.trim()}
35533
-
35624
+ ${prev2}
35534
35625
  Return ONLY the JSON object described in the system prompt.`;
35535
35626
  }
35536
35627
  function uniqueId(base, existing) {
@@ -35597,14 +35688,19 @@ async function planTaskGraph(opts) {
35597
35688
  const client = opts.llmClient ?? await createDefaultLlmClient({ provider: opts.provider, model: opts.model });
35598
35689
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
35599
35690
  const graphId = opts.graphId ?? `kraken-${Date.now().toString(36)}`;
35600
- const userBase = buildPlannerUserPrompt(opts.prompt);
35691
+ const userBase = buildPlannerUserPrompt(opts.prompt, opts.previousAttempt);
35601
35692
  let lastError;
35602
35693
  let userMessage = userBase;
35603
35694
  for (let attempt = 1; attempt <= MAX_PLAN_ATTEMPTS; attempt++) {
35604
35695
  try {
35605
35696
  const text = await client.complete({ system: KRAKEN_PLANNER_SYSTEM_PROMPT, user: userMessage });
35606
- const parsedJson = extractJsonObject(text);
35697
+ const parsedJson = extractJsonObject(text, { requireKey: "nodes" });
35607
35698
  const validated = PlannedGraphSchema.parse(parsedJson);
35699
+ if (!validated.nodes.some((n) => n.kind === "general")) {
35700
+ throw new Error(
35701
+ 'the plan contains no "general" node, so it cannot change anything \u2014 every node is read-only "explore". Include at least one "general" node that performs the actual work.'
35702
+ );
35703
+ }
35608
35704
  const graph = buildGraphFromPlan(graphId, validated.nodes);
35609
35705
  const check2 = validateGraph(graph, { maxNodes });
35610
35706
  if (!check2.ok) {
@@ -35682,6 +35778,93 @@ var init_planner = __esm({
35682
35778
  }
35683
35779
  });
35684
35780
 
35781
+ // src/cli/kraken/graphMemory.ts
35782
+ var graphMemory_exports = {};
35783
+ __export(graphMemory_exports, {
35784
+ formatSnapshotForPlanner: () => formatSnapshotForPlanner,
35785
+ loadGraphSnapshot: () => loadGraphSnapshot,
35786
+ saveGraphSnapshot: () => saveGraphSnapshot,
35787
+ toGraphSnapshot: () => toGraphSnapshot
35788
+ });
35789
+ import { promises as fs19 } from "node:fs";
35790
+ import path34 from "node:path";
35791
+ function snapshotPath(cwd) {
35792
+ return path34.join(cwd, SNAPSHOT_DIR, SNAPSHOT_FILE);
35793
+ }
35794
+ function toGraphSnapshot(graph, opts) {
35795
+ return {
35796
+ graphId: graph.id,
35797
+ goal: opts.goal,
35798
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
35799
+ converged: opts.converged,
35800
+ nodes: [...graph.nodes.values()].map((n) => ({
35801
+ id: n.id,
35802
+ kind: n.kind,
35803
+ label: n.label,
35804
+ status: n.status,
35805
+ ...n.scope && n.scope.length > 0 ? { scope: n.scope } : {},
35806
+ ...n.error ? { error: n.error.slice(0, 300) } : {}
35807
+ }))
35808
+ };
35809
+ }
35810
+ async function saveGraphSnapshot(cwd, snapshot) {
35811
+ try {
35812
+ await fs19.access(cwd);
35813
+ const file2 = snapshotPath(cwd);
35814
+ await fs19.mkdir(path34.dirname(file2), { recursive: true });
35815
+ await fs19.writeFile(file2, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
35816
+ } catch {
35817
+ }
35818
+ }
35819
+ async function loadGraphSnapshot(cwd) {
35820
+ try {
35821
+ const raw = await fs19.readFile(snapshotPath(cwd), "utf8");
35822
+ const parsed = JSON.parse(raw);
35823
+ if (!parsed || !Array.isArray(parsed.nodes)) return null;
35824
+ return parsed;
35825
+ } catch {
35826
+ return null;
35827
+ }
35828
+ }
35829
+ function formatSnapshotForPlanner(snapshot) {
35830
+ if (!snapshot) return "";
35831
+ const done = snapshot.nodes.filter((n) => n.status === "done");
35832
+ const failed = snapshot.nodes.filter((n) => n.status === "error");
35833
+ const skipped = snapshot.nodes.filter((n) => n.status === "skipped");
35834
+ if (failed.length === 0 && skipped.length === 0) return "";
35835
+ const line = (n) => `- ${n.label}${n.scope ? ` [${n.scope.join(", ")}]` : ""}${n.error ? ` \u2014 ${n.error}` : ""}`;
35836
+ const parts = [
35837
+ "",
35838
+ "## Previous unfinished task graph in this project",
35839
+ `Goal it was working on: "${snapshot.goal}"`,
35840
+ `It did NOT finish (${done.length} done, ${failed.length} failed, ${skipped.length} never ran).`,
35841
+ "",
35842
+ "If that goal is unrelated to the one above, ignore this section entirely.",
35843
+ ""
35844
+ ];
35845
+ if (done.length > 0) {
35846
+ parts.push("Already completed \u2014 do NOT redo this work:", ...done.map(line), "");
35847
+ }
35848
+ if (failed.length > 0) {
35849
+ parts.push("Failed \u2014 needs to be finished or repaired:", ...failed.map(line), "");
35850
+ }
35851
+ if (skipped.length > 0) {
35852
+ parts.push("Never ran (blocked by the failures above):", ...skipped.map(line), "");
35853
+ }
35854
+ parts.push(
35855
+ "Plan only the remaining work. Inspect what is already on disk before rewriting it, and prefer wiring/finishing existing modules over recreating them."
35856
+ );
35857
+ return parts.join("\n");
35858
+ }
35859
+ var SNAPSHOT_DIR, SNAPSHOT_FILE;
35860
+ var init_graphMemory = __esm({
35861
+ "src/cli/kraken/graphMemory.ts"() {
35862
+ "use strict";
35863
+ SNAPSHOT_DIR = path34.join(".zelari", "kraken");
35864
+ SNAPSHOT_FILE = "last-graph.json";
35865
+ }
35866
+ });
35867
+
35685
35868
  // src/cli/kraken/tentacle.ts
35686
35869
  var init_tentacle = __esm({
35687
35870
  "src/cli/kraken/tentacle.ts"() {
@@ -35693,6 +35876,7 @@ var init_tentacle = __esm({
35693
35876
  // src/cli/kraken/executor.ts
35694
35877
  var executor_exports = {};
35695
35878
  __export(executor_exports, {
35879
+ DEFAULT_CANCEL_GRACE_MS: () => DEFAULT_CANCEL_GRACE_MS,
35696
35880
  DEFAULT_FIX_BUDGET: () => DEFAULT_FIX_BUDGET,
35697
35881
  DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
35698
35882
  DEFAULT_NODE_TIMEOUT_MS: () => DEFAULT_NODE_TIMEOUT_MS,
@@ -35700,12 +35884,13 @@ __export(executor_exports, {
35700
35884
  KrakenGraphExecutor: () => KrakenGraphExecutor,
35701
35885
  isKrakenGraphEnabled: () => isKrakenGraphEnabled,
35702
35886
  isWorldModelGateEnabled: () => isWorldModelGateEnabled,
35887
+ resolveCancelGraceMs: () => resolveCancelGraceMs,
35703
35888
  resolveFixBudget: () => resolveFixBudget,
35704
35889
  resolveMaxParallel: () => resolveMaxParallel,
35705
35890
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs
35706
35891
  });
35707
35892
  import { existsSync as existsSync34 } from "node:fs";
35708
- import path34 from "node:path";
35893
+ import path35 from "node:path";
35709
35894
  function resolveMaxParallel(env = process.env) {
35710
35895
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
35711
35896
  if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
@@ -35718,6 +35903,12 @@ function resolveFixBudget(env = process.env) {
35718
35903
  const n = Number.parseInt(raw, 10);
35719
35904
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FIX_BUDGET;
35720
35905
  }
35906
+ function resolveCancelGraceMs(env = process.env) {
35907
+ const raw = env.ZELARI_KRAKEN_CANCEL_GRACE_MS;
35908
+ if (raw === void 0 || raw === "") return DEFAULT_CANCEL_GRACE_MS;
35909
+ const n = Number.parseInt(raw, 10);
35910
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_CANCEL_GRACE_MS;
35911
+ }
35721
35912
  function resolveNodeTimeoutMs(env = process.env, agent) {
35722
35913
  const raw = env.ZELARI_KRAKEN_NODE_TIMEOUT_MS;
35723
35914
  if (raw !== void 0 && raw !== "") {
@@ -35743,12 +35934,12 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
35743
35934
  }
35744
35935
  function defaultChecksExists(cwd) {
35745
35936
  try {
35746
- return existsSync34(path34.join(cwd, ".zelari", "world", "checks.json"));
35937
+ return existsSync34(path35.join(cwd, ".zelari", "world", "checks.json"));
35747
35938
  } catch {
35748
35939
  return false;
35749
35940
  }
35750
35941
  }
35751
- var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, KrakenGraphExecutor;
35942
+ var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_NODE_TIMEOUT_MS, DEFAULT_WRITER_NODE_TIMEOUT_MS, DEFAULT_CANCEL_GRACE_MS, KrakenGraphExecutor;
35752
35943
  var init_executor = __esm({
35753
35944
  "src/cli/kraken/executor.ts"() {
35754
35945
  "use strict";
@@ -35758,17 +35949,21 @@ var init_executor = __esm({
35758
35949
  init_krakenRadio();
35759
35950
  init_worldModel();
35760
35951
  init_graphStatus();
35952
+ init_graphMemory();
35761
35953
  DEFAULT_MAX_PARALLEL = 12;
35762
35954
  DEFAULT_FIX_BUDGET = 3;
35763
35955
  DEFAULT_NODE_TIMEOUT_MS = 3e5;
35764
35956
  DEFAULT_WRITER_NODE_TIMEOUT_MS = 9e5;
35957
+ DEFAULT_CANCEL_GRACE_MS = 3e4;
35765
35958
  KrakenGraphExecutor = class {
35766
35959
  deps;
35767
35960
  parentCwd;
35768
35961
  sessionId;
35962
+ goal;
35769
35963
  maxParallel;
35770
35964
  /** Explicit all-kinds override; when undefined the budget is per-kind. */
35771
35965
  nodeTimeoutMs;
35966
+ cancelGraceMs;
35772
35967
  fixBudgetRemaining;
35773
35968
  worldModelGateOverride;
35774
35969
  runTentacleFn;
@@ -35780,8 +35975,10 @@ var init_executor = __esm({
35780
35975
  this.deps = opts.taskToolDeps;
35781
35976
  this.parentCwd = opts.parentCwd;
35782
35977
  this.sessionId = opts.sessionId;
35978
+ this.goal = opts.goal;
35783
35979
  this.maxParallel = opts.maxParallel ?? resolveMaxParallel();
35784
35980
  this.nodeTimeoutMs = opts.nodeTimeoutMs;
35981
+ this.cancelGraceMs = opts.cancelGraceMs;
35785
35982
  this.fixBudgetRemaining = opts.fixBudget ?? resolveFixBudget();
35786
35983
  this.worldModelGateOverride = opts.worldModelGate;
35787
35984
  this.runTentacleFn = opts.runTentacleFn ?? runTentacle;
@@ -35839,6 +36036,10 @@ var init_executor = __esm({
35839
36036
  });
35840
36037
  }
35841
36038
  endKrakenGraphLive(graph, converged);
36039
+ await saveGraphSnapshot(
36040
+ this.parentCwd,
36041
+ toGraphSnapshot(graph, { goal: this.goal ?? graph.id, converged })
36042
+ );
35842
36043
  return {
35843
36044
  graph,
35844
36045
  converged,
@@ -35855,6 +36056,7 @@ var init_executor = __esm({
35855
36056
  }
35856
36057
  const usesWorktree = node.kind === "general" || node.kind === "fix";
35857
36058
  const agent = node.kind === "fix" ? "general" : node.kind;
36059
+ const controller = new AbortController();
35858
36060
  const res = await this.withNodeTimeout(
35859
36061
  this.runTentacleFn({
35860
36062
  deps: this.deps,
@@ -35872,9 +36074,11 @@ var init_executor = __esm({
35872
36074
  // (Correction 4); explore/verify never create a worktree.
35873
36075
  deferMerge: usesWorktree,
35874
36076
  graphId: graph.id,
35875
- nodeId: node.id
36077
+ nodeId: node.id,
36078
+ signal: controller.signal
35876
36079
  }),
35877
- agent
36080
+ agent,
36081
+ controller
35878
36082
  );
35879
36083
  if (res.ok && usesWorktree) {
35880
36084
  this.nodeRunState.set(node.id, { worktreeHandle: res.worktreeHandle });
@@ -35883,25 +36087,49 @@ var init_executor = __esm({
35883
36087
  }
35884
36088
  /**
35885
36089
  * Bound a tentacle run to its wall-clock budget (explicit `nodeTimeoutMs`
35886
- * option, else per-kind — writers get more than readers). On timeout, resolves
35887
- * to a synthetic `TentacleFailure` so the normal retry/fix/cascade-skip
35888
- * path handles it this only bounds how long the EXECUTOR waits, it does
35889
- * not (cannot) forcibly cancel the underlying sub-agent run; the point is
35890
- * to guarantee `execute()` itself always settles so the caller's process
35891
- * can exit instead of hanging forever on one stuck node.
36090
+ * option, else per-kind — writers get more than readers). On timeout the
36091
+ * run is CANCELLED via its AbortSignal, then given `cancelGraceMs` to
36092
+ * unwind. Cancellation lands at the sub-agent's next event boundary (an
36093
+ * async generator only observes `.return()` when it next yields), so a run
36094
+ * blocked on a slow provider call can outlive the grace period — that case
36095
+ * resolves with `cancelled: false` and `applyResult` refuses to re-spawn
36096
+ * the node, since a tentacle that may still be writing must not be joined
36097
+ * by a second one on the same scope.
36098
+ *
36099
+ * Either way `execute()` always settles, so the caller's process can exit
36100
+ * instead of hanging forever on one stuck node.
35892
36101
  */
35893
- withNodeTimeout(promise2, agent) {
36102
+ async withNodeTimeout(promise2, agent, controller) {
35894
36103
  const ms = this.nodeTimeoutMs ?? resolveNodeTimeoutMs(process.env, agent);
35895
36104
  if (ms <= 0) return promise2;
35896
36105
  let timer;
36106
+ const TIMED_OUT = Symbol("timeout");
35897
36107
  const timeout = new Promise((resolve3) => {
35898
- timer = setTimeout(() => {
35899
- resolve3({ ok: false, agent, error: `tentacle timed out after ${ms}ms` });
35900
- }, ms);
36108
+ timer = setTimeout(() => resolve3(TIMED_OUT), ms);
35901
36109
  });
35902
- return Promise.race([promise2, timeout]).finally(() => {
36110
+ const raced = await Promise.race([promise2, timeout]).finally(() => {
35903
36111
  if (timer) clearTimeout(timer);
35904
36112
  });
36113
+ if (raced !== TIMED_OUT) return raced;
36114
+ controller?.abort();
36115
+ const graceMs = this.cancelGraceMs ?? resolveCancelGraceMs();
36116
+ let graceTimer;
36117
+ const grace = new Promise((resolve3) => {
36118
+ graceTimer = setTimeout(() => resolve3(TIMED_OUT), graceMs);
36119
+ });
36120
+ const settled = await Promise.race([promise2, grace]).finally(() => {
36121
+ if (graceTimer) clearTimeout(graceTimer);
36122
+ });
36123
+ if (settled !== TIMED_OUT) {
36124
+ if (settled.ok) return settled;
36125
+ return { ...settled, cancelled: true };
36126
+ }
36127
+ return {
36128
+ ok: false,
36129
+ agent,
36130
+ error: `tentacle timed out after ${ms}ms and did not stop within ${graceMs}ms of being cancelled; not retrying to avoid two tentacles writing the same scope`,
36131
+ cancelled: false
36132
+ };
35905
36133
  }
35906
36134
  /**
35907
36135
  * Sequentially merge every dep node's deferred worktree (in dep order) into
@@ -35965,6 +36193,16 @@ var init_executor = __esm({
35965
36193
  return;
35966
36194
  }
35967
36195
  node.error = res.error;
36196
+ if (res.cancelled === false) {
36197
+ node.status = "error";
36198
+ this.radio("node_end", {
36199
+ description: node.label,
36200
+ agent: node.kind,
36201
+ detail: res.error,
36202
+ ok: false
36203
+ });
36204
+ return;
36205
+ }
35968
36206
  if (node.retryCount < node.maxRetries) {
35969
36207
  node.retryCount += 1;
35970
36208
  node.status = "pending";
@@ -36453,10 +36691,10 @@ var init_prereqChecks = __esm({
36453
36691
 
36454
36692
  // src/cli/plugins/prefs.ts
36455
36693
  import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
36456
- import path35 from "node:path";
36694
+ import path36 from "node:path";
36457
36695
  import os9 from "node:os";
36458
36696
  function getPluginPrefsPath() {
36459
- return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path35.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
36697
+ return process.env.ZELARI_PLUGINS_PREFS_FILE ?? path36.join(os9.homedir(), ".tmp", "zelari-code", "plugins.json");
36460
36698
  }
36461
36699
  function getPluginPrefs() {
36462
36700
  const file2 = getPluginPrefsPath();
@@ -36477,7 +36715,7 @@ function getPluginPrefs() {
36477
36715
  }
36478
36716
  function writePluginPrefs(prefs) {
36479
36717
  const file2 = getPluginPrefsPath();
36480
- mkdirSync15(path35.dirname(file2), { recursive: true });
36718
+ mkdirSync15(path36.dirname(file2), { recursive: true });
36481
36719
  writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
36482
36720
  encoding: "utf-8",
36483
36721
  mode: 384
@@ -36514,7 +36752,7 @@ __export(registry_exports, {
36514
36752
  isBinaryOnPath: () => isBinaryOnPath
36515
36753
  });
36516
36754
  import { existsSync as existsSync37 } from "node:fs";
36517
- import path36 from "node:path";
36755
+ import path37 from "node:path";
36518
36756
  function detectLocalBin(bin) {
36519
36757
  return (cwd) => {
36520
36758
  try {
@@ -36532,7 +36770,7 @@ function isBinaryOnPath(bin, opts = {}) {
36532
36770
  const platform = opts.platform ?? process.platform;
36533
36771
  const exists = opts.exists ?? existsSync37;
36534
36772
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
36535
- const pathMod = platform === "win32" ? path36.win32 : path36.posix;
36773
+ const pathMod = platform === "win32" ? path37.win32 : path37.posix;
36536
36774
  const sep2 = platform === "win32" ? ";" : ":";
36537
36775
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
36538
36776
  const candidates = [bin];
@@ -36841,10 +37079,10 @@ __export(triggerLock_exports, {
36841
37079
  lockPath: () => lockPath,
36842
37080
  releaseLock: () => releaseLock
36843
37081
  });
36844
- import { promises as fs23 } from "node:fs";
36845
- import * as path41 from "node:path";
37082
+ import { promises as fs24 } from "node:fs";
37083
+ import * as path42 from "node:path";
36846
37084
  function lockPath(projectRoot) {
36847
- return path41.join(projectRoot, ".zelari", "trigger.lock");
37085
+ return path42.join(projectRoot, ".zelari", "trigger.lock");
36848
37086
  }
36849
37087
  function isPidAlive(pid) {
36850
37088
  try {
@@ -36857,10 +37095,10 @@ function isPidAlive(pid) {
36857
37095
  }
36858
37096
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
36859
37097
  const lp = lockPath(projectRoot);
36860
- const dir = path41.dirname(lp);
36861
- await fs23.mkdir(dir, { recursive: true });
37098
+ const dir = path42.dirname(lp);
37099
+ await fs24.mkdir(dir, { recursive: true });
36862
37100
  try {
36863
- const raw = await fs23.readFile(lp, "utf8");
37101
+ const raw = await fs24.readFile(lp, "utf8");
36864
37102
  const existing = JSON.parse(raw);
36865
37103
  if (existing.pid && isPidAlive(existing.pid)) {
36866
37104
  return { acquired: false, heldBy: existing.pid, lockPath: lp };
@@ -36871,13 +37109,13 @@ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date())
36871
37109
  pid: process.pid,
36872
37110
  acquiredAt: now().toISOString()
36873
37111
  };
36874
- await fs23.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
37112
+ await fs24.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
36875
37113
  return { acquired: true, lockPath: lp };
36876
37114
  }
36877
37115
  async function releaseLock(projectRoot) {
36878
37116
  const lp = lockPath(projectRoot);
36879
37117
  try {
36880
- await fs23.unlink(lp);
37118
+ await fs24.unlink(lp);
36881
37119
  } catch {
36882
37120
  }
36883
37121
  }
@@ -37165,12 +37403,12 @@ function ensureHome() {
37165
37403
  }
37166
37404
  }
37167
37405
  function loadCompanionConfig() {
37168
- const path43 = getCompanionConfigPath();
37169
- if (!existsSync43(path43)) {
37406
+ const path44 = getCompanionConfigPath();
37407
+ if (!existsSync43(path44)) {
37170
37408
  return { projects: [] };
37171
37409
  }
37172
37410
  try {
37173
- const raw = JSON.parse(readFileSync34(path43, "utf8"));
37411
+ const raw = JSON.parse(readFileSync34(path44, "utf8"));
37174
37412
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
37175
37413
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
37176
37414
  ).map((p3) => ({
@@ -37208,16 +37446,16 @@ function loadOrCreateToken(explicit) {
37208
37446
  return { token: explicit.trim(), created: false };
37209
37447
  }
37210
37448
  ensureHome();
37211
- const path43 = getCompanionTokenPath();
37212
- if (existsSync43(path43)) {
37213
- const t = readFileSync34(path43, "utf8").trim();
37449
+ const path44 = getCompanionTokenPath();
37450
+ if (existsSync43(path44)) {
37451
+ const t = readFileSync34(path44, "utf8").trim();
37214
37452
  if (t) return { token: t, created: false };
37215
37453
  }
37216
37454
  const token = randomBytes3(24).toString("base64url");
37217
- writeFileSync21(path43, token + "\n", "utf8");
37455
+ writeFileSync21(path44, token + "\n", "utf8");
37218
37456
  try {
37219
- const fs24 = __require("node:fs");
37220
- fs24.chmodSync?.(path43, 384);
37457
+ const fs25 = __require("node:fs");
37458
+ fs25.chmodSync?.(path44, 384);
37221
37459
  } catch {
37222
37460
  }
37223
37461
  return { token, created: true };
@@ -37242,17 +37480,17 @@ function mergeProjects(cfg, extraPaths) {
37242
37480
  byId.set(p3.id, p3);
37243
37481
  }
37244
37482
  for (const raw of extraPaths) {
37245
- const path43 = raw.trim();
37246
- if (!path43) continue;
37247
- let id = slugFromPath(path43);
37483
+ const path44 = raw.trim();
37484
+ if (!path44) continue;
37485
+ let id = slugFromPath(path44);
37248
37486
  let n = 2;
37249
- while (byId.has(id) && byId.get(id).path !== path43) {
37250
- id = `${slugFromPath(path43)}-${n++}`;
37487
+ while (byId.has(id) && byId.get(id).path !== path44) {
37488
+ id = `${slugFromPath(path44)}-${n++}`;
37251
37489
  }
37252
37490
  byId.set(id, {
37253
37491
  id,
37254
- name: slugFromPath(path43),
37255
- path: path43
37492
+ name: slugFromPath(path44),
37493
+ path: path44
37256
37494
  });
37257
37495
  }
37258
37496
  return [...byId.values()];
@@ -37629,9 +37867,9 @@ async function runCompanionServe(opts = {}) {
37629
37867
  return;
37630
37868
  }
37631
37869
  const url2 = parseUrl(req);
37632
- const path43 = url2.pathname.replace(/\/+$/, "") || "/";
37870
+ const path44 = url2.pathname.replace(/\/+$/, "") || "/";
37633
37871
  try {
37634
- if (req.method === "GET" && (path43 === "/health" || path43 === "/v1/health")) {
37872
+ if (req.method === "GET" && (path44 === "/health" || path44 === "/v1/health")) {
37635
37873
  sendJson(res, 200, {
37636
37874
  ok: true,
37637
37875
  service: "zelari-companion",
@@ -37643,18 +37881,18 @@ async function runCompanionServe(opts = {}) {
37643
37881
  });
37644
37882
  return;
37645
37883
  }
37646
- if (path43.startsWith("/v1")) {
37884
+ if (path44.startsWith("/v1")) {
37647
37885
  if (!tokenMatches(token, getBearer(req))) {
37648
37886
  sendJson(res, 401, { ok: false, error: "unauthorized" });
37649
37887
  return;
37650
37888
  }
37651
37889
  }
37652
- if (req.method === "GET" && path43 === "/v1/config") {
37890
+ if (req.method === "GET" && path44 === "/v1/config") {
37653
37891
  const snap = buildDesktopConfigSnapshot();
37654
37892
  sendJson(res, 200, { ok: true, ...snap });
37655
37893
  return;
37656
37894
  }
37657
- if (req.method === "GET" && path43 === "/v1/projects") {
37895
+ if (req.method === "GET" && path44 === "/v1/projects") {
37658
37896
  sendJson(res, 200, {
37659
37897
  ok: true,
37660
37898
  projects: projects.map((p3) => ({
@@ -37665,7 +37903,7 @@ async function runCompanionServe(opts = {}) {
37665
37903
  });
37666
37904
  return;
37667
37905
  }
37668
- if (req.method === "GET" && path43 === "/v1/runs") {
37906
+ if (req.method === "GET" && path44 === "/v1/runs") {
37669
37907
  sendJson(res, 200, {
37670
37908
  ok: true,
37671
37909
  active: runs.getActive(),
@@ -37683,7 +37921,7 @@ async function runCompanionServe(opts = {}) {
37683
37921
  });
37684
37922
  return;
37685
37923
  }
37686
- if (req.method === "POST" && path43 === "/v1/runs") {
37924
+ if (req.method === "POST" && path44 === "/v1/runs") {
37687
37925
  const raw = await readBody(req);
37688
37926
  let body = {};
37689
37927
  try {
@@ -37730,7 +37968,7 @@ async function runCompanionServe(opts = {}) {
37730
37968
  });
37731
37969
  return;
37732
37970
  }
37733
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path43);
37971
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path44);
37734
37972
  if (req.method === "GET" && eventsMatch) {
37735
37973
  const runId = eventsMatch[1];
37736
37974
  const run = runs.getRun(runId);
@@ -37795,7 +38033,7 @@ async function runCompanionServe(opts = {}) {
37795
38033
  }, 500);
37796
38034
  return;
37797
38035
  }
37798
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path43);
38036
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path44);
37799
38037
  if (req.method === "POST" && cancelMatch) {
37800
38038
  const runId = cancelMatch[1];
37801
38039
  const result = runs.cancel(runId);
@@ -37907,11 +38145,11 @@ import { execSync as execSync2 } from "node:child_process";
37907
38145
  import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
37908
38146
  import { createRequire as createRequire3 } from "node:module";
37909
38147
  import { fileURLToPath as fileURLToPath2 } from "node:url";
37910
- import path42 from "node:path";
38148
+ import path43 from "node:path";
37911
38149
  function findPackageRoot(start) {
37912
38150
  let dir = start;
37913
38151
  for (let i = 0; i < 6; i += 1) {
37914
- const candidate = path42.join(dir, "package.json");
38152
+ const candidate = path43.join(dir, "package.json");
37915
38153
  if (existsSync45(candidate)) {
37916
38154
  try {
37917
38155
  const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
@@ -37919,11 +38157,11 @@ function findPackageRoot(start) {
37919
38157
  } catch {
37920
38158
  }
37921
38159
  }
37922
- const parent = path42.dirname(dir);
38160
+ const parent = path43.dirname(dir);
37923
38161
  if (parent === dir) break;
37924
38162
  dir = parent;
37925
38163
  }
37926
- return path42.resolve(__dirname3, "..", "..", "..");
38164
+ return path43.resolve(__dirname3, "..", "..", "..");
37927
38165
  }
37928
38166
  function tryExec(cmd) {
37929
38167
  try {
@@ -37937,7 +38175,7 @@ function tryExec(cmd) {
37937
38175
  }
37938
38176
  function readPackageJson3() {
37939
38177
  try {
37940
- const pkgPath = path42.join(packageRoot, "package.json");
38178
+ const pkgPath = path43.join(packageRoot, "package.json");
37941
38179
  return JSON.parse(readFileSync35(pkgPath, "utf8"));
37942
38180
  } catch {
37943
38181
  return null;
@@ -37953,7 +38191,7 @@ function checkShim(pkgName) {
37953
38191
  }
37954
38192
  const isWin = process.platform === "win32";
37955
38193
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
37956
- const shimPath = path42.join(prefix, shimName);
38194
+ const shimPath = path43.join(prefix, shimName);
37957
38195
  if (!existsSync45(shimPath)) {
37958
38196
  return FAIL(
37959
38197
  `shim not found at ${shimPath}
@@ -37981,8 +38219,8 @@ function checkShim(pkgName) {
37981
38219
  fix: npm install -g ${pkgName}@latest --force`
37982
38220
  );
37983
38221
  }
37984
- const resolved = path42.resolve(path42.dirname(shimPath), target);
37985
- const expected = path42.join(
38222
+ const resolved = path43.resolve(path43.dirname(shimPath), target);
38223
+ const expected = path43.join(
37986
38224
  prefix,
37987
38225
  "node_modules",
37988
38226
  pkgName,
@@ -38021,7 +38259,7 @@ function checkNode(pkg) {
38021
38259
  return OK(`node ${raw}`);
38022
38260
  }
38023
38261
  function checkBundle() {
38024
- const bundle = path42.join(packageRoot, "dist", "cli", "main.bundled.js");
38262
+ const bundle = path43.join(packageRoot, "dist", "cli", "main.bundled.js");
38025
38263
  if (!existsSync45(bundle)) {
38026
38264
  return FAIL(
38027
38265
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -38042,7 +38280,7 @@ function checkRuntimeDeps() {
38042
38280
  const missing = [];
38043
38281
  for (const dep of required2) {
38044
38282
  try {
38045
- const localReq = createRequire3(path42.join(packageRoot, "package.json"));
38283
+ const localReq = createRequire3(path43.join(packageRoot, "package.json"));
38046
38284
  localReq.resolve(dep);
38047
38285
  } catch {
38048
38286
  missing.push(dep);
@@ -38218,7 +38456,7 @@ var init_doctor = __esm({
38218
38456
  "use strict";
38219
38457
  init_prereqChecks();
38220
38458
  require3 = createRequire3(import.meta.url);
38221
- __dirname3 = path42.dirname(fileURLToPath2(import.meta.url));
38459
+ __dirname3 = path43.dirname(fileURLToPath2(import.meta.url));
38222
38460
  packageRoot = findPackageRoot(__dirname3);
38223
38461
  OK = (message) => ({
38224
38462
  ok: true,
@@ -44633,6 +44871,7 @@ init_krakenRadio();
44633
44871
  init_auditLogger();
44634
44872
  init_toolRegistry();
44635
44873
  init_planner();
44874
+ init_graphMemory();
44636
44875
  init_executor();
44637
44876
  init_graphStatus();
44638
44877
  async function handleKrakenGraph(ctx, prompt) {
@@ -44657,12 +44896,22 @@ async function handleKrakenGraph(ctx, prompt) {
44657
44896
  })
44658
44897
  };
44659
44898
  try {
44660
- const graph = await planTaskGraph({ prompt, graphId: `kraken-${Date.now().toString(36)}` });
44899
+ const previous = await loadGraphSnapshot(ctx.cwd);
44900
+ const previousAttempt = formatSnapshotForPlanner(previous);
44901
+ if (previousAttempt) {
44902
+ appendSystem(ctx.setMessages, "[kraken] resuming from the previous unfinished graph");
44903
+ }
44904
+ const graph = await planTaskGraph({
44905
+ prompt,
44906
+ graphId: `kraken-${Date.now().toString(36)}`,
44907
+ ...previousAttempt ? { previousAttempt } : {}
44908
+ });
44661
44909
  appendSystem(ctx.setMessages, formatKrakenGraphAscii(graph));
44662
44910
  const executor = new KrakenGraphExecutor({
44663
44911
  taskToolDeps,
44664
44912
  parentCwd: ctx.cwd,
44665
- sessionId: ctx.sessionId
44913
+ sessionId: ctx.sessionId,
44914
+ goal: prompt
44666
44915
  });
44667
44916
  const summary = await executor.execute(graph);
44668
44917
  appendSystem(
@@ -44976,17 +45225,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
44976
45225
  }
44977
45226
 
44978
45227
  // src/cli/slashHandlers/promoteMember.ts
44979
- import { promises as fs19 } from "node:fs";
44980
- import path37 from "node:path";
45228
+ import { promises as fs20 } from "node:fs";
45229
+ import path38 from "node:path";
44981
45230
  import os10 from "node:os";
44982
45231
  async function handlePromoteMember(ctx, memberId) {
44983
45232
  try {
44984
45233
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
44985
45234
  const { skill, markdown } = promoteMember2(memberId);
44986
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path37.join(os10.homedir(), ".tmp", "zelari-code", "skills");
44987
- await fs19.mkdir(skillDir, { recursive: true });
44988
- const filePath = path37.join(skillDir, `${skill.id}.md`);
44989
- await fs19.writeFile(filePath, markdown, "utf8");
45235
+ const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path38.join(os10.homedir(), ".tmp", "zelari-code", "skills");
45236
+ await fs20.mkdir(skillDir, { recursive: true });
45237
+ const filePath = path38.join(skillDir, `${skill.id}.md`);
45238
+ await fs20.writeFile(filePath, markdown, "utf8");
44990
45239
  appendSystem(
44991
45240
  ctx.setMessages,
44992
45241
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -45002,25 +45251,25 @@ async function handlePromoteMember(ctx, memberId) {
45002
45251
  }
45003
45252
 
45004
45253
  // src/cli/branchManager.ts
45005
- import { promises as fs20, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
45006
- import path38 from "node:path";
45254
+ import { promises as fs21, existsSync as existsSync38, readFileSync as readFileSync30, writeFileSync as writeFileSync19, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync3 } from "node:fs";
45255
+ import path39 from "node:path";
45007
45256
  import os11 from "node:os";
45008
45257
  var META_FILENAME = "meta.json";
45009
45258
  var SESSIONS_SUBDIR = "sessions";
45010
45259
  function getBranchesBaseDir() {
45011
- return process.env.ANATHEMA_BRANCHES_DIR ?? path38.join(os11.homedir(), ".tmp", "zelari-code", "branches");
45260
+ return process.env.ANATHEMA_BRANCHES_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "branches");
45012
45261
  }
45013
45262
  function getSessionsBaseDir() {
45014
- return process.env.ANATHEMA_SESSIONS_DIR ?? path38.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
45263
+ return process.env.ANATHEMA_SESSIONS_DIR ?? path39.join(os11.homedir(), ".tmp", "zelari-code", "sessions");
45015
45264
  }
45016
45265
  function branchPathFor(name, baseDir) {
45017
- return path38.join(baseDir, name);
45266
+ return path39.join(baseDir, name);
45018
45267
  }
45019
45268
  function metaPathFor(name, baseDir) {
45020
- return path38.join(baseDir, name, META_FILENAME);
45269
+ return path39.join(baseDir, name, META_FILENAME);
45021
45270
  }
45022
45271
  function sessionsPathFor(name, baseDir) {
45023
- return path38.join(baseDir, name, SESSIONS_SUBDIR);
45272
+ return path39.join(baseDir, name, SESSIONS_SUBDIR);
45024
45273
  }
45025
45274
  function readBranchMeta(name, baseDir) {
45026
45275
  const metaPath = metaPathFor(name, baseDir);
@@ -45045,13 +45294,13 @@ function readBranchMeta(name, baseDir) {
45045
45294
  }
45046
45295
  function writeBranchMeta(name, baseDir, meta3) {
45047
45296
  const metaPath = metaPathFor(name, baseDir);
45048
- mkdirSync16(path38.dirname(metaPath), { recursive: true });
45297
+ mkdirSync16(path39.dirname(metaPath), { recursive: true });
45049
45298
  writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
45050
45299
  }
45051
45300
  async function countSessions(name, baseDir) {
45052
45301
  const sessionsPath = sessionsPathFor(name, baseDir);
45053
45302
  try {
45054
- const entries = await fs20.readdir(sessionsPath);
45303
+ const entries = await fs21.readdir(sessionsPath);
45055
45304
  return entries.filter((e) => e.endsWith(".jsonl")).length;
45056
45305
  } catch (err) {
45057
45306
  if (err.code === "ENOENT") return 0;
@@ -45096,15 +45345,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45096
45345
  if (branchExists(name, baseDir)) {
45097
45346
  throw new BranchAlreadyExistsError(name);
45098
45347
  }
45099
- const sourcePath = path38.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45348
+ const sourcePath = path39.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45100
45349
  if (!existsSync38(sourcePath)) {
45101
45350
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
45102
45351
  }
45103
45352
  const branchPath = branchPathFor(name, baseDir);
45104
45353
  const branchSessionsPath = sessionsPathFor(name, baseDir);
45105
45354
  mkdirSync16(branchSessionsPath, { recursive: true });
45106
- const destPath = path38.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45107
- await fs20.copyFile(sourcePath, destPath);
45355
+ const destPath = path39.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45356
+ await fs21.copyFile(sourcePath, destPath);
45108
45357
  const meta3 = {
45109
45358
  name,
45110
45359
  createdAt: Date.now(),
@@ -45122,7 +45371,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45122
45371
  async function listBranches(baseDir = getBranchesBaseDir()) {
45123
45372
  let entries;
45124
45373
  try {
45125
- entries = await fs20.readdir(baseDir);
45374
+ entries = await fs21.readdir(baseDir);
45126
45375
  } catch (err) {
45127
45376
  if (err.code === "ENOENT") return [];
45128
45377
  throw err;
@@ -45203,26 +45452,26 @@ async function handleBranchCheckout(ctx, branchName) {
45203
45452
  }
45204
45453
 
45205
45454
  // src/cli/slashHandlers/workspace.ts
45206
- import { promises as fs21 } from "node:fs";
45207
- import path39 from "node:path";
45455
+ import { promises as fs22 } from "node:fs";
45456
+ import path40 from "node:path";
45208
45457
  async function handleWorkspaceShow(ctx, what) {
45209
45458
  try {
45210
- const zelari = path39.join(process.cwd(), ".zelari");
45459
+ const zelari = path40.join(process.cwd(), ".zelari");
45211
45460
  let content;
45212
45461
  switch (what) {
45213
45462
  case "plan": {
45214
- const planPath = path39.join(zelari, "plan.md");
45463
+ const planPath = path40.join(zelari, "plan.md");
45215
45464
  try {
45216
- content = await fs21.readFile(planPath, "utf-8");
45465
+ content = await fs22.readFile(planPath, "utf-8");
45217
45466
  } catch {
45218
45467
  content = "(no plan.md yet \u2014 run a council session first)";
45219
45468
  }
45220
45469
  break;
45221
45470
  }
45222
45471
  case "decisions": {
45223
- const decisionsDir = path39.join(zelari, "decisions");
45472
+ const decisionsDir = path40.join(zelari, "decisions");
45224
45473
  try {
45225
- const files = (await fs21.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
45474
+ const files = (await fs22.readdir(decisionsDir)).filter((f) => f.endsWith(".md")).sort();
45226
45475
  if (files.length === 0) {
45227
45476
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
45228
45477
  } else {
@@ -45230,7 +45479,7 @@ async function handleWorkspaceShow(ctx, what) {
45230
45479
  `];
45231
45480
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
45232
45481
  for (const f of files) {
45233
- const raw = await fs21.readFile(path39.join(decisionsDir, f), "utf-8");
45482
+ const raw = await fs22.readFile(path40.join(decisionsDir, f), "utf-8");
45234
45483
  const { meta: meta3, body } = parseFrontmatter2(raw);
45235
45484
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
45236
45485
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -45243,27 +45492,27 @@ async function handleWorkspaceShow(ctx, what) {
45243
45492
  break;
45244
45493
  }
45245
45494
  case "risks": {
45246
- const risksPath = path39.join(zelari, "risks.md");
45495
+ const risksPath = path40.join(zelari, "risks.md");
45247
45496
  try {
45248
- content = await fs21.readFile(risksPath, "utf-8");
45497
+ content = await fs22.readFile(risksPath, "utf-8");
45249
45498
  } catch {
45250
45499
  content = "(no risks.md yet)";
45251
45500
  }
45252
45501
  break;
45253
45502
  }
45254
45503
  case "agents": {
45255
- const agentsPath = path39.join(process.cwd(), "AGENTS.MD");
45504
+ const agentsPath = path40.join(process.cwd(), "AGENTS.MD");
45256
45505
  try {
45257
- content = await fs21.readFile(agentsPath, "utf-8");
45506
+ content = await fs22.readFile(agentsPath, "utf-8");
45258
45507
  } catch {
45259
45508
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
45260
45509
  }
45261
45510
  break;
45262
45511
  }
45263
45512
  case "docs": {
45264
- const docsDir = path39.join(zelari, "docs");
45513
+ const docsDir = path40.join(zelari, "docs");
45265
45514
  try {
45266
- const files = (await fs21.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
45515
+ const files = (await fs22.readdir(docsDir)).filter((f) => f.endsWith(".md")).sort();
45267
45516
  content = files.length ? `# Docs (${files.length})
45268
45517
 
45269
45518
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -45303,8 +45552,8 @@ async function handleWorkspaceReset(ctx, force) {
45303
45552
  return;
45304
45553
  }
45305
45554
  try {
45306
- const target = path39.join(process.cwd(), ".zelari");
45307
- await fs21.rm(target, { recursive: true, force: true });
45555
+ const target = path40.join(process.cwd(), ".zelari");
45556
+ await fs22.rm(target, { recursive: true, force: true });
45308
45557
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
45309
45558
  } catch (err) {
45310
45559
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -45663,16 +45912,16 @@ function handleModelsRefresh(ctx) {
45663
45912
  }
45664
45913
 
45665
45914
  // src/cli/slashHandlers/skills.ts
45666
- import path40 from "node:path";
45915
+ import path41 from "node:path";
45667
45916
  import os12 from "node:os";
45668
45917
 
45669
45918
  // src/cli/skillHistory.ts
45670
- import { promises as fs22, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
45919
+ import { promises as fs23, existsSync as existsSync39, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
45671
45920
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
45672
45921
  async function readSkillHistory(file2) {
45673
45922
  let raw = "";
45674
45923
  try {
45675
- raw = await fs22.readFile(file2, "utf-8");
45924
+ raw = await fs23.readFile(file2, "utf-8");
45676
45925
  } catch {
45677
45926
  return [];
45678
45927
  }
@@ -45789,7 +46038,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
45789
46038
  });
45790
46039
  }
45791
46040
  async function handleSkillStats(ctx, skillId) {
45792
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path40.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
46041
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
45793
46042
  try {
45794
46043
  const records = await readSkillHistory(historyFile);
45795
46044
  const stats = getSkillStats(records, skillId);
@@ -45805,7 +46054,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
45805
46054
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
45806
46055
  return;
45807
46056
  }
45808
- const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path40.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
46057
+ const historyFile = process.env.ANATHEMA_SKILL_HISTORY_FILE ?? path41.join(os12.homedir(), ".tmp", "zelari-code", "skill-history.jsonl");
45809
46058
  try {
45810
46059
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
45811
46060
  appendSystem(ctx.setMessages, formatted);
@@ -47530,6 +47779,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47530
47779
  return 1;
47531
47780
  }
47532
47781
  const { planTaskGraph: planTaskGraph2 } = await Promise.resolve().then(() => (init_planner(), planner_exports));
47782
+ const { loadGraphSnapshot: loadGraphSnapshot2, formatSnapshotForPlanner: formatSnapshotForPlanner2 } = await Promise.resolve().then(() => (init_graphMemory(), graphMemory_exports));
47533
47783
  const { formatKrakenGraphAscii: formatKrakenGraphAscii2 } = await Promise.resolve().then(() => (init_graphStatus(), graphStatus_exports));
47534
47784
  const { AuditLogger: AuditLogger2 } = await Promise.resolve().then(() => (init_auditLogger(), auditLogger_exports));
47535
47785
  const { createKrakenSubAgentContextFactory: createKrakenSubAgentContextFactory2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
@@ -47545,7 +47795,15 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47545
47795
  };
47546
47796
  try {
47547
47797
  log(`planning kraken graph: ${prompt}`);
47548
- const graph = await planTaskGraph2({ prompt, provider, model });
47798
+ const previous = await loadGraphSnapshot2(cwd);
47799
+ const previousAttempt = formatSnapshotForPlanner2(previous);
47800
+ if (previousAttempt) log("resuming from the previous unfinished graph");
47801
+ const graph = await planTaskGraph2({
47802
+ prompt,
47803
+ provider,
47804
+ model,
47805
+ ...previousAttempt ? { previousAttempt } : {}
47806
+ });
47549
47807
  log(formatKrakenGraphAscii2(graph));
47550
47808
  const audit = new AuditLogger2();
47551
47809
  const executor = new KrakenGraphExecutor2({
@@ -47564,7 +47822,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47564
47822
  })
47565
47823
  },
47566
47824
  parentCwd: cwd,
47567
- sessionId
47825
+ sessionId,
47826
+ goal: prompt
47568
47827
  });
47569
47828
  const summary = await executor.execute(graph);
47570
47829
  const finalAscii = formatKrakenGraphAscii2(summary.graph);
@@ -48709,7 +48968,7 @@ function upsertSkill(opts) {
48709
48968
  }
48710
48969
  dir = getProjectSkillsDir(root);
48711
48970
  }
48712
- const path43 = skillFilePath(dir, name);
48971
+ const path44 = skillFilePath(dir, name);
48713
48972
  const content = serializeSkillMd({
48714
48973
  name,
48715
48974
  description,
@@ -48718,13 +48977,13 @@ function upsertSkill(opts) {
48718
48977
  tools: opts.tools,
48719
48978
  cost: opts.cost
48720
48979
  });
48721
- const parsed = parseSkillMd(content, path43);
48980
+ const parsed = parseSkillMd(content, path44);
48722
48981
  if (!parsed) {
48723
48982
  return { ok: false, error: "Generated SKILL.md failed validation" };
48724
48983
  }
48725
- mkdirSync18(dirname9(path43), { recursive: true });
48726
- writeFileSync20(path43, content, "utf8");
48727
- return { ok: true, path: path43 };
48984
+ mkdirSync18(dirname9(path44), { recursive: true });
48985
+ writeFileSync20(path44, content, "utf8");
48986
+ return { ok: true, path: path44 };
48728
48987
  }
48729
48988
  function removeSkill(opts) {
48730
48989
  const name = opts.name.trim().toLowerCase();
@@ -48742,8 +49001,8 @@ function removeSkill(opts) {
48742
49001
  dir = getProjectSkillsDir(root);
48743
49002
  }
48744
49003
  const skillDir = join34(dir, name);
48745
- const path43 = skillFilePath(dir, name);
48746
- if (!existsSync42(path43) && !existsSync42(skillDir)) {
49004
+ const path44 = skillFilePath(dir, name);
49005
+ if (!existsSync42(path44) && !existsSync42(skillDir)) {
48747
49006
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
48748
49007
  }
48749
49008
  try {
@@ -48754,7 +49013,7 @@ function removeSkill(opts) {
48754
49013
  error: err instanceof Error ? err.message : String(err)
48755
49014
  };
48756
49015
  }
48757
- return { ok: true, path: path43 };
49016
+ return { ok: true, path: path44 };
48758
49017
  }
48759
49018
 
48760
49019
  // src/cli/generateSkillFromUrl.ts
@@ -48844,8 +49103,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
48844
49103
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
48845
49104
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
48846
49105
  try {
48847
- const path43 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
48848
- name = path43 && /^[a-z0-9]/.test(path43) ? path43 : "imported-skill";
49106
+ const path44 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
49107
+ name = path44 && /^[a-z0-9]/.test(path44) ? path44 : "imported-skill";
48849
49108
  } catch {
48850
49109
  name = "imported-skill";
48851
49110
  }