zelari-code 1.27.1 → 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;
@@ -35328,6 +35416,23 @@ function extractBalancedJsonObject2(s) {
35328
35416
  }
35329
35417
  return null;
35330
35418
  }
35419
+ function escapeJsonControlChar(c) {
35420
+ if (c >= " ") return c;
35421
+ switch (c) {
35422
+ case "\n":
35423
+ return "\\n";
35424
+ case "\r":
35425
+ return "\\r";
35426
+ case " ":
35427
+ return "\\t";
35428
+ case "\b":
35429
+ return "\\b";
35430
+ case "\f":
35431
+ return "\\f";
35432
+ default:
35433
+ return `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`;
35434
+ }
35435
+ }
35331
35436
  function repairLooseJson(s) {
35332
35437
  let out = "";
35333
35438
  let i = 0;
@@ -35339,15 +35444,16 @@ function repairLooseJson(s) {
35339
35444
  i += 1;
35340
35445
  while (i < n) {
35341
35446
  const c = s[i];
35342
- out += c;
35343
35447
  i += 1;
35344
35448
  if (c === "\\") {
35449
+ out += c;
35345
35450
  if (i < n) {
35346
35451
  out += s[i];
35347
35452
  i += 1;
35348
35453
  }
35349
35454
  continue;
35350
35455
  }
35456
+ out += escapeJsonControlChar(c);
35351
35457
  if (c === '"') break;
35352
35458
  }
35353
35459
  continue;
@@ -35366,7 +35472,7 @@ function repairLooseJson(s) {
35366
35472
  i += 1;
35367
35473
  break;
35368
35474
  }
35369
- value += c;
35475
+ value += escapeJsonControlChar(c);
35370
35476
  i += 1;
35371
35477
  }
35372
35478
  out += `"${value.replace(/"/g, '\\"')}"`;
@@ -35509,10 +35615,13 @@ async function createDefaultLlmClient(opts) {
35509
35615
  }
35510
35616
  };
35511
35617
  }
35512
- function buildPlannerUserPrompt(prompt) {
35618
+ function buildPlannerUserPrompt(prompt, previousAttempt) {
35619
+ const prev2 = previousAttempt?.trim() ? `
35620
+ ${previousAttempt.trim()}
35621
+ ` : "";
35513
35622
  return `Goal:
35514
35623
  ${prompt.trim()}
35515
-
35624
+ ${prev2}
35516
35625
  Return ONLY the JSON object described in the system prompt.`;
35517
35626
  }
35518
35627
  function uniqueId(base, existing) {
@@ -35579,14 +35688,19 @@ async function planTaskGraph(opts) {
35579
35688
  const client = opts.llmClient ?? await createDefaultLlmClient({ provider: opts.provider, model: opts.model });
35580
35689
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
35581
35690
  const graphId = opts.graphId ?? `kraken-${Date.now().toString(36)}`;
35582
- const userBase = buildPlannerUserPrompt(opts.prompt);
35691
+ const userBase = buildPlannerUserPrompt(opts.prompt, opts.previousAttempt);
35583
35692
  let lastError;
35584
35693
  let userMessage = userBase;
35585
35694
  for (let attempt = 1; attempt <= MAX_PLAN_ATTEMPTS; attempt++) {
35586
35695
  try {
35587
35696
  const text = await client.complete({ system: KRAKEN_PLANNER_SYSTEM_PROMPT, user: userMessage });
35588
- const parsedJson = extractJsonObject(text);
35697
+ const parsedJson = extractJsonObject(text, { requireKey: "nodes" });
35589
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
+ }
35590
35704
  const graph = buildGraphFromPlan(graphId, validated.nodes);
35591
35705
  const check2 = validateGraph(graph, { maxNodes });
35592
35706
  if (!check2.ok) {
@@ -35664,6 +35778,93 @@ var init_planner = __esm({
35664
35778
  }
35665
35779
  });
35666
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
+
35667
35868
  // src/cli/kraken/tentacle.ts
35668
35869
  var init_tentacle = __esm({
35669
35870
  "src/cli/kraken/tentacle.ts"() {
@@ -35675,18 +35876,21 @@ var init_tentacle = __esm({
35675
35876
  // src/cli/kraken/executor.ts
35676
35877
  var executor_exports = {};
35677
35878
  __export(executor_exports, {
35879
+ DEFAULT_CANCEL_GRACE_MS: () => DEFAULT_CANCEL_GRACE_MS,
35678
35880
  DEFAULT_FIX_BUDGET: () => DEFAULT_FIX_BUDGET,
35679
35881
  DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
35680
35882
  DEFAULT_NODE_TIMEOUT_MS: () => DEFAULT_NODE_TIMEOUT_MS,
35883
+ DEFAULT_WRITER_NODE_TIMEOUT_MS: () => DEFAULT_WRITER_NODE_TIMEOUT_MS,
35681
35884
  KrakenGraphExecutor: () => KrakenGraphExecutor,
35682
35885
  isKrakenGraphEnabled: () => isKrakenGraphEnabled,
35683
35886
  isWorldModelGateEnabled: () => isWorldModelGateEnabled,
35887
+ resolveCancelGraceMs: () => resolveCancelGraceMs,
35684
35888
  resolveFixBudget: () => resolveFixBudget,
35685
35889
  resolveMaxParallel: () => resolveMaxParallel,
35686
35890
  resolveNodeTimeoutMs: () => resolveNodeTimeoutMs
35687
35891
  });
35688
35892
  import { existsSync as existsSync34 } from "node:fs";
35689
- import path34 from "node:path";
35893
+ import path35 from "node:path";
35690
35894
  function resolveMaxParallel(env = process.env) {
35691
35895
  const raw = env.ZELARI_KRAKEN_MAX_PARALLEL;
35692
35896
  if (raw === void 0 || raw === "") return DEFAULT_MAX_PARALLEL;
@@ -35699,11 +35903,27 @@ function resolveFixBudget(env = process.env) {
35699
35903
  const n = Number.parseInt(raw, 10);
35700
35904
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_FIX_BUDGET;
35701
35905
  }
35702
- function resolveNodeTimeoutMs(env = process.env) {
35703
- const raw = env.ZELARI_KRAKEN_NODE_TIMEOUT_MS;
35704
- if (raw === void 0 || raw === "") return DEFAULT_NODE_TIMEOUT_MS;
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;
35705
35909
  const n = Number.parseInt(raw, 10);
35706
- return Number.isFinite(n) && n >= 0 ? n : DEFAULT_NODE_TIMEOUT_MS;
35910
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_CANCEL_GRACE_MS;
35911
+ }
35912
+ function resolveNodeTimeoutMs(env = process.env, agent) {
35913
+ const raw = env.ZELARI_KRAKEN_NODE_TIMEOUT_MS;
35914
+ if (raw !== void 0 && raw !== "") {
35915
+ const n = Number.parseInt(raw, 10);
35916
+ if (Number.isFinite(n) && n >= 0) return n;
35917
+ }
35918
+ if (agent === "general") {
35919
+ const rawWriter = env.ZELARI_KRAKEN_WRITER_NODE_TIMEOUT_MS;
35920
+ if (rawWriter !== void 0 && rawWriter !== "") {
35921
+ const n = Number.parseInt(rawWriter, 10);
35922
+ if (Number.isFinite(n) && n >= 0) return n;
35923
+ }
35924
+ return DEFAULT_WRITER_NODE_TIMEOUT_MS;
35925
+ }
35926
+ return DEFAULT_NODE_TIMEOUT_MS;
35707
35927
  }
35708
35928
  function isKrakenGraphEnabled(env = process.env) {
35709
35929
  return env.ZELARI_KRAKEN_GRAPH !== "0";
@@ -35714,12 +35934,12 @@ function isWorldModelGateEnabled(cwd, env = process.env, checksExists = defaultC
35714
35934
  }
35715
35935
  function defaultChecksExists(cwd) {
35716
35936
  try {
35717
- return existsSync34(path34.join(cwd, ".zelari", "world", "checks.json"));
35937
+ return existsSync34(path35.join(cwd, ".zelari", "world", "checks.json"));
35718
35938
  } catch {
35719
35939
  return false;
35720
35940
  }
35721
35941
  }
35722
- var DEFAULT_MAX_PARALLEL, DEFAULT_FIX_BUDGET, DEFAULT_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;
35723
35943
  var init_executor = __esm({
35724
35944
  "src/cli/kraken/executor.ts"() {
35725
35945
  "use strict";
@@ -35729,15 +35949,21 @@ var init_executor = __esm({
35729
35949
  init_krakenRadio();
35730
35950
  init_worldModel();
35731
35951
  init_graphStatus();
35952
+ init_graphMemory();
35732
35953
  DEFAULT_MAX_PARALLEL = 12;
35733
35954
  DEFAULT_FIX_BUDGET = 3;
35734
35955
  DEFAULT_NODE_TIMEOUT_MS = 3e5;
35956
+ DEFAULT_WRITER_NODE_TIMEOUT_MS = 9e5;
35957
+ DEFAULT_CANCEL_GRACE_MS = 3e4;
35735
35958
  KrakenGraphExecutor = class {
35736
35959
  deps;
35737
35960
  parentCwd;
35738
35961
  sessionId;
35962
+ goal;
35739
35963
  maxParallel;
35964
+ /** Explicit all-kinds override; when undefined the budget is per-kind. */
35740
35965
  nodeTimeoutMs;
35966
+ cancelGraceMs;
35741
35967
  fixBudgetRemaining;
35742
35968
  worldModelGateOverride;
35743
35969
  runTentacleFn;
@@ -35749,8 +35975,10 @@ var init_executor = __esm({
35749
35975
  this.deps = opts.taskToolDeps;
35750
35976
  this.parentCwd = opts.parentCwd;
35751
35977
  this.sessionId = opts.sessionId;
35978
+ this.goal = opts.goal;
35752
35979
  this.maxParallel = opts.maxParallel ?? resolveMaxParallel();
35753
- this.nodeTimeoutMs = opts.nodeTimeoutMs ?? resolveNodeTimeoutMs();
35980
+ this.nodeTimeoutMs = opts.nodeTimeoutMs;
35981
+ this.cancelGraceMs = opts.cancelGraceMs;
35754
35982
  this.fixBudgetRemaining = opts.fixBudget ?? resolveFixBudget();
35755
35983
  this.worldModelGateOverride = opts.worldModelGate;
35756
35984
  this.runTentacleFn = opts.runTentacleFn ?? runTentacle;
@@ -35808,6 +36036,10 @@ var init_executor = __esm({
35808
36036
  });
35809
36037
  }
35810
36038
  endKrakenGraphLive(graph, converged);
36039
+ await saveGraphSnapshot(
36040
+ this.parentCwd,
36041
+ toGraphSnapshot(graph, { goal: this.goal ?? graph.id, converged })
36042
+ );
35811
36043
  return {
35812
36044
  graph,
35813
36045
  converged,
@@ -35824,6 +36056,7 @@ var init_executor = __esm({
35824
36056
  }
35825
36057
  const usesWorktree = node.kind === "general" || node.kind === "fix";
35826
36058
  const agent = node.kind === "fix" ? "general" : node.kind;
36059
+ const controller = new AbortController();
35827
36060
  const res = await this.withNodeTimeout(
35828
36061
  this.runTentacleFn({
35829
36062
  deps: this.deps,
@@ -35841,9 +36074,11 @@ var init_executor = __esm({
35841
36074
  // (Correction 4); explore/verify never create a worktree.
35842
36075
  deferMerge: usesWorktree,
35843
36076
  graphId: graph.id,
35844
- nodeId: node.id
36077
+ nodeId: node.id,
36078
+ signal: controller.signal
35845
36079
  }),
35846
- agent
36080
+ agent,
36081
+ controller
35847
36082
  );
35848
36083
  if (res.ok && usesWorktree) {
35849
36084
  this.nodeRunState.set(node.id, { worktreeHandle: res.worktreeHandle });
@@ -35851,25 +36086,50 @@ var init_executor = __esm({
35851
36086
  return res;
35852
36087
  }
35853
36088
  /**
35854
- * Bound a tentacle run to `nodeTimeoutMs` wall-clock. On timeout, resolves
35855
- * to a synthetic `TentacleFailure` so the normal retry/fix/cascade-skip
35856
- * path handles it this only bounds how long the EXECUTOR waits, it does
35857
- * not (cannot) forcibly cancel the underlying sub-agent run; the point is
35858
- * to guarantee `execute()` itself always settles so the caller's process
35859
- * can exit instead of hanging forever on one stuck node.
36089
+ * Bound a tentacle run to its wall-clock budget (explicit `nodeTimeoutMs`
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.
35860
36101
  */
35861
- withNodeTimeout(promise2, agent) {
35862
- if (this.nodeTimeoutMs <= 0) return promise2;
35863
- const ms = this.nodeTimeoutMs;
36102
+ async withNodeTimeout(promise2, agent, controller) {
36103
+ const ms = this.nodeTimeoutMs ?? resolveNodeTimeoutMs(process.env, agent);
36104
+ if (ms <= 0) return promise2;
35864
36105
  let timer;
36106
+ const TIMED_OUT = Symbol("timeout");
35865
36107
  const timeout = new Promise((resolve3) => {
35866
- timer = setTimeout(() => {
35867
- resolve3({ ok: false, agent, error: `tentacle timed out after ${ms}ms` });
35868
- }, ms);
36108
+ timer = setTimeout(() => resolve3(TIMED_OUT), ms);
35869
36109
  });
35870
- return Promise.race([promise2, timeout]).finally(() => {
36110
+ const raced = await Promise.race([promise2, timeout]).finally(() => {
35871
36111
  if (timer) clearTimeout(timer);
35872
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
+ };
35873
36133
  }
35874
36134
  /**
35875
36135
  * Sequentially merge every dep node's deferred worktree (in dep order) into
@@ -35933,6 +36193,16 @@ var init_executor = __esm({
35933
36193
  return;
35934
36194
  }
35935
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
+ }
35936
36206
  if (node.retryCount < node.maxRetries) {
35937
36207
  node.retryCount += 1;
35938
36208
  node.status = "pending";
@@ -36421,10 +36691,10 @@ var init_prereqChecks = __esm({
36421
36691
 
36422
36692
  // src/cli/plugins/prefs.ts
36423
36693
  import { existsSync as existsSync36, readFileSync as readFileSync29, writeFileSync as writeFileSync18, mkdirSync as mkdirSync15 } from "node:fs";
36424
- import path35 from "node:path";
36694
+ import path36 from "node:path";
36425
36695
  import os9 from "node:os";
36426
36696
  function getPluginPrefsPath() {
36427
- 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");
36428
36698
  }
36429
36699
  function getPluginPrefs() {
36430
36700
  const file2 = getPluginPrefsPath();
@@ -36445,7 +36715,7 @@ function getPluginPrefs() {
36445
36715
  }
36446
36716
  function writePluginPrefs(prefs) {
36447
36717
  const file2 = getPluginPrefsPath();
36448
- mkdirSync15(path35.dirname(file2), { recursive: true });
36718
+ mkdirSync15(path36.dirname(file2), { recursive: true });
36449
36719
  writeFileSync18(file2, JSON.stringify(prefs, null, 2), {
36450
36720
  encoding: "utf-8",
36451
36721
  mode: 384
@@ -36482,7 +36752,7 @@ __export(registry_exports, {
36482
36752
  isBinaryOnPath: () => isBinaryOnPath
36483
36753
  });
36484
36754
  import { existsSync as existsSync37 } from "node:fs";
36485
- import path36 from "node:path";
36755
+ import path37 from "node:path";
36486
36756
  function detectLocalBin(bin) {
36487
36757
  return (cwd) => {
36488
36758
  try {
@@ -36500,7 +36770,7 @@ function isBinaryOnPath(bin, opts = {}) {
36500
36770
  const platform = opts.platform ?? process.platform;
36501
36771
  const exists = opts.exists ?? existsSync37;
36502
36772
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
36503
- const pathMod = platform === "win32" ? path36.win32 : path36.posix;
36773
+ const pathMod = platform === "win32" ? path37.win32 : path37.posix;
36504
36774
  const sep2 = platform === "win32" ? ";" : ":";
36505
36775
  const dirs = pathEnv.split(sep2).filter((d) => d.length > 0);
36506
36776
  const candidates = [bin];
@@ -36809,10 +37079,10 @@ __export(triggerLock_exports, {
36809
37079
  lockPath: () => lockPath,
36810
37080
  releaseLock: () => releaseLock
36811
37081
  });
36812
- import { promises as fs23 } from "node:fs";
36813
- import * as path41 from "node:path";
37082
+ import { promises as fs24 } from "node:fs";
37083
+ import * as path42 from "node:path";
36814
37084
  function lockPath(projectRoot) {
36815
- return path41.join(projectRoot, ".zelari", "trigger.lock");
37085
+ return path42.join(projectRoot, ".zelari", "trigger.lock");
36816
37086
  }
36817
37087
  function isPidAlive(pid) {
36818
37088
  try {
@@ -36825,10 +37095,10 @@ function isPidAlive(pid) {
36825
37095
  }
36826
37096
  async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date()) {
36827
37097
  const lp = lockPath(projectRoot);
36828
- const dir = path41.dirname(lp);
36829
- await fs23.mkdir(dir, { recursive: true });
37098
+ const dir = path42.dirname(lp);
37099
+ await fs24.mkdir(dir, { recursive: true });
36830
37100
  try {
36831
- const raw = await fs23.readFile(lp, "utf8");
37101
+ const raw = await fs24.readFile(lp, "utf8");
36832
37102
  const existing = JSON.parse(raw);
36833
37103
  if (existing.pid && isPidAlive(existing.pid)) {
36834
37104
  return { acquired: false, heldBy: existing.pid, lockPath: lp };
@@ -36839,13 +37109,13 @@ async function acquireLock(projectRoot, now = () => /* @__PURE__ */ new Date())
36839
37109
  pid: process.pid,
36840
37110
  acquiredAt: now().toISOString()
36841
37111
  };
36842
- await fs23.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
37112
+ await fs24.writeFile(lp, JSON.stringify(payload, null, 2) + "\n", "utf8");
36843
37113
  return { acquired: true, lockPath: lp };
36844
37114
  }
36845
37115
  async function releaseLock(projectRoot) {
36846
37116
  const lp = lockPath(projectRoot);
36847
37117
  try {
36848
- await fs23.unlink(lp);
37118
+ await fs24.unlink(lp);
36849
37119
  } catch {
36850
37120
  }
36851
37121
  }
@@ -37133,12 +37403,12 @@ function ensureHome() {
37133
37403
  }
37134
37404
  }
37135
37405
  function loadCompanionConfig() {
37136
- const path43 = getCompanionConfigPath();
37137
- if (!existsSync43(path43)) {
37406
+ const path44 = getCompanionConfigPath();
37407
+ if (!existsSync43(path44)) {
37138
37408
  return { projects: [] };
37139
37409
  }
37140
37410
  try {
37141
- const raw = JSON.parse(readFileSync34(path43, "utf8"));
37411
+ const raw = JSON.parse(readFileSync34(path44, "utf8"));
37142
37412
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
37143
37413
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
37144
37414
  ).map((p3) => ({
@@ -37176,16 +37446,16 @@ function loadOrCreateToken(explicit) {
37176
37446
  return { token: explicit.trim(), created: false };
37177
37447
  }
37178
37448
  ensureHome();
37179
- const path43 = getCompanionTokenPath();
37180
- if (existsSync43(path43)) {
37181
- const t = readFileSync34(path43, "utf8").trim();
37449
+ const path44 = getCompanionTokenPath();
37450
+ if (existsSync43(path44)) {
37451
+ const t = readFileSync34(path44, "utf8").trim();
37182
37452
  if (t) return { token: t, created: false };
37183
37453
  }
37184
37454
  const token = randomBytes3(24).toString("base64url");
37185
- writeFileSync21(path43, token + "\n", "utf8");
37455
+ writeFileSync21(path44, token + "\n", "utf8");
37186
37456
  try {
37187
- const fs24 = __require("node:fs");
37188
- fs24.chmodSync?.(path43, 384);
37457
+ const fs25 = __require("node:fs");
37458
+ fs25.chmodSync?.(path44, 384);
37189
37459
  } catch {
37190
37460
  }
37191
37461
  return { token, created: true };
@@ -37210,17 +37480,17 @@ function mergeProjects(cfg, extraPaths) {
37210
37480
  byId.set(p3.id, p3);
37211
37481
  }
37212
37482
  for (const raw of extraPaths) {
37213
- const path43 = raw.trim();
37214
- if (!path43) continue;
37215
- let id = slugFromPath(path43);
37483
+ const path44 = raw.trim();
37484
+ if (!path44) continue;
37485
+ let id = slugFromPath(path44);
37216
37486
  let n = 2;
37217
- while (byId.has(id) && byId.get(id).path !== path43) {
37218
- id = `${slugFromPath(path43)}-${n++}`;
37487
+ while (byId.has(id) && byId.get(id).path !== path44) {
37488
+ id = `${slugFromPath(path44)}-${n++}`;
37219
37489
  }
37220
37490
  byId.set(id, {
37221
37491
  id,
37222
- name: slugFromPath(path43),
37223
- path: path43
37492
+ name: slugFromPath(path44),
37493
+ path: path44
37224
37494
  });
37225
37495
  }
37226
37496
  return [...byId.values()];
@@ -37597,9 +37867,9 @@ async function runCompanionServe(opts = {}) {
37597
37867
  return;
37598
37868
  }
37599
37869
  const url2 = parseUrl(req);
37600
- const path43 = url2.pathname.replace(/\/+$/, "") || "/";
37870
+ const path44 = url2.pathname.replace(/\/+$/, "") || "/";
37601
37871
  try {
37602
- if (req.method === "GET" && (path43 === "/health" || path43 === "/v1/health")) {
37872
+ if (req.method === "GET" && (path44 === "/health" || path44 === "/v1/health")) {
37603
37873
  sendJson(res, 200, {
37604
37874
  ok: true,
37605
37875
  service: "zelari-companion",
@@ -37611,18 +37881,18 @@ async function runCompanionServe(opts = {}) {
37611
37881
  });
37612
37882
  return;
37613
37883
  }
37614
- if (path43.startsWith("/v1")) {
37884
+ if (path44.startsWith("/v1")) {
37615
37885
  if (!tokenMatches(token, getBearer(req))) {
37616
37886
  sendJson(res, 401, { ok: false, error: "unauthorized" });
37617
37887
  return;
37618
37888
  }
37619
37889
  }
37620
- if (req.method === "GET" && path43 === "/v1/config") {
37890
+ if (req.method === "GET" && path44 === "/v1/config") {
37621
37891
  const snap = buildDesktopConfigSnapshot();
37622
37892
  sendJson(res, 200, { ok: true, ...snap });
37623
37893
  return;
37624
37894
  }
37625
- if (req.method === "GET" && path43 === "/v1/projects") {
37895
+ if (req.method === "GET" && path44 === "/v1/projects") {
37626
37896
  sendJson(res, 200, {
37627
37897
  ok: true,
37628
37898
  projects: projects.map((p3) => ({
@@ -37633,7 +37903,7 @@ async function runCompanionServe(opts = {}) {
37633
37903
  });
37634
37904
  return;
37635
37905
  }
37636
- if (req.method === "GET" && path43 === "/v1/runs") {
37906
+ if (req.method === "GET" && path44 === "/v1/runs") {
37637
37907
  sendJson(res, 200, {
37638
37908
  ok: true,
37639
37909
  active: runs.getActive(),
@@ -37651,7 +37921,7 @@ async function runCompanionServe(opts = {}) {
37651
37921
  });
37652
37922
  return;
37653
37923
  }
37654
- if (req.method === "POST" && path43 === "/v1/runs") {
37924
+ if (req.method === "POST" && path44 === "/v1/runs") {
37655
37925
  const raw = await readBody(req);
37656
37926
  let body = {};
37657
37927
  try {
@@ -37698,7 +37968,7 @@ async function runCompanionServe(opts = {}) {
37698
37968
  });
37699
37969
  return;
37700
37970
  }
37701
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path43);
37971
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path44);
37702
37972
  if (req.method === "GET" && eventsMatch) {
37703
37973
  const runId = eventsMatch[1];
37704
37974
  const run = runs.getRun(runId);
@@ -37763,7 +38033,7 @@ async function runCompanionServe(opts = {}) {
37763
38033
  }, 500);
37764
38034
  return;
37765
38035
  }
37766
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path43);
38036
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path44);
37767
38037
  if (req.method === "POST" && cancelMatch) {
37768
38038
  const runId = cancelMatch[1];
37769
38039
  const result = runs.cancel(runId);
@@ -37875,11 +38145,11 @@ import { execSync as execSync2 } from "node:child_process";
37875
38145
  import { existsSync as existsSync45, readFileSync as readFileSync35, readlinkSync, statSync as statSync7 } from "node:fs";
37876
38146
  import { createRequire as createRequire3 } from "node:module";
37877
38147
  import { fileURLToPath as fileURLToPath2 } from "node:url";
37878
- import path42 from "node:path";
38148
+ import path43 from "node:path";
37879
38149
  function findPackageRoot(start) {
37880
38150
  let dir = start;
37881
38151
  for (let i = 0; i < 6; i += 1) {
37882
- const candidate = path42.join(dir, "package.json");
38152
+ const candidate = path43.join(dir, "package.json");
37883
38153
  if (existsSync45(candidate)) {
37884
38154
  try {
37885
38155
  const pkg = JSON.parse(readFileSync35(candidate, "utf8"));
@@ -37887,11 +38157,11 @@ function findPackageRoot(start) {
37887
38157
  } catch {
37888
38158
  }
37889
38159
  }
37890
- const parent = path42.dirname(dir);
38160
+ const parent = path43.dirname(dir);
37891
38161
  if (parent === dir) break;
37892
38162
  dir = parent;
37893
38163
  }
37894
- return path42.resolve(__dirname3, "..", "..", "..");
38164
+ return path43.resolve(__dirname3, "..", "..", "..");
37895
38165
  }
37896
38166
  function tryExec(cmd) {
37897
38167
  try {
@@ -37905,7 +38175,7 @@ function tryExec(cmd) {
37905
38175
  }
37906
38176
  function readPackageJson3() {
37907
38177
  try {
37908
- const pkgPath = path42.join(packageRoot, "package.json");
38178
+ const pkgPath = path43.join(packageRoot, "package.json");
37909
38179
  return JSON.parse(readFileSync35(pkgPath, "utf8"));
37910
38180
  } catch {
37911
38181
  return null;
@@ -37921,7 +38191,7 @@ function checkShim(pkgName) {
37921
38191
  }
37922
38192
  const isWin = process.platform === "win32";
37923
38193
  const shimName = isWin ? "zelari-code.cmd" : "zelari-code";
37924
- const shimPath = path42.join(prefix, shimName);
38194
+ const shimPath = path43.join(prefix, shimName);
37925
38195
  if (!existsSync45(shimPath)) {
37926
38196
  return FAIL(
37927
38197
  `shim not found at ${shimPath}
@@ -37949,8 +38219,8 @@ function checkShim(pkgName) {
37949
38219
  fix: npm install -g ${pkgName}@latest --force`
37950
38220
  );
37951
38221
  }
37952
- const resolved = path42.resolve(path42.dirname(shimPath), target);
37953
- const expected = path42.join(
38222
+ const resolved = path43.resolve(path43.dirname(shimPath), target);
38223
+ const expected = path43.join(
37954
38224
  prefix,
37955
38225
  "node_modules",
37956
38226
  pkgName,
@@ -37989,7 +38259,7 @@ function checkNode(pkg) {
37989
38259
  return OK(`node ${raw}`);
37990
38260
  }
37991
38261
  function checkBundle() {
37992
- const bundle = path42.join(packageRoot, "dist", "cli", "main.bundled.js");
38262
+ const bundle = path43.join(packageRoot, "dist", "cli", "main.bundled.js");
37993
38263
  if (!existsSync45(bundle)) {
37994
38264
  return FAIL(
37995
38265
  `dist/cli/main.bundled.js missing at ${bundle}
@@ -38010,7 +38280,7 @@ function checkRuntimeDeps() {
38010
38280
  const missing = [];
38011
38281
  for (const dep of required2) {
38012
38282
  try {
38013
- const localReq = createRequire3(path42.join(packageRoot, "package.json"));
38283
+ const localReq = createRequire3(path43.join(packageRoot, "package.json"));
38014
38284
  localReq.resolve(dep);
38015
38285
  } catch {
38016
38286
  missing.push(dep);
@@ -38186,7 +38456,7 @@ var init_doctor = __esm({
38186
38456
  "use strict";
38187
38457
  init_prereqChecks();
38188
38458
  require3 = createRequire3(import.meta.url);
38189
- __dirname3 = path42.dirname(fileURLToPath2(import.meta.url));
38459
+ __dirname3 = path43.dirname(fileURLToPath2(import.meta.url));
38190
38460
  packageRoot = findPackageRoot(__dirname3);
38191
38461
  OK = (message) => ({
38192
38462
  ok: true,
@@ -44601,6 +44871,7 @@ init_krakenRadio();
44601
44871
  init_auditLogger();
44602
44872
  init_toolRegistry();
44603
44873
  init_planner();
44874
+ init_graphMemory();
44604
44875
  init_executor();
44605
44876
  init_graphStatus();
44606
44877
  async function handleKrakenGraph(ctx, prompt) {
@@ -44625,12 +44896,22 @@ async function handleKrakenGraph(ctx, prompt) {
44625
44896
  })
44626
44897
  };
44627
44898
  try {
44628
- 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
+ });
44629
44909
  appendSystem(ctx.setMessages, formatKrakenGraphAscii(graph));
44630
44910
  const executor = new KrakenGraphExecutor({
44631
44911
  taskToolDeps,
44632
44912
  parentCwd: ctx.cwd,
44633
- sessionId: ctx.sessionId
44913
+ sessionId: ctx.sessionId,
44914
+ goal: prompt
44634
44915
  });
44635
44916
  const summary = await executor.execute(graph);
44636
44917
  appendSystem(
@@ -44944,17 +45225,17 @@ ${result.output.split("\n").slice(-8).join("\n")}` : "";
44944
45225
  }
44945
45226
 
44946
45227
  // src/cli/slashHandlers/promoteMember.ts
44947
- import { promises as fs19 } from "node:fs";
44948
- import path37 from "node:path";
45228
+ import { promises as fs20 } from "node:fs";
45229
+ import path38 from "node:path";
44949
45230
  import os10 from "node:os";
44950
45231
  async function handlePromoteMember(ctx, memberId) {
44951
45232
  try {
44952
45233
  const { promoteMember: promoteMember2 } = await Promise.resolve().then(() => (init_council(), council_exports));
44953
45234
  const { skill, markdown } = promoteMember2(memberId);
44954
- const skillDir = process.env.ANATHEMA_SKILL_DIR ?? path37.join(os10.homedir(), ".tmp", "zelari-code", "skills");
44955
- await fs19.mkdir(skillDir, { recursive: true });
44956
- const filePath = path37.join(skillDir, `${skill.id}.md`);
44957
- 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");
44958
45239
  appendSystem(
44959
45240
  ctx.setMessages,
44960
45241
  `[promote-member] ${skill.name} (${memberId}) \u2192 ${filePath}
@@ -44970,25 +45251,25 @@ async function handlePromoteMember(ctx, memberId) {
44970
45251
  }
44971
45252
 
44972
45253
  // src/cli/branchManager.ts
44973
- 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";
44974
- 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";
44975
45256
  import os11 from "node:os";
44976
45257
  var META_FILENAME = "meta.json";
44977
45258
  var SESSIONS_SUBDIR = "sessions";
44978
45259
  function getBranchesBaseDir() {
44979
- 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");
44980
45261
  }
44981
45262
  function getSessionsBaseDir() {
44982
- 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");
44983
45264
  }
44984
45265
  function branchPathFor(name, baseDir) {
44985
- return path38.join(baseDir, name);
45266
+ return path39.join(baseDir, name);
44986
45267
  }
44987
45268
  function metaPathFor(name, baseDir) {
44988
- return path38.join(baseDir, name, META_FILENAME);
45269
+ return path39.join(baseDir, name, META_FILENAME);
44989
45270
  }
44990
45271
  function sessionsPathFor(name, baseDir) {
44991
- return path38.join(baseDir, name, SESSIONS_SUBDIR);
45272
+ return path39.join(baseDir, name, SESSIONS_SUBDIR);
44992
45273
  }
44993
45274
  function readBranchMeta(name, baseDir) {
44994
45275
  const metaPath = metaPathFor(name, baseDir);
@@ -45013,13 +45294,13 @@ function readBranchMeta(name, baseDir) {
45013
45294
  }
45014
45295
  function writeBranchMeta(name, baseDir, meta3) {
45015
45296
  const metaPath = metaPathFor(name, baseDir);
45016
- mkdirSync16(path38.dirname(metaPath), { recursive: true });
45297
+ mkdirSync16(path39.dirname(metaPath), { recursive: true });
45017
45298
  writeFileSync19(metaPath, JSON.stringify(meta3, null, 2), "utf-8");
45018
45299
  }
45019
45300
  async function countSessions(name, baseDir) {
45020
45301
  const sessionsPath = sessionsPathFor(name, baseDir);
45021
45302
  try {
45022
- const entries = await fs20.readdir(sessionsPath);
45303
+ const entries = await fs21.readdir(sessionsPath);
45023
45304
  return entries.filter((e) => e.endsWith(".jsonl")).length;
45024
45305
  } catch (err) {
45025
45306
  if (err.code === "ENOENT") return 0;
@@ -45064,15 +45345,15 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45064
45345
  if (branchExists(name, baseDir)) {
45065
45346
  throw new BranchAlreadyExistsError(name);
45066
45347
  }
45067
- const sourcePath = path38.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45348
+ const sourcePath = path39.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
45068
45349
  if (!existsSync38(sourcePath)) {
45069
45350
  throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
45070
45351
  }
45071
45352
  const branchPath = branchPathFor(name, baseDir);
45072
45353
  const branchSessionsPath = sessionsPathFor(name, baseDir);
45073
45354
  mkdirSync16(branchSessionsPath, { recursive: true });
45074
- const destPath = path38.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45075
- await fs20.copyFile(sourcePath, destPath);
45355
+ const destPath = path39.join(branchSessionsPath, `${fromSessionId}.jsonl`);
45356
+ await fs21.copyFile(sourcePath, destPath);
45076
45357
  const meta3 = {
45077
45358
  name,
45078
45359
  createdAt: Date.now(),
@@ -45090,7 +45371,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
45090
45371
  async function listBranches(baseDir = getBranchesBaseDir()) {
45091
45372
  let entries;
45092
45373
  try {
45093
- entries = await fs20.readdir(baseDir);
45374
+ entries = await fs21.readdir(baseDir);
45094
45375
  } catch (err) {
45095
45376
  if (err.code === "ENOENT") return [];
45096
45377
  throw err;
@@ -45171,26 +45452,26 @@ async function handleBranchCheckout(ctx, branchName) {
45171
45452
  }
45172
45453
 
45173
45454
  // src/cli/slashHandlers/workspace.ts
45174
- import { promises as fs21 } from "node:fs";
45175
- import path39 from "node:path";
45455
+ import { promises as fs22 } from "node:fs";
45456
+ import path40 from "node:path";
45176
45457
  async function handleWorkspaceShow(ctx, what) {
45177
45458
  try {
45178
- const zelari = path39.join(process.cwd(), ".zelari");
45459
+ const zelari = path40.join(process.cwd(), ".zelari");
45179
45460
  let content;
45180
45461
  switch (what) {
45181
45462
  case "plan": {
45182
- const planPath = path39.join(zelari, "plan.md");
45463
+ const planPath = path40.join(zelari, "plan.md");
45183
45464
  try {
45184
- content = await fs21.readFile(planPath, "utf-8");
45465
+ content = await fs22.readFile(planPath, "utf-8");
45185
45466
  } catch {
45186
45467
  content = "(no plan.md yet \u2014 run a council session first)";
45187
45468
  }
45188
45469
  break;
45189
45470
  }
45190
45471
  case "decisions": {
45191
- const decisionsDir = path39.join(zelari, "decisions");
45472
+ const decisionsDir = path40.join(zelari, "decisions");
45192
45473
  try {
45193
- 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();
45194
45475
  if (files.length === 0) {
45195
45476
  content = "(no ADRs yet \u2014 invoke /council to generate some)";
45196
45477
  } else {
@@ -45198,7 +45479,7 @@ async function handleWorkspaceShow(ctx, what) {
45198
45479
  `];
45199
45480
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
45200
45481
  for (const f of files) {
45201
- const raw = await fs21.readFile(path39.join(decisionsDir, f), "utf-8");
45482
+ const raw = await fs22.readFile(path40.join(decisionsDir, f), "utf-8");
45202
45483
  const { meta: meta3, body } = parseFrontmatter2(raw);
45203
45484
  const title = meta3.title ?? body.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? f;
45204
45485
  lines.push(`- **${f.replace(/\.md$/, "")}** [${meta3.status ?? "unknown"}] ${title}`);
@@ -45211,27 +45492,27 @@ async function handleWorkspaceShow(ctx, what) {
45211
45492
  break;
45212
45493
  }
45213
45494
  case "risks": {
45214
- const risksPath = path39.join(zelari, "risks.md");
45495
+ const risksPath = path40.join(zelari, "risks.md");
45215
45496
  try {
45216
- content = await fs21.readFile(risksPath, "utf-8");
45497
+ content = await fs22.readFile(risksPath, "utf-8");
45217
45498
  } catch {
45218
45499
  content = "(no risks.md yet)";
45219
45500
  }
45220
45501
  break;
45221
45502
  }
45222
45503
  case "agents": {
45223
- const agentsPath = path39.join(process.cwd(), "AGENTS.MD");
45504
+ const agentsPath = path40.join(process.cwd(), "AGENTS.MD");
45224
45505
  try {
45225
- content = await fs21.readFile(agentsPath, "utf-8");
45506
+ content = await fs22.readFile(agentsPath, "utf-8");
45226
45507
  } catch {
45227
45508
  content = "(no AGENTS.MD yet at project root \u2014 run `/workspace sync` after a council session)";
45228
45509
  }
45229
45510
  break;
45230
45511
  }
45231
45512
  case "docs": {
45232
- const docsDir = path39.join(zelari, "docs");
45513
+ const docsDir = path40.join(zelari, "docs");
45233
45514
  try {
45234
- 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();
45235
45516
  content = files.length ? `# Docs (${files.length})
45236
45517
 
45237
45518
  ` + files.map((f) => `- ${f}`).join("\n") : "(no docs drafts yet)";
@@ -45271,8 +45552,8 @@ async function handleWorkspaceReset(ctx, force) {
45271
45552
  return;
45272
45553
  }
45273
45554
  try {
45274
- const target = path39.join(process.cwd(), ".zelari");
45275
- 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 });
45276
45557
  appendSystem(ctx.setMessages, "[workspace] .zelari/ removed");
45277
45558
  } catch (err) {
45278
45559
  appendSystem(ctx.setMessages, `[workspace reset error] ${err instanceof Error ? err.message : String(err)}`);
@@ -45631,16 +45912,16 @@ function handleModelsRefresh(ctx) {
45631
45912
  }
45632
45913
 
45633
45914
  // src/cli/slashHandlers/skills.ts
45634
- import path40 from "node:path";
45915
+ import path41 from "node:path";
45635
45916
  import os12 from "node:os";
45636
45917
 
45637
45918
  // src/cli/skillHistory.ts
45638
- 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";
45639
45920
  var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
45640
45921
  async function readSkillHistory(file2) {
45641
45922
  let raw = "";
45642
45923
  try {
45643
- raw = await fs22.readFile(file2, "utf-8");
45924
+ raw = await fs23.readFile(file2, "utf-8");
45644
45925
  } catch {
45645
45926
  return [];
45646
45927
  }
@@ -45757,7 +46038,7 @@ function handleSkillPicker(ctx, skills, openPicker, fallbackMessage) {
45757
46038
  });
45758
46039
  }
45759
46040
  async function handleSkillStats(ctx, skillId) {
45760
- 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");
45761
46042
  try {
45762
46043
  const records = await readSkillHistory(historyFile);
45763
46044
  const stats = getSkillStats(records, skillId);
@@ -45773,7 +46054,7 @@ async function handleSkillCompare(ctx, ids, fallbackMessage) {
45773
46054
  appendSystem(ctx.setMessages, fallbackMessage ?? "[skill-compare] missing args");
45774
46055
  return;
45775
46056
  }
45776
- 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");
45777
46058
  try {
45778
46059
  const formatted = await compareSkillsFromFile(ids[0], ids[1], historyFile);
45779
46060
  appendSystem(ctx.setMessages, formatted);
@@ -47498,6 +47779,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47498
47779
  return 1;
47499
47780
  }
47500
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));
47501
47783
  const { formatKrakenGraphAscii: formatKrakenGraphAscii2 } = await Promise.resolve().then(() => (init_graphStatus(), graphStatus_exports));
47502
47784
  const { AuditLogger: AuditLogger2 } = await Promise.resolve().then(() => (init_auditLogger(), auditLogger_exports));
47503
47785
  const { createKrakenSubAgentContextFactory: createKrakenSubAgentContextFactory2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
@@ -47513,7 +47795,15 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47513
47795
  };
47514
47796
  try {
47515
47797
  log(`planning kraken graph: ${prompt}`);
47516
- 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
+ });
47517
47807
  log(formatKrakenGraphAscii2(graph));
47518
47808
  const audit = new AuditLogger2();
47519
47809
  const executor = new KrakenGraphExecutor2({
@@ -47532,7 +47822,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
47532
47822
  })
47533
47823
  },
47534
47824
  parentCwd: cwd,
47535
- sessionId
47825
+ sessionId,
47826
+ goal: prompt
47536
47827
  });
47537
47828
  const summary = await executor.execute(graph);
47538
47829
  const finalAscii = formatKrakenGraphAscii2(summary.graph);
@@ -48677,7 +48968,7 @@ function upsertSkill(opts) {
48677
48968
  }
48678
48969
  dir = getProjectSkillsDir(root);
48679
48970
  }
48680
- const path43 = skillFilePath(dir, name);
48971
+ const path44 = skillFilePath(dir, name);
48681
48972
  const content = serializeSkillMd({
48682
48973
  name,
48683
48974
  description,
@@ -48686,13 +48977,13 @@ function upsertSkill(opts) {
48686
48977
  tools: opts.tools,
48687
48978
  cost: opts.cost
48688
48979
  });
48689
- const parsed = parseSkillMd(content, path43);
48980
+ const parsed = parseSkillMd(content, path44);
48690
48981
  if (!parsed) {
48691
48982
  return { ok: false, error: "Generated SKILL.md failed validation" };
48692
48983
  }
48693
- mkdirSync18(dirname9(path43), { recursive: true });
48694
- writeFileSync20(path43, content, "utf8");
48695
- return { ok: true, path: path43 };
48984
+ mkdirSync18(dirname9(path44), { recursive: true });
48985
+ writeFileSync20(path44, content, "utf8");
48986
+ return { ok: true, path: path44 };
48696
48987
  }
48697
48988
  function removeSkill(opts) {
48698
48989
  const name = opts.name.trim().toLowerCase();
@@ -48710,8 +49001,8 @@ function removeSkill(opts) {
48710
49001
  dir = getProjectSkillsDir(root);
48711
49002
  }
48712
49003
  const skillDir = join34(dir, name);
48713
- const path43 = skillFilePath(dir, name);
48714
- if (!existsSync42(path43) && !existsSync42(skillDir)) {
49004
+ const path44 = skillFilePath(dir, name);
49005
+ if (!existsSync42(path44) && !existsSync42(skillDir)) {
48715
49006
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
48716
49007
  }
48717
49008
  try {
@@ -48722,7 +49013,7 @@ function removeSkill(opts) {
48722
49013
  error: err instanceof Error ? err.message : String(err)
48723
49014
  };
48724
49015
  }
48725
- return { ok: true, path: path43 };
49016
+ return { ok: true, path: path44 };
48726
49017
  }
48727
49018
 
48728
49019
  // src/cli/generateSkillFromUrl.ts
@@ -48812,8 +49103,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
48812
49103
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
48813
49104
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
48814
49105
  try {
48815
- 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);
48816
- 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";
48817
49108
  } catch {
48818
49109
  name = "imported-skill";
48819
49110
  }