zelari-code 2.17.0 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3288,10 +3288,10 @@ function mergeDefs(...defs) {
3288
3288
  function cloneDef(schema) {
3289
3289
  return mergeDefs(schema._zod.def);
3290
3290
  }
3291
- function getElementAtPath(obj, path90) {
3292
- if (!path90)
3291
+ function getElementAtPath(obj, path91) {
3292
+ if (!path91)
3293
3293
  return obj;
3294
- return path90.reduce((acc, key) => acc?.[key], obj);
3294
+ return path91.reduce((acc, key) => acc?.[key], obj);
3295
3295
  }
3296
3296
  function promiseAllObject(promisesObj) {
3297
3297
  const keys = Object.keys(promisesObj);
@@ -3619,11 +3619,11 @@ function explicitlyAborted(x, startIndex = 0) {
3619
3619
  }
3620
3620
  return false;
3621
3621
  }
3622
- function prefixIssues(path90, issues) {
3622
+ function prefixIssues(path91, issues) {
3623
3623
  return issues.map((iss) => {
3624
3624
  var _a3;
3625
3625
  (_a3 = iss).path ?? (_a3.path = []);
3626
- iss.path.unshift(path90);
3626
+ iss.path.unshift(path91);
3627
3627
  return iss;
3628
3628
  });
3629
3629
  }
@@ -3841,16 +3841,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
3841
3841
  }
3842
3842
  function formatError(error51, mapper = (issue2) => issue2.message) {
3843
3843
  const fieldErrors = { _errors: [] };
3844
- const processError = (error52, path90 = []) => {
3844
+ const processError = (error52, path91 = []) => {
3845
3845
  for (const issue2 of error52.issues) {
3846
3846
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3847
- issue2.errors.map((issues) => processError({ issues }, [...path90, ...issue2.path]));
3847
+ issue2.errors.map((issues) => processError({ issues }, [...path91, ...issue2.path]));
3848
3848
  } else if (issue2.code === "invalid_key") {
3849
- processError({ issues: issue2.issues }, [...path90, ...issue2.path]);
3849
+ processError({ issues: issue2.issues }, [...path91, ...issue2.path]);
3850
3850
  } else if (issue2.code === "invalid_element") {
3851
- processError({ issues: issue2.issues }, [...path90, ...issue2.path]);
3851
+ processError({ issues: issue2.issues }, [...path91, ...issue2.path]);
3852
3852
  } else {
3853
- const fullpath = [...path90, ...issue2.path];
3853
+ const fullpath = [...path91, ...issue2.path];
3854
3854
  if (fullpath.length === 0) {
3855
3855
  fieldErrors._errors.push(mapper(issue2));
3856
3856
  } else {
@@ -3877,17 +3877,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
3877
3877
  }
3878
3878
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
3879
3879
  const result = { errors: [] };
3880
- const processError = (error52, path90 = []) => {
3880
+ const processError = (error52, path91 = []) => {
3881
3881
  var _a3, _b;
3882
3882
  for (const issue2 of error52.issues) {
3883
3883
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3884
- issue2.errors.map((issues) => processError({ issues }, [...path90, ...issue2.path]));
3884
+ issue2.errors.map((issues) => processError({ issues }, [...path91, ...issue2.path]));
3885
3885
  } else if (issue2.code === "invalid_key") {
3886
- processError({ issues: issue2.issues }, [...path90, ...issue2.path]);
3886
+ processError({ issues: issue2.issues }, [...path91, ...issue2.path]);
3887
3887
  } else if (issue2.code === "invalid_element") {
3888
- processError({ issues: issue2.issues }, [...path90, ...issue2.path]);
3888
+ processError({ issues: issue2.issues }, [...path91, ...issue2.path]);
3889
3889
  } else {
3890
- const fullpath = [...path90, ...issue2.path];
3890
+ const fullpath = [...path91, ...issue2.path];
3891
3891
  if (fullpath.length === 0) {
3892
3892
  result.errors.push(mapper(issue2));
3893
3893
  continue;
@@ -3919,8 +3919,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
3919
3919
  }
3920
3920
  function toDotPath(_path) {
3921
3921
  const segs = [];
3922
- const path90 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3923
- for (const seg of path90) {
3922
+ const path91 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3923
+ for (const seg of path91) {
3924
3924
  if (typeof seg === "number")
3925
3925
  segs.push(`[${seg}]`);
3926
3926
  else if (typeof seg === "symbol")
@@ -17423,13 +17423,13 @@ function resolveRef(ref, ctx) {
17423
17423
  if (!ref.startsWith("#")) {
17424
17424
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
17425
17425
  }
17426
- const path90 = ref.slice(1).split("/").filter(Boolean);
17427
- if (path90.length === 0) {
17426
+ const path91 = ref.slice(1).split("/").filter(Boolean);
17427
+ if (path91.length === 0) {
17428
17428
  return ctx.rootSchema;
17429
17429
  }
17430
17430
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
17431
- if (path90[0] === defsKey) {
17432
- const key = path90[1];
17431
+ if (path91[0] === defsKey) {
17432
+ const key = path91[1];
17433
17433
  if (!key || !ctx.defs[key]) {
17434
17434
  throw new Error(`Reference not found: ${ref}`);
17435
17435
  }
@@ -18777,11 +18777,18 @@ function filterByInclude(entries, include) {
18777
18777
  if (include.length === 0 || include.length === 1 && include[0] === "*") {
18778
18778
  return entries.filter((e) => e.type === "file");
18779
18779
  }
18780
- const regexes = include.map(globToRegex);
18780
+ const pathRegexes = [];
18781
+ const baseRegexes = [];
18782
+ for (const g of include) {
18783
+ (g.includes("/") || g.includes("**") ? pathRegexes : baseRegexes).push(globToRegex(g));
18784
+ }
18781
18785
  return entries.filter((e) => {
18782
18786
  if (e.type !== "file")
18783
18787
  return false;
18784
- return regexes.some((re) => re.test(e.name));
18788
+ if (pathRegexes.some((re) => re.test(e.name)))
18789
+ return true;
18790
+ const base2 = e.name.slice(e.name.lastIndexOf("/") + 1);
18791
+ return baseRegexes.some((re) => re.test(base2));
18785
18792
  });
18786
18793
  }
18787
18794
  var REGEX_ESCAPE, DEFAULT_EXCLUDES;
@@ -18828,8 +18835,8 @@ function coerceStringList(value, fallback) {
18828
18835
  }
18829
18836
  return fallback;
18830
18837
  }
18831
- function hasRecursiveGlob(include) {
18832
- return include.some((g) => g.includes("**"));
18838
+ function hasPathAnchoredGlob(include) {
18839
+ return include.some((g) => g.includes("/") || g.includes("**"));
18833
18840
  }
18834
18841
  function scopeWarnings(allEntries, include, matched) {
18835
18842
  const warnings = [];
@@ -18837,15 +18844,17 @@ function scopeWarnings(allEntries, include, matched) {
18837
18844
  if (filesWalked === 0)
18838
18845
  return warnings;
18839
18846
  if (matched === 0) {
18840
- warnings.push(`SEARCH_EMPTY_SCOPE: include globs matched 0 of ${filesWalked} files walked \u2014 '*' matches only one path segment; use '**/<glob>' for recursive matching. Do not interpret this result as "pattern not found".`);
18847
+ const cause = hasPathAnchoredGlob(include) ? `path-anchored globs (with '/') match the relative path at exactly that level; use '**/<glob>' for recursive matching` : `flat globs ('*.ts') match the basename at any depth, so no file with these names/extensions exists in the walked tree`;
18848
+ warnings.push(`SEARCH_EMPTY_SCOPE: include globs matched 0 of ${filesWalked} files walked \u2014 ${cause}. Do not interpret this result as "pattern not found".`);
18841
18849
  return warnings;
18842
18850
  }
18843
- if (hasRecursiveGlob(include) || matched >= filesWalked)
18851
+ if (!hasPathAnchoredGlob(include) || matched >= filesWalked)
18844
18852
  return warnings;
18845
- const recursiveRegexes = compileGlobs(include.map((g) => `**/${g}`));
18853
+ const anchored = include.filter((g) => g.includes("/") || g.includes("**"));
18854
+ const recursiveRegexes = compileGlobs(anchored.map((g) => `**/${g}`));
18846
18855
  const wouldMatch = allEntries.filter((e) => e.type === "file" && matchesAnyCompiled(e.name, recursiveRegexes)).length;
18847
18856
  if (wouldMatch > matched) {
18848
- warnings.push(`include globs matched ${matched} of ${filesWalked} files walked \u2014 '*' matches only one path segment; a '**/*.ts'-style recursive glob would have matched ${wouldMatch - matched} more file(s) in subdirectories`);
18857
+ warnings.push(`include globs matched ${matched} of ${filesWalked} files walked \u2014 a path-anchored glob ('${anchored[0]}') matches only that exact level; a '**/${anchored[0]}'-style recursive glob would have matched ${wouldMatch - matched} more file(s) in subdirectories`);
18849
18858
  }
18850
18859
  return warnings;
18851
18860
  }
@@ -18912,7 +18921,10 @@ var init_search = __esm({
18912
18921
  /**
18913
18922
  * Glob pattern(s) to INCLUDE when path is a directory.
18914
18923
  * Accepts a string OR string[] (models often emit a bare string).
18915
- * Default ['*'] = all files. Ignored when path is a file.
18924
+ * A glob without '/' (e.g. '*.ts') matches the file basename at any depth
18925
+ * (grep --include style); a glob with '/' (e.g. 'src/*.ts') matches the
18926
+ * relative path at exactly that level. Default ['*'] = all files.
18927
+ * Ignored when path is a file.
18916
18928
  */
18917
18929
  include: stringOrStringArray.optional().default(["*"]),
18918
18930
  /**
@@ -18925,7 +18937,7 @@ var init_search = __esm({
18925
18937
  });
18926
18938
  grepContentTool = {
18927
18939
  name: "grep_content",
18928
- description: 'Regex search for content in a file OR recursively in a directory. When path is a directory, include/exclude globs filter which files are searched (default: all files, excluding node_modules/dist/.git/etc.). Glob semantics: "*" matches ONE path segment only ("*.ts" does NOT match "sub/file.ts"); use "**" for recursive matching ("**/*.ts" matches at any depth). include/exclude accept a single glob string (e.g. "*.ts") OR an array of globs. Returns matches with line numbers and context, plus filesWalked/filesInTree counts and a warning when the include globs matched suspiciously few files.',
18940
+ description: `Regex search for content in a file OR recursively in a directory. When path is a directory, include/exclude globs filter which files are searched (default: all files, excluding node_modules/dist/.git/etc.). Glob semantics (grep --include style): a glob without '/' (e.g. "*.md") matches the file basename at ANY depth; a glob with '/' (e.g. "src/*.ts") matches the relative path at exactly that level; "**" is explicit recursion ("**/*.ts" matches at any depth, same as the bare form). include/exclude accept a single glob string (e.g. "*.ts") OR an array of globs. Returns matches with line numbers and context, plus filesWalked/filesInTree counts and a warning when the include globs matched suspiciously few files.`,
18929
18941
  permissions: ["read"],
18930
18942
  sideEffect: "none",
18931
18943
  timeoutMs: 3e4,
@@ -19875,11 +19887,11 @@ var init_tools = __esm({
19875
19887
  if (!ctx.addDocument)
19876
19888
  return "Knowledge vault tool not available.";
19877
19889
  const title = args["title"] || "New Document";
19878
- const path90 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
19890
+ const path91 = args["path"] || `notes/${title.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
19879
19891
  const content = args["content"] || "";
19880
19892
  const tags = args["tags"] || [];
19881
19893
  ctx.addDocument({
19882
- path: path90,
19894
+ path: path91,
19883
19895
  title,
19884
19896
  content,
19885
19897
  format: "markdown",
@@ -19888,7 +19900,7 @@ var init_tools = __esm({
19888
19900
  workspaceId: ctx.workspaceId
19889
19901
  });
19890
19902
  ctx.addActivity("vault", "created document", title);
19891
- return `Document "${title}" created at "${path90}".`;
19903
+ return `Document "${title}" created at "${path91}".`;
19892
19904
  }
19893
19905
  }
19894
19906
  ];
@@ -23096,6 +23108,9 @@ var init_ObserverBus = __esm({
23096
23108
  });
23097
23109
 
23098
23110
  // packages/core/dist/core/AgentHarness.js
23111
+ function gateAdvice(hardLimit) {
23112
+ return hardLimit ? "Finalize now with the evidence already collected; no further tool calls will run this turn." : "Prioritize verification/repair actions (test, typecheck, build, read failures) or finalize honestly.";
23113
+ }
23099
23114
  function hashToolCall(toolName, args) {
23100
23115
  const canonical = stableStringify2(args);
23101
23116
  return `${toolName}::${canonical}`;
@@ -23127,10 +23142,18 @@ function normalizeTextToolArgs(name, args) {
23127
23142
  }
23128
23143
  return out;
23129
23144
  }
23130
- function parseTextToolCalls(text) {
23145
+ function parseTextToolCallsDetailed(text) {
23146
+ let truncatedBlock = false;
23147
+ let rawBody = null;
23131
23148
  const m = /---TOOLS---\s*([\s\S]*?)---END---/.exec(text);
23132
23149
  if (m?.[1]) {
23133
- let body = m[1].trim();
23150
+ rawBody = m[1];
23151
+ } else if (text.includes("---TOOLS---")) {
23152
+ rawBody = text.slice(text.indexOf("---TOOLS---") + "---TOOLS---".length);
23153
+ truncatedBlock = true;
23154
+ }
23155
+ if (rawBody !== null) {
23156
+ let body = rawBody.trim();
23134
23157
  body = body.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
23135
23158
  const candidates = [body];
23136
23159
  if (/\]\s*\[/.test(body)) {
@@ -23145,7 +23168,7 @@ function parseTextToolCalls(text) {
23145
23168
  for (const cand of candidates) {
23146
23169
  const items = tryParseToolArray(cand);
23147
23170
  if (items.length > 0)
23148
- return items;
23171
+ return { calls: items, truncatedBlock };
23149
23172
  }
23150
23173
  const arrays = extractJsonArrays(body);
23151
23174
  if (arrays.length > 1) {
@@ -23154,16 +23177,19 @@ function parseTextToolCalls(text) {
23154
23177
  merged.push(...tryParseToolArray(a));
23155
23178
  }
23156
23179
  if (merged.length > 0)
23157
- return merged;
23180
+ return { calls: merged, truncatedBlock };
23158
23181
  }
23159
23182
  const objs = extractToolObjects(body);
23160
23183
  if (objs.length > 0)
23161
- return objs;
23184
+ return { calls: objs, truncatedBlock };
23162
23185
  }
23163
23186
  const mini = parseMinimaxStyleToolCalls(text);
23164
23187
  if (mini.length > 0)
23165
- return mini;
23166
- return [];
23188
+ return { calls: mini, truncatedBlock };
23189
+ return { calls: [], truncatedBlock };
23190
+ }
23191
+ function parseTextToolCalls(text) {
23192
+ return parseTextToolCallsDetailed(text).calls;
23167
23193
  }
23168
23194
  function parseMinimaxStyleToolCalls(text) {
23169
23195
  const out = [];
@@ -23310,25 +23336,63 @@ function extractJsonArrays(text) {
23310
23336
  }
23311
23337
  return out;
23312
23338
  }
23339
+ function findBalancedBrace(text, openIdx) {
23340
+ let depth = 0;
23341
+ let inStr = false;
23342
+ let esc2 = false;
23343
+ for (let j = openIdx; j < text.length; j++) {
23344
+ const c = text[j];
23345
+ if (inStr) {
23346
+ if (esc2)
23347
+ esc2 = false;
23348
+ else if (c === "\\")
23349
+ esc2 = true;
23350
+ else if (c === '"')
23351
+ inStr = false;
23352
+ continue;
23353
+ }
23354
+ if (c === '"') {
23355
+ inStr = true;
23356
+ continue;
23357
+ }
23358
+ if (c === "{")
23359
+ depth++;
23360
+ else if (c === "}") {
23361
+ depth--;
23362
+ if (depth === 0)
23363
+ return j;
23364
+ }
23365
+ }
23366
+ return -1;
23367
+ }
23313
23368
  function extractToolObjects(text) {
23314
23369
  const out = [];
23315
- const re = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})\s*\}/g;
23370
+ const headRe = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"args"\s*:\s*/g;
23316
23371
  let match;
23317
- while ((match = re.exec(text)) !== null) {
23372
+ while ((match = headRe.exec(text)) !== null) {
23318
23373
  const name = match[1];
23374
+ const argsStart = match.index + match[0].length;
23319
23375
  let args = {};
23320
- try {
23321
- const parsed = JSON.parse(match[2]);
23322
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
23323
- args = parsed;
23376
+ if (text[argsStart] === "{") {
23377
+ const end = findBalancedBrace(text, argsStart);
23378
+ if (end === -1)
23379
+ continue;
23380
+ try {
23381
+ const parsed = JSON.parse(text.slice(argsStart, end + 1));
23382
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
23383
+ args = parsed;
23384
+ } else {
23385
+ continue;
23386
+ }
23387
+ } catch {
23388
+ continue;
23324
23389
  }
23325
- } catch {
23326
23390
  }
23327
23391
  out.push({ name, args });
23328
23392
  }
23329
23393
  return out;
23330
23394
  }
23331
- var BUILD_LIVENESS_RECOVERY_PROMPT, AgentHarness, DOOM_LOOP_THRESHOLD;
23395
+ var TOOL_CALL_TRUNCATED_RECOVERY_MARKER, TOOL_CALL_TRUNCATED_RECOVERY_USER, TEXT_TOOLS_FAILED_MARKER, TEXT_TOOLS_FAILED_USER, TEXT_TOOLS_PARTIAL_MARKER, TEXT_TOOLS_PARTIAL_USER, BUILD_LIVENESS_RECOVERY_PROMPT, AgentHarness, DOOM_LOOP_THRESHOLD;
23332
23396
  var init_AgentHarness = __esm({
23333
23397
  "packages/core/dist/core/AgentHarness.js"() {
23334
23398
  "use strict";
@@ -23340,6 +23404,12 @@ var init_AgentHarness = __esm({
23340
23404
  init_textLoopDetect();
23341
23405
  init_ObserverBus();
23342
23406
  init_textLoopDetect();
23407
+ TOOL_CALL_TRUNCATED_RECOVERY_MARKER = "[harness] Previous tool call was truncated";
23408
+ TOOL_CALL_TRUNCATED_RECOVERY_USER = `${TOOL_CALL_TRUNCATED_RECOVERY_MARKER} by the provider before completion (finish_reason=tool_calls but no complete tool_call arrived). Retry with a shorter payload or split the work into smaller tool calls.`;
23409
+ TEXT_TOOLS_FAILED_MARKER = "[harness] Your ---TOOLS--- block did not run";
23410
+ TEXT_TOOLS_FAILED_USER = `${TEXT_TOOLS_FAILED_MARKER}: no call parsed (malformed, or truncated by the provider before ---END---). Re-emit the calls with native tool_call, or ONE valid ---TOOLS--- JSON array \u2014 smaller payloads if the block was cut off.`;
23411
+ TEXT_TOOLS_PARTIAL_MARKER = "[harness] Your ---TOOLS--- block was cut off";
23412
+ TEXT_TOOLS_PARTIAL_USER = `${TEXT_TOOLS_PARTIAL_MARKER} by the provider before ---END---: only the complete leading call(s) ran \u2014 the incomplete tail was dropped. Re-emit the remaining work with native tool_call or smaller payloads.`;
23343
23413
  BUILD_LIVENESS_RECOVERY_PROMPT = "[build-liveness] The requested task requires an on-disk implementation, but no successful project mutation has occurred yet. Continue working. Inspect only as needed, then make the required change with an available mutating tool. Do not merely describe a patch or claim completion.";
23344
23414
  AgentHarness = class {
23345
23415
  config;
@@ -23569,7 +23639,7 @@ var init_AgentHarness = __esm({
23569
23639
  const gate = this.checkToolCallGate(p3.toolName, p3.args);
23570
23640
  if (!gate.allowed) {
23571
23641
  return {
23572
- content: `[resource-gate] ${gate.reason ?? "denied by resource policy"} Prioritize verification/repair actions (test, typecheck, build, read failures) or finalize honestly.`,
23642
+ content: `[resource-gate] ${gate.reason ?? "denied by resource policy"} ` + gateAdvice(gate.hardLimit),
23573
23643
  isError: true,
23574
23644
  durationMs: 0
23575
23645
  };
@@ -23834,6 +23904,8 @@ ${shared.content}`,
23834
23904
  return null;
23835
23905
  if (this.buildProgress.recoveries >= maxBuildRecoveries)
23836
23906
  return null;
23907
+ if (!this.checkToolCallGate("edit_file", {}).allowed)
23908
+ return null;
23837
23909
  this.buildProgress.recoveries += 1;
23838
23910
  this.config.messages.push({ role: "user", content: BUILD_LIVENESS_RECOVERY_PROMPT });
23839
23911
  return {
@@ -24230,7 +24302,8 @@ ${cached2}`
24230
24302
  finishRef.value = "stop";
24231
24303
  finishRef.clarificationRequested = true;
24232
24304
  }
24233
- const textTools = clarificationPause ? [] : parseTextToolCalls(turnText);
24305
+ const textParse = clarificationPause ? { calls: [], truncatedBlock: false } : parseTextToolCallsDetailed(turnText);
24306
+ const textTools = textParse.calls;
24234
24307
  const toolsToRun = textTools.filter((tt) => {
24235
24308
  const key = hashToolCall(tt.name, tt.args);
24236
24309
  return !turnToolCalls.some((n) => hashToolCall(n.name, n.args) === key);
@@ -24238,12 +24311,21 @@ ${cached2}`
24238
24311
  if (!clarificationPause && (/---TOOLS---/.test(turnText) || /<\/?minimax:tool_call\b/i.test(turnText) || /invoke\s+name\s*=/i.test(turnText) || /\]\s*<\s*\]\s*minimax\s*\[/i.test(turnText)) && textTools.length === 0) {
24239
24312
  const parseErr = createBrainEvent("error", this.sessionId, {
24240
24313
  severity: "recoverable",
24241
- message: "Found text-format tool block but parse failed; tool calls were not executed. Prefer native tool_call, or ---TOOLS--- with ONE valid JSON array.",
24314
+ message: textParse.truncatedBlock ? "Found text-format tool block but parse failed; the block is TRUNCATED (no ---END--- marker \u2014 the provider cut the response mid-block). Tool calls were not executed. Re-emit with native tool_call or smaller payloads." : "Found text-format tool block but parse failed; tool calls were not executed. Prefer native tool_call, or ---TOOLS--- with ONE valid JSON array.",
24242
24315
  code: "text_tools_parse_failed"
24243
24316
  });
24244
24317
  this.emit(parseErr);
24245
24318
  yield parseErr;
24246
24319
  }
24320
+ if (!clarificationPause && textParse.truncatedBlock && textTools.length > 0) {
24321
+ const partial2 = createBrainEvent("error", this.sessionId, {
24322
+ severity: "recoverable",
24323
+ message: `Text-format tool block was truncated (missing ---END---): recovered ${textTools.length} complete call(s); the incomplete tail was dropped.`,
24324
+ code: "text_tools_truncated"
24325
+ });
24326
+ this.emit(partial2);
24327
+ yield partial2;
24328
+ }
24247
24329
  if (!clarificationPause && toolsToRun.length > 0 && this.config.toolRegistry && !this.cancelled && this.textToolReentries < 4) {
24248
24330
  let executedAny = false;
24249
24331
  for (let ti = 0; ti < toolsToRun.length; ti++) {
@@ -24266,7 +24348,7 @@ ${cached2}`
24266
24348
  yield startEv;
24267
24349
  const gate = this.checkToolCallGate(tt.name, tt.args);
24268
24350
  if (!gate.allowed) {
24269
- const denied = `[resource-gate] ${gate.reason ?? "denied by resource policy"} Prioritize verification/repair actions (test, typecheck, build, read failures) or finalize honestly.`;
24351
+ const denied = `[resource-gate] ${gate.reason ?? "denied by resource policy"} ` + gateAdvice(gate.hardLimit);
24270
24352
  const denyEv = createBrainEvent("tool_execution_end", this.sessionId, {
24271
24353
  toolCallId,
24272
24354
  result: denied,
@@ -24343,7 +24425,8 @@ ${cached2}`
24343
24425
  if (turnToolResults.length > 0) {
24344
24426
  finishRef.value = "tool_calls";
24345
24427
  }
24346
- if (finishRef.value === "tool_calls" && turnToolCalls.length === 0 && turnToolResults.length === 0) {
24428
+ const truncatedToolCall = finishRef.value === "tool_calls" && turnToolCalls.length === 0 && turnToolResults.length === 0;
24429
+ if (truncatedToolCall) {
24347
24430
  const truncErr = createBrainEvent("error", this.sessionId, {
24348
24431
  severity: "recoverable",
24349
24432
  message: "Tool call was truncated (finish_reason=tool_calls but no complete tool_call received). The provider cut the response mid-arguments. Retry with a shorter payload or split the work.",
@@ -24383,6 +24466,24 @@ ${cached2}`
24383
24466
  content: tr.content
24384
24467
  });
24385
24468
  }
24469
+ if (truncatedToolCall) {
24470
+ const last = this.config.messages[this.config.messages.length - 1];
24471
+ if (!(last?.role === "user" && typeof last.content === "string" && last.content.includes(TOOL_CALL_TRUNCATED_RECOVERY_MARKER))) {
24472
+ this.config.messages.push({
24473
+ role: "user",
24474
+ content: TOOL_CALL_TRUNCATED_RECOVERY_USER
24475
+ });
24476
+ }
24477
+ }
24478
+ if (!clarificationPause) {
24479
+ const feedbackText = textParse.truncatedBlock && textTools.length > 0 ? TEXT_TOOLS_PARTIAL_USER : /---TOOLS---/.test(turnText) && textTools.length === 0 ? TEXT_TOOLS_FAILED_USER : null;
24480
+ if (feedbackText) {
24481
+ const last = this.config.messages[this.config.messages.length - 1];
24482
+ if (!(last?.role === "user" && typeof last.content === "string" && (last.content.includes(TEXT_TOOLS_PARTIAL_MARKER) || last.content.includes(TEXT_TOOLS_FAILED_MARKER)))) {
24483
+ this.config.messages.push({ role: "user", content: feedbackText });
24484
+ }
24485
+ }
24486
+ }
24386
24487
  break;
24387
24488
  } else if (delta.kind === "error") {
24388
24489
  finishRef.providerError = true;
@@ -25317,6 +25418,12 @@ __export(harness_exports, {
25317
25418
  SessionJsonlWriter: () => SessionJsonlWriter,
25318
25419
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
25319
25420
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
25421
+ TEXT_TOOLS_FAILED_MARKER: () => TEXT_TOOLS_FAILED_MARKER,
25422
+ TEXT_TOOLS_FAILED_USER: () => TEXT_TOOLS_FAILED_USER,
25423
+ TEXT_TOOLS_PARTIAL_MARKER: () => TEXT_TOOLS_PARTIAL_MARKER,
25424
+ TEXT_TOOLS_PARTIAL_USER: () => TEXT_TOOLS_PARTIAL_USER,
25425
+ TOOL_CALL_TRUNCATED_RECOVERY_MARKER: () => TOOL_CALL_TRUNCATED_RECOVERY_MARKER,
25426
+ TOOL_CALL_TRUNCATED_RECOVERY_USER: () => TOOL_CALL_TRUNCATED_RECOVERY_USER,
25320
25427
  canonicalTools: () => canonicalTools,
25321
25428
  collapseLoopedAssistantText: () => collapseLoopedAssistantText,
25322
25429
  compareReplayPrefix: () => compareReplayPrefix,
@@ -25334,6 +25441,7 @@ __export(harness_exports, {
25334
25441
  normalizeToolName: () => normalizeToolName,
25335
25442
  parseMinimaxStyleToolCalls: () => parseMinimaxStyleToolCalls,
25336
25443
  parseTextToolCalls: () => parseTextToolCalls,
25444
+ parseTextToolCallsDetailed: () => parseTextToolCallsDetailed,
25337
25445
  readSession: () => readSession,
25338
25446
  recordRequest: () => recordRequest,
25339
25447
  recordToolResult: () => recordToolResult,
@@ -26138,11 +26246,11 @@ var init_synthesisAudit = __esm({
26138
26246
  import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
26139
26247
  import { join as join4 } from "node:path";
26140
26248
  function loadNfrSpec(zelariRoot) {
26141
- const path90 = join4(zelariRoot, "nfr-spec.json");
26142
- if (!existsSync7(path90))
26249
+ const path91 = join4(zelariRoot, "nfr-spec.json");
26250
+ if (!existsSync7(path91))
26143
26251
  return null;
26144
26252
  try {
26145
- const raw = JSON.parse(readFileSync7(path90, "utf8"));
26253
+ const raw = JSON.parse(readFileSync7(path91, "utf8"));
26146
26254
  if (raw.version !== 1 || !Array.isArray(raw.targets))
26147
26255
  return null;
26148
26256
  return raw;
@@ -28448,9 +28556,9 @@ var init_types5 = __esm({
28448
28556
  import { readFileSync as readFileSync12 } from "node:fs";
28449
28557
  import { join as join10 } from "node:path";
28450
28558
  function readLessonsDeduped(zelariRoot) {
28451
- const path90 = join10(zelariRoot, LESSONS_FILE);
28559
+ const path91 = join10(zelariRoot, LESSONS_FILE);
28452
28560
  try {
28453
- const raw = readFileSync12(path90, "utf8");
28561
+ const raw = readFileSync12(path91, "utf8");
28454
28562
  const byId = /* @__PURE__ */ new Map();
28455
28563
  for (const line of raw.split(/\r?\n/)) {
28456
28564
  if (!line.trim())
@@ -28551,8 +28659,8 @@ function keywordsFrom(check2, signature) {
28551
28659
  return [.../* @__PURE__ */ new Set([...fromId, ...words])].slice(0, 12);
28552
28660
  }
28553
28661
  function writeLesson(zelariRoot, lesson) {
28554
- const path90 = join11(zelariRoot, LESSONS_FILE);
28555
- appendFileSync(path90, `${JSON.stringify(lesson)}
28662
+ const path91 = join11(zelariRoot, LESSONS_FILE);
28663
+ appendFileSync(path91, `${JSON.stringify(lesson)}
28556
28664
  `, "utf8");
28557
28665
  }
28558
28666
  function findSimilar(lessons, signature) {
@@ -30515,9 +30623,9 @@ function findCycle(nodes) {
30515
30623
  if (color.get(start) !== WHITE)
30516
30624
  continue;
30517
30625
  const stack = [[start, 0]];
30518
- const path90 = [];
30626
+ const path91 = [];
30519
30627
  color.set(start, GRAY);
30520
- path90.push(start);
30628
+ path91.push(start);
30521
30629
  while (stack.length > 0) {
30522
30630
  const top = stack[stack.length - 1];
30523
30631
  const [id3, idx] = top;
@@ -30530,17 +30638,17 @@ function findCycle(nodes) {
30530
30638
  continue;
30531
30639
  const c = color.get(dep);
30532
30640
  if (c === GRAY) {
30533
- const at = path90.indexOf(dep);
30534
- return [...path90.slice(at), dep];
30641
+ const at = path91.indexOf(dep);
30642
+ return [...path91.slice(at), dep];
30535
30643
  }
30536
30644
  if (c === WHITE) {
30537
30645
  color.set(dep, GRAY);
30538
- path90.push(dep);
30646
+ path91.push(dep);
30539
30647
  stack.push([dep, 0]);
30540
30648
  }
30541
30649
  } else {
30542
30650
  color.set(id3, BLACK);
30543
- path90.pop();
30651
+ path91.pop();
30544
30652
  stack.pop();
30545
30653
  }
30546
30654
  }
@@ -31460,8 +31568,8 @@ var init_runner = __esm({
31460
31568
  failed: [...this.tentaclesById.values()].filter((r) => r.status === "error"),
31461
31569
  pending: []
31462
31570
  };
31463
- const path90 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
31464
- this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path90}`);
31571
+ const path91 = await this.host.saveSnapshot(snapshot, ".zelari/kraken/snapshots");
31572
+ this.host.log(`checkpoint: ${label ?? "auto"} \u2192 ${path91}`);
31465
31573
  return snapshot;
31466
31574
  }
31467
31575
  callLog(msg, data) {
@@ -31587,6 +31695,15 @@ var init_types9 = __esm({
31587
31695
  "resource.reserve_entered",
31588
31696
  // 2.6.1 (closure plan §9): hard-limit overrun telemetry — state-only.
31589
31697
  "resource.overrun",
31698
+ // ADR-0024 v1.1 (amended 2026-08-30): per-node ENVELOPE for kraken-graph
31699
+ // runs — written by the HOST (the sole spine writer) around the executor's
31700
+ // tentacle-run seam. State-only (never model-surface): data = {nodeId,
31701
+ // agent, graphId?, ok?, cancelled?, durationMs?}; node labels, prompts and
31702
+ // turn output stay on the kraken radio JSONL. One pair per attempt.
31703
+ // ADR-0021 schema review: additive state kinds need no SCHEMA_VERSION bump —
31704
+ // older readers skip them via the tolerant replay (schema-mismatch issues).
31705
+ "graph.node_started",
31706
+ "graph.node_ended",
31590
31707
  "note"
31591
31708
  ];
31592
31709
  SessionEventEnvelopeSchema = external_exports.object({
@@ -33912,16 +34029,16 @@ function runRetentionFromEnv() {
33912
34029
  maxTotalBytes: Number.isFinite(parseMb) && parseMb > 0 ? Math.round(parseMb * 1024 * 1024) : DEFAULT_RUN_RETENTION_MAX_MB * 1024 * 1024
33913
34030
  };
33914
34031
  }
33915
- async function dirSize(path90) {
34032
+ async function dirSize(path91) {
33916
34033
  let total = 0;
33917
34034
  let entries;
33918
34035
  try {
33919
- entries = await readdir(path90, { withFileTypes: true });
34036
+ entries = await readdir(path91, { withFileTypes: true });
33920
34037
  } catch {
33921
34038
  return 0;
33922
34039
  }
33923
34040
  for (const entry of entries) {
33924
- const child = join13(path90, entry.name);
34041
+ const child = join13(path91, entry.name);
33925
34042
  if (entry.isDirectory())
33926
34043
  total += await dirSize(child);
33927
34044
  else {
@@ -33948,19 +34065,19 @@ async function enforceRunRetention(runsDir, options = {}) {
33948
34065
  for (const entry of entries) {
33949
34066
  if (!entry.isDirectory())
33950
34067
  continue;
33951
- const path90 = join13(runsDir, entry.name);
34068
+ const path91 = join13(runsDir, entry.name);
33952
34069
  let startedAt = 0;
33953
34070
  let endedAt;
33954
34071
  let completed = false;
33955
34072
  try {
33956
- const manifest = JSON.parse(await readFile(join13(path90, "manifest.json"), "utf8"));
34073
+ const manifest = JSON.parse(await readFile(join13(path91, "manifest.json"), "utf8"));
33957
34074
  startedAt = manifest.startedAt ?? 0;
33958
34075
  endedAt = manifest.endedAt;
33959
34076
  completed = Boolean(endedAt) && manifest.status !== "running";
33960
34077
  } catch {
33961
34078
  completed = false;
33962
34079
  }
33963
- infos.push({ name: entry.name, path: path90, startedAt, endedAt, completed, bytes: await dirSize(path90) });
34080
+ infos.push({ name: entry.name, path: path91, startedAt, endedAt, completed, bytes: await dirSize(path91) });
33964
34081
  }
33965
34082
  const remove = async (info) => {
33966
34083
  await rm(info.path, { recursive: true, force: true });
@@ -34732,12 +34849,12 @@ var init_engine = __esm({
34732
34849
  * content digest) and the returned ref carries the event seq when the
34733
34850
  * emitter resolved one.
34734
34851
  */
34735
- async fsEvidence(observation, path90, sha256, content, extra = {}) {
34852
+ async fsEvidence(observation, path91, sha256, content, extra = {}) {
34736
34853
  const digest = sha256 && content !== void 0 ? sha256(content) : void 0;
34737
- const seq = await this.emitEvidence({ observation, path: path90, ...extra, ...digest ? { digest } : {} });
34854
+ const seq = await this.emitEvidence({ observation, path: path91, ...extra, ...digest ? { digest } : {} });
34738
34855
  return {
34739
34856
  tier: "fs-observation",
34740
- ref: path90,
34857
+ ref: path91,
34741
34858
  capturedAt: Date.now(),
34742
34859
  ...digest ? { digest } : {},
34743
34860
  ...seq !== void 0 ? { seq } : {}
@@ -35776,7 +35893,13 @@ __export(dist_exports, {
35776
35893
  TERMINAL_STATUSES: () => TERMINAL_STATUSES,
35777
35894
  TEXT_LOOP_RECOVERY_SYSTEM: () => TEXT_LOOP_RECOVERY_SYSTEM,
35778
35895
  TEXT_LOOP_RECOVERY_USER_PROMPT: () => TEXT_LOOP_RECOVERY_USER_PROMPT,
35896
+ TEXT_TOOLS_FAILED_MARKER: () => TEXT_TOOLS_FAILED_MARKER,
35897
+ TEXT_TOOLS_FAILED_USER: () => TEXT_TOOLS_FAILED_USER,
35898
+ TEXT_TOOLS_PARTIAL_MARKER: () => TEXT_TOOLS_PARTIAL_MARKER,
35899
+ TEXT_TOOLS_PARTIAL_USER: () => TEXT_TOOLS_PARTIAL_USER,
35779
35900
  TIER_RANK: () => TIER_RANK,
35901
+ TOOL_CALL_TRUNCATED_RECOVERY_MARKER: () => TOOL_CALL_TRUNCATED_RECOVERY_MARKER,
35902
+ TOOL_CALL_TRUNCATED_RECOVERY_USER: () => TOOL_CALL_TRUNCATED_RECOVERY_USER,
35780
35903
  TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
35781
35904
  TOOL_USE_PROTOCOL_DIRECTIVE: () => TOOL_USE_PROTOCOL_DIRECTIVE,
35782
35905
  TURN_COMPLETION_MODULE: () => TURN_COMPLETION_MODULE,
@@ -36003,6 +36126,7 @@ __export(dist_exports, {
36003
36126
  parsePersonaVerdict: () => parsePersonaVerdict,
36004
36127
  parseProjectRootFromWorkspaceContext: () => parseProjectRootFromWorkspaceContext,
36005
36128
  parseTextToolCalls: () => parseTextToolCalls,
36129
+ parseTextToolCallsDetailed: () => parseTextToolCallsDetailed,
36006
36130
  parseThinking: () => parseThinking,
36007
36131
  parseVerificationRunPayload: () => parseVerificationRunPayload,
36008
36132
  parseVerificationTable: () => parseVerificationTable,
@@ -37078,6 +37202,29 @@ function mapBrainEventToSpine(ev) {
37078
37202
  ...ev.memberName ? { member: ev.memberName } : {}
37079
37203
  }
37080
37204
  };
37205
+ case "error":
37206
+ if (ev.code === "tool_call_truncated") {
37207
+ return {
37208
+ kind: "user.message",
37209
+ actor: ACTOR_USER,
37210
+ data: { text: TOOL_CALL_TRUNCATED_RECOVERY_USER }
37211
+ };
37212
+ }
37213
+ if (ev.code === "text_tools_parse_failed") {
37214
+ return {
37215
+ kind: "user.message",
37216
+ actor: ACTOR_USER,
37217
+ data: { text: TEXT_TOOLS_FAILED_USER }
37218
+ };
37219
+ }
37220
+ if (ev.code === "text_tools_truncated") {
37221
+ return {
37222
+ kind: "user.message",
37223
+ actor: ACTOR_USER,
37224
+ data: { text: TEXT_TOOLS_PARTIAL_USER }
37225
+ };
37226
+ }
37227
+ return null;
37081
37228
  default:
37082
37229
  return null;
37083
37230
  }
@@ -37134,6 +37281,7 @@ var MAX_STREAM_BUFFERS, SessionSpineMirror, SpineMirroringWriter;
37134
37281
  var init_sessionSpine = __esm({
37135
37282
  "src/cli/sessionSpine.ts"() {
37136
37283
  "use strict";
37284
+ init_harness();
37137
37285
  init_session();
37138
37286
  init_session();
37139
37287
  init_verification2();
@@ -37880,9 +38028,9 @@ function spillToolOutput(fullText, meta3) {
37880
38028
  const rnd = randomBytes3(3).toString("hex");
37881
38029
  const safeTool = (meta3?.toolName ?? "tool").replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 32);
37882
38030
  const file2 = `${stamp}-${safeTool}-${hash3}-${rnd}.txt`;
37883
- const path90 = join14(dir, file2);
37884
- writeFileSync11(path90, fullText, "utf8");
37885
- return path90;
38031
+ const path91 = join14(dir, file2);
38032
+ writeFileSync11(path91, fullText, "utf8");
38033
+ return path91;
37886
38034
  } catch {
37887
38035
  return null;
37888
38036
  }
@@ -37928,10 +38076,10 @@ function truncateToolResult(text, capOrOpts = TOOL_RESULT_LINE_CAP) {
37928
38076
  ${tail2}`;
37929
38077
  }
37930
38078
  if (doSpill) {
37931
- const path90 = spillToolOutput(text, { toolName: opts.toolName });
37932
- if (path90) {
38079
+ const path91 = spillToolOutput(text, { toolName: opts.toolName });
38080
+ if (path91) {
37933
38081
  const spillNote = `
37934
- \u2026 [full output spilled to: ${path90} \u2014 re-read with read_file if you need the complete text] \u2026`;
38082
+ \u2026 [full output spilled to: ${path91} \u2014 re-read with read_file if you need the complete text] \u2026`;
37935
38083
  if (preview.includes("] \u2026\n")) {
37936
38084
  preview = preview.replace("] \u2026\n", `] \u2026${spillNote}
37937
38085
  `);
@@ -41044,28 +41192,28 @@ var init_storage = __esm({
41044
41192
  VALID_SCALARS = /^(true|false|null|~)$/i;
41045
41193
  Storage = class {
41046
41194
  /** Read a Markdown file with frontmatter. Throws if not found. */
41047
- read(path90) {
41048
- if (!existsSync19(path90)) {
41049
- throw new Error(`File not found: ${path90}`);
41195
+ read(path91) {
41196
+ if (!existsSync19(path91)) {
41197
+ throw new Error(`File not found: ${path91}`);
41050
41198
  }
41051
- const md = readFileSync16(path90, "utf8");
41199
+ const md = readFileSync16(path91, "utf8");
41052
41200
  return parseFrontmatter(md);
41053
41201
  }
41054
41202
  /** Read a Markdown file; returns null if not found. */
41055
- readIfExists(path90) {
41056
- if (!existsSync19(path90)) return null;
41057
- return this.read(path90);
41203
+ readIfExists(path91) {
41204
+ if (!existsSync19(path91)) return null;
41205
+ return this.read(path91);
41058
41206
  }
41059
41207
  /**
41060
41208
  * Write a Markdown file atomically (tmp + rename). Creates parent dirs.
41061
41209
  * The meta object is serialized as YAML frontmatter; body as Markdown.
41062
41210
  */
41063
- write(path90, meta3, body) {
41064
- mkdirSync10(dirname2(path90), { recursive: true });
41065
- const tmp = path90 + ".tmp-" + process.pid;
41211
+ write(path91, meta3, body) {
41212
+ mkdirSync10(dirname2(path91), { recursive: true });
41213
+ const tmp = path91 + ".tmp-" + process.pid;
41066
41214
  const md = serializeFrontmatter(meta3, body);
41067
41215
  writeFileSync13(tmp, md, "utf8");
41068
- renameSync(tmp, path90);
41216
+ renameSync(tmp, path91);
41069
41217
  }
41070
41218
  /** List all .md files in a directory (non-recursive). */
41071
41219
  listMarkdown(dir) {
@@ -41127,8 +41275,8 @@ function nextPlanTaskId(store6) {
41127
41275
  return `t${store6.counter}`;
41128
41276
  }
41129
41277
  function writePlanTaskArtifact(rootDir, task) {
41130
- const path90 = join18(rootDir, "plan-tasks", `${task.id}.md`);
41131
- mkdirSync11(dirname3(path90), { recursive: true });
41278
+ const path91 = join18(rootDir, "plan-tasks", `${task.id}.md`);
41279
+ mkdirSync11(dirname3(path91), { recursive: true });
41132
41280
  const meta3 = {
41133
41281
  kind: "task",
41134
41282
  id: task.id,
@@ -41149,7 +41297,7 @@ function writePlanTaskArtifact(rootDir, task) {
41149
41297
  task.notes?.trim() ? task.notes.trim() : "_(no notes)_",
41150
41298
  ""
41151
41299
  ].filter((l) => l !== null).join("\n");
41152
- new Storage().write(path90, meta3, body);
41300
+ new Storage().write(path91, meta3, body);
41153
41301
  }
41154
41302
  function loadHandle(rootDir) {
41155
41303
  const jsonPath = join18(rootDir, "plan.json");
@@ -44930,21 +45078,21 @@ function normalizeAuth(auth) {
44930
45078
  return "agent";
44931
45079
  }
44932
45080
  function readSecrets() {
44933
- const path90 = getSshSecretsPath();
44934
- if (!existsSync25(path90)) return {};
45081
+ const path91 = getSshSecretsPath();
45082
+ if (!existsSync25(path91)) return {};
44935
45083
  try {
44936
- return JSON.parse(readFileSync20(path90, "utf8"));
45084
+ return JSON.parse(readFileSync20(path91, "utf8"));
44937
45085
  } catch {
44938
45086
  return {};
44939
45087
  }
44940
45088
  }
44941
45089
  function writeSecrets(data) {
44942
- const path90 = getSshSecretsPath();
44943
- mkdirSync12(dirname4(path90), { recursive: true });
44944
- writeFileSync15(path90, `${JSON.stringify(data, null, 2)}
45090
+ const path91 = getSshSecretsPath();
45091
+ mkdirSync12(dirname4(path91), { recursive: true });
45092
+ writeFileSync15(path91, `${JSON.stringify(data, null, 2)}
44945
45093
  `, "utf8");
44946
45094
  try {
44947
- chmodSync(path90, 384);
45095
+ chmodSync(path91, 384);
44948
45096
  } catch {
44949
45097
  }
44950
45098
  }
@@ -44973,10 +45121,10 @@ function deleteSshPassword(id3) {
44973
45121
  writeSecrets({ passwords });
44974
45122
  }
44975
45123
  function readStore2() {
44976
- const path90 = getSshTargetsPath();
44977
- if (!existsSync25(path90)) return [];
45124
+ const path91 = getSshTargetsPath();
45125
+ if (!existsSync25(path91)) return [];
44978
45126
  try {
44979
- const parsed = JSON.parse(readFileSync20(path90, "utf8"));
45127
+ const parsed = JSON.parse(readFileSync20(path91, "utf8"));
44980
45128
  const list = Array.isArray(parsed.targets) ? parsed.targets : [];
44981
45129
  return list.filter(
44982
45130
  (t) => t && typeof t.id === "string" && typeof t.host === "string" && typeof t.user === "string"
@@ -44991,11 +45139,11 @@ function readStore2() {
44991
45139
  }
44992
45140
  }
44993
45141
  function writeStore2(targets) {
44994
- const path90 = getSshTargetsPath();
44995
- mkdirSync12(dirname4(path90), { recursive: true });
45142
+ const path91 = getSshTargetsPath();
45143
+ mkdirSync12(dirname4(path91), { recursive: true });
44996
45144
  const clean = targets.map(({ hasPassword: _hp, ...t }) => t);
44997
45145
  writeFileSync15(
44998
- path90,
45146
+ path91,
44999
45147
  `${JSON.stringify({ targets: clean }, null, 2)}
45000
45148
  `,
45001
45149
  "utf8"
@@ -45241,11 +45389,11 @@ function formatSshTargetsForPrompt() {
45241
45389
  ];
45242
45390
  for (const t of targets) {
45243
45391
  const tags = t.tags?.length ? ` tags=[${t.tags.join(",")}]` : "";
45244
- const path90 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
45392
+ const path91 = t.defaultRemotePath ? ` remotePath=${t.defaultRemotePath}` : "";
45245
45393
  const allow = t.allowedCommands?.length ? ` allowed=${t.allowedCommands.join("|")}` : " allowed=status-only";
45246
45394
  const auth = t.auth === "password" ? " auth=password" : t.auth === "keyPath" ? " auth=key" : " auth=agent";
45247
45395
  lines.push(
45248
- `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path90}${tags}${allow}`
45396
+ `- id=${t.id} name=${t.name} ${t.user}@${t.host}:${t.port ?? 22}${auth}${path91}${tags}${allow}`
45249
45397
  );
45250
45398
  }
45251
45399
  return lines.join("\n");
@@ -54236,8 +54384,8 @@ function readPlan(ctx) {
54236
54384
  } catch {
54237
54385
  }
54238
54386
  }
54239
- const path90 = workspaceFile(ctx.rootDir, "plan");
54240
- const doc = ctx.storage.readIfExists(path90);
54387
+ const path91 = workspaceFile(ctx.rootDir, "plan");
54388
+ const doc = ctx.storage.readIfExists(path91);
54241
54389
  if (!doc) return { phases: [], tasks: [], milestones: [] };
54242
54390
  const meta3 = doc.meta;
54243
54391
  return {
@@ -54415,7 +54563,7 @@ function addMilestoneRecord(ctx, summary, input) {
54415
54563
  dueDate: input.dueDate,
54416
54564
  targetVersion: version2
54417
54565
  });
54418
- const path90 = join31(ctx.rootDir, "milestones", `${id3}.md`);
54566
+ const path91 = join31(ctx.rootDir, "milestones", `${id3}.md`);
54419
54567
  const meta3 = {
54420
54568
  kind: "milestone",
54421
54569
  id: id3,
@@ -54432,7 +54580,7 @@ function addMilestoneRecord(ctx, summary, input) {
54432
54580
  `Target version: ${version2}`,
54433
54581
  ""
54434
54582
  ].join("\n");
54435
- ctx.storage.write(path90, meta3, body);
54583
+ ctx.storage.write(path91, meta3, body);
54436
54584
  return { id: id3, created: true };
54437
54585
  }
54438
54586
  function readPlanSummary(ctx) {
@@ -54636,7 +54784,7 @@ function addIdeaStub(ctx) {
54636
54784
  const tags = args["tags"] ?? [];
54637
54785
  const category = args["category"] ?? "General";
54638
54786
  const id3 = `${nextAdrId(ctx)}-${slugify3(title)}`;
54639
- const path90 = workspaceArtifact(ctx.rootDir, "decisions", id3);
54787
+ const path91 = workspaceArtifact(ctx.rootDir, "decisions", id3);
54640
54788
  const meta3 = {
54641
54789
  kind: "adr",
54642
54790
  status: "proposed",
@@ -54662,7 +54810,7 @@ function addIdeaStub(ctx) {
54662
54810
  ...consequences.map((c) => `- ${c}`),
54663
54811
  ""
54664
54812
  ].join("\n");
54665
- ctx.storage.write(path90, meta3, body);
54813
+ ctx.storage.write(path91, meta3, body);
54666
54814
  return `ADR ${id3} created: "${title}". Status: proposed. Promote to accepted via /update ADR or manual edit.`;
54667
54815
  });
54668
54816
  }
@@ -54744,14 +54892,14 @@ function createDocumentStub(ctx) {
54744
54892
  ctx.storage.write(risksPath, riskMeta, content);
54745
54893
  return `Document "${title}" created at risks.md (workspace root).`;
54746
54894
  }
54747
- const path90 = workspaceArtifact(ctx.rootDir, "docs", slug);
54895
+ const path91 = workspaceArtifact(ctx.rootDir, "docs", slug);
54748
54896
  const meta3 = {
54749
54897
  kind: "doc",
54750
54898
  id: slug,
54751
54899
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
54752
54900
  tags
54753
54901
  };
54754
- ctx.storage.write(path90, meta3, content);
54902
+ ctx.storage.write(path91, meta3, content);
54755
54903
  return `Document "${title}" created at docs/${slug}.md.`;
54756
54904
  });
54757
54905
  }
@@ -55138,10 +55286,10 @@ function getUserMcpPath() {
55138
55286
  function getProjectMcpPath(projectRoot) {
55139
55287
  return join32(projectRoot, ".zelari", "mcp.json");
55140
55288
  }
55141
- function readFile6(path90) {
55142
- if (!existsSync37(path90)) return {};
55289
+ function readFile6(path91) {
55290
+ if (!existsSync37(path91)) return {};
55143
55291
  try {
55144
- const parsed = JSON.parse(readFileSync29(path90, "utf8"));
55292
+ const parsed = JSON.parse(readFileSync29(path91, "utf8"));
55145
55293
  const out = {};
55146
55294
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
55147
55295
  if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
@@ -55157,10 +55305,10 @@ function readFile6(path90) {
55157
55305
  return {};
55158
55306
  }
55159
55307
  }
55160
- function writeFile2(path90, servers) {
55161
- mkdirSync16(dirname9(path90), { recursive: true });
55308
+ function writeFile2(path91, servers) {
55309
+ mkdirSync16(dirname9(path91), { recursive: true });
55162
55310
  const body = { mcpServers: servers };
55163
- writeFileSync18(path90, `${JSON.stringify(body, null, 2)}
55311
+ writeFileSync18(path91, `${JSON.stringify(body, null, 2)}
55164
55312
  `, "utf8");
55165
55313
  }
55166
55314
  function listMcpServers(projectRoot) {
@@ -55193,9 +55341,9 @@ function upsertMcpServer(opts) {
55193
55341
  if (!opts.config.command?.trim()) {
55194
55342
  return { ok: false, error: "command is required" };
55195
55343
  }
55196
- let path90;
55344
+ let path91;
55197
55345
  if (opts.scope === "user") {
55198
- path90 = getUserMcpPath();
55346
+ path91 = getUserMcpPath();
55199
55347
  } else {
55200
55348
  const root = opts.projectRoot?.trim();
55201
55349
  if (!root) {
@@ -55204,30 +55352,30 @@ function upsertMcpServer(opts) {
55204
55352
  error: "projectRoot required for project scope (Open Folder first)"
55205
55353
  };
55206
55354
  }
55207
- path90 = getProjectMcpPath(root);
55355
+ path91 = getProjectMcpPath(root);
55208
55356
  }
55209
- const current = readFile6(path90);
55357
+ const current = readFile6(path91);
55210
55358
  current[name] = {
55211
55359
  command: opts.config.command.trim(),
55212
55360
  args: opts.config.args,
55213
55361
  env: opts.config.env,
55214
55362
  enabled: opts.config.enabled !== false
55215
55363
  };
55216
- writeFile2(path90, current);
55217
- return { ok: true, path: path90 };
55364
+ writeFile2(path91, current);
55365
+ return { ok: true, path: path91 };
55218
55366
  }
55219
55367
  function removeMcpServer(opts) {
55220
- const path90 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
55221
- if (!path90) {
55368
+ const path91 = opts.scope === "user" ? getUserMcpPath() : opts.projectRoot ? getProjectMcpPath(opts.projectRoot) : null;
55369
+ if (!path91) {
55222
55370
  return { ok: false, error: "projectRoot required for project scope" };
55223
55371
  }
55224
- const current = readFile6(path90);
55372
+ const current = readFile6(path91);
55225
55373
  if (!(opts.name in current)) {
55226
- return { ok: false, error: `Server "${opts.name}" not found in ${path90}` };
55374
+ return { ok: false, error: `Server "${opts.name}" not found in ${path91}` };
55227
55375
  }
55228
55376
  delete current[opts.name];
55229
- writeFile2(path90, current);
55230
- return { ok: true, path: path90 };
55377
+ writeFile2(path91, current);
55378
+ return { ok: true, path: path91 };
55231
55379
  }
55232
55380
  var init_mcpConfigIo = __esm({
55233
55381
  "src/cli/mcp/mcpConfigIo.ts"() {
@@ -55628,10 +55776,10 @@ import { createHash as createHash20 } from "node:crypto";
55628
55776
  import { join as join34 } from "node:path";
55629
55777
  import { readFile as readFile7 } from "node:fs/promises";
55630
55778
  async function readPackageJson3(projectRoot) {
55631
- const path90 = join34(projectRoot, "package.json");
55632
- if (!existsSync39(path90)) return null;
55779
+ const path91 = join34(projectRoot, "package.json");
55780
+ if (!existsSync39(path91)) return null;
55633
55781
  try {
55634
- return JSON.parse(await readFile7(path90, "utf8"));
55782
+ return JSON.parse(await readFile7(path91, "utf8"));
55635
55783
  } catch {
55636
55784
  return null;
55637
55785
  }
@@ -55713,9 +55861,9 @@ async function genBuild(ctx) {
55713
55861
  ].join("\n");
55714
55862
  }
55715
55863
  async function genOpenQuestions(ctx) {
55716
- const path90 = join34(ctx.rootDir, "risks.md");
55717
- if (!existsSync39(path90)) return "_No open questions._";
55718
- const content = readFileSync31(path90, "utf8");
55864
+ const path91 = join34(ctx.rootDir, "risks.md");
55865
+ if (!existsSync39(path91)) return "_No open questions._";
55866
+ const content = readFileSync31(path91, "utf8");
55719
55867
  const lines = content.split("\n");
55720
55868
  const questions = [];
55721
55869
  let currentTitle = "";
@@ -55989,9 +56137,9 @@ function versionKey(value) {
55989
56137
  function firstString2(v) {
55990
56138
  return typeof v === "string" && v.trim().length > 0 ? v : null;
55991
56139
  }
55992
- function readFileSyncSafe(path90) {
56140
+ function readFileSyncSafe(path91) {
55993
56141
  try {
55994
- return readFileSync32(path90, "utf8");
56142
+ return readFileSync32(path91, "utf8");
55995
56143
  } catch {
55996
56144
  return null;
55997
56145
  }
@@ -56454,8 +56602,8 @@ async function runPostCouncilHook(ctx, options) {
56454
56602
  sources: scope.sources
56455
56603
  } : void 0
56456
56604
  });
56457
- const path90 = writeCouncilCompletion(ctx.rootDir, completion);
56458
- completionHook = { ran: true, path: path90, completion };
56605
+ const path91 = writeCouncilCompletion(ctx.rootDir, completion);
56606
+ completionHook = { ran: true, path: path91, completion };
56459
56607
  } catch (err) {
56460
56608
  completionHook = {
56461
56609
  ran: true,
@@ -57791,7 +57939,7 @@ function safeSocketPath(socketPath) {
57791
57939
  return socketPath.trim();
57792
57940
  }
57793
57941
  function startPermissionBroker(socketPath, handlers, opts) {
57794
- const path90 = safeSocketPath(socketPath);
57942
+ const path91 = safeSocketPath(socketPath);
57795
57943
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
57796
57944
  const sockets = /* @__PURE__ */ new Set();
57797
57945
  const server = createServer2((socket) => {
@@ -57891,10 +58039,10 @@ function startPermissionBroker(socketPath, handlers, opts) {
57891
58039
  return new Promise((resolve9, reject) => {
57892
58040
  const onError = (err) => reject(err);
57893
58041
  server.once("error", onError);
57894
- server.listen(path90, () => {
58042
+ server.listen(path91, () => {
57895
58043
  server.removeListener("error", onError);
57896
58044
  resolve9({
57897
- socketPath: path90,
58045
+ socketPath: path91,
57898
58046
  stop: () => new Promise((res) => {
57899
58047
  for (const s of sockets) s.destroy();
57900
58048
  sockets.clear();
@@ -57905,7 +58053,7 @@ function startPermissionBroker(socketPath, handlers, opts) {
57905
58053
  if (done) return;
57906
58054
  done = true;
57907
58055
  if (process.platform !== "win32") {
57908
- unlink(path90, () => res());
58056
+ unlink(path91, () => res());
57909
58057
  } else {
57910
58058
  res();
57911
58059
  }
@@ -57918,11 +58066,11 @@ function startPermissionBroker(socketPath, handlers, opts) {
57918
58066
  });
57919
58067
  }
57920
58068
  function requestBrokerAsk(socketPath, ask, opts) {
57921
- const path90 = safeSocketPath(socketPath);
58069
+ const path91 = safeSocketPath(socketPath);
57922
58070
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
57923
58071
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
57924
58072
  return new Promise((resolve9, reject) => {
57925
- const socket = connect(path90);
58073
+ const socket = connect(path91);
57926
58074
  let buffer = "";
57927
58075
  let settled = false;
57928
58076
  const settle = (fn) => {
@@ -57937,7 +58085,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
57937
58085
  settle(
57938
58086
  () => reject(
57939
58087
  new Error(
57940
- `permission broker unavailable at "${path90}" (connect timed out after ${connectTimeoutMs}ms)`
58088
+ `permission broker unavailable at "${path91}" (connect timed out after ${connectTimeoutMs}ms)`
57941
58089
  )
57942
58090
  )
57943
58091
  );
@@ -58843,6 +58991,12 @@ var init_graphMemory = __esm({
58843
58991
  });
58844
58992
 
58845
58993
  // src/cli/kraken/tentacle.ts
58994
+ var tentacle_exports = {};
58995
+ __export(tentacle_exports, {
58996
+ TASK_TOOL_TIMEOUT_MS: () => TASK_TOOL_TIMEOUT_MS,
58997
+ runSubAgent: () => runSubAgent,
58998
+ runTentacle: () => runTentacle
58999
+ });
58846
59000
  var init_tentacle = __esm({
58847
59001
  "src/cli/kraken/tentacle.ts"() {
58848
59002
  "use strict";
@@ -65742,6 +65896,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
65742
65896
  return exitCode;
65743
65897
  }
65744
65898
  const audit = new AuditLogger2();
65899
+ const { runTentacle: runTentacle2 } = await Promise.resolve().then(() => (init_tentacle(), tentacle_exports));
65745
65900
  const executor = new KrakenGraphExecutor2({
65746
65901
  taskToolDeps: {
65747
65902
  createSubAgentContext: createKrakenSubAgentContextFactory2({
@@ -65774,7 +65929,11 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
65774
65929
  parentCwd: cwd,
65775
65930
  sessionId: sessionId2,
65776
65931
  goal: prompt,
65777
- signal: abort.signal
65932
+ signal: abort.signal,
65933
+ // ADR-0024 v1.1: same delegate (`runTentacle`), wrapped so each node
65934
+ // turn leaves a graph.node_started / graph.node_ended envelope pair on
65935
+ // the spine. The executor owns scheduling; the HOST owns the spine.
65936
+ runTentacleFn: (runOpts) => nodeSpineEnvelopeRun(spine, runOpts, () => runTentacle2(runOpts))
65778
65937
  });
65779
65938
  const summary = await executor.execute(graph);
65780
65939
  if (summary.cancelled) log("graph cancelled \u2014 partial results below");
@@ -65819,6 +65978,37 @@ ${formatKrakenGraphDigest2(
65819
65978
  await emitHarnessStateEvent({ spine, workspaceRoot: cwd, output: opts.output, emitEvent });
65820
65979
  }
65821
65980
  }
65981
+ function nodeSpineEnvelopeRun(spine, runOpts, run) {
65982
+ const startedAt = Date.now();
65983
+ const noteNode = (kind2, extra) => {
65984
+ if (!runOpts.nodeId) return;
65985
+ void spine.appendEvent({
65986
+ kind: kind2,
65987
+ actor: { type: "system" },
65988
+ data: {
65989
+ nodeId: runOpts.nodeId,
65990
+ agent: runOpts.agent,
65991
+ ...runOpts.graphId ? { graphId: runOpts.graphId } : {},
65992
+ ...extra
65993
+ }
65994
+ });
65995
+ };
65996
+ noteNode("graph.node_started");
65997
+ return run().then(
65998
+ (res) => {
65999
+ noteNode("graph.node_ended", {
66000
+ ok: res.ok,
66001
+ ...!res.ok && res.cancelled ? { cancelled: true } : {},
66002
+ durationMs: Date.now() - startedAt
66003
+ });
66004
+ return res;
66005
+ },
66006
+ (err) => {
66007
+ noteNode("graph.node_ended", { ok: false, durationMs: Date.now() - startedAt });
66008
+ throw err;
66009
+ }
66010
+ );
66011
+ }
65822
66012
  async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAutoWrite = false, extras) {
65823
66013
  const cwd = opts ? resolveHeadlessCwd(opts) : process.cwd();
65824
66014
  const { registry: toolRegistry } = createBuiltinToolRegistry({
@@ -67062,7 +67252,7 @@ function upsertSkill(opts) {
67062
67252
  }
67063
67253
  dir = getProjectSkillsDir(root);
67064
67254
  }
67065
- const path90 = skillFilePath(dir, name);
67255
+ const path91 = skillFilePath(dir, name);
67066
67256
  const content = serializeSkillMd({
67067
67257
  name,
67068
67258
  description,
@@ -67071,13 +67261,13 @@ function upsertSkill(opts) {
67071
67261
  tools: opts.tools,
67072
67262
  cost: opts.cost
67073
67263
  });
67074
- const parsed = parseSkillMd(content, path90);
67264
+ const parsed = parseSkillMd(content, path91);
67075
67265
  if (!parsed) {
67076
67266
  return { ok: false, error: "Generated SKILL.md failed validation" };
67077
67267
  }
67078
- mkdirSync21(dirname13(path90), { recursive: true });
67079
- writeFileSync24(path90, content, "utf8");
67080
- return { ok: true, path: path90 };
67268
+ mkdirSync21(dirname13(path91), { recursive: true });
67269
+ writeFileSync24(path91, content, "utf8");
67270
+ return { ok: true, path: path91 };
67081
67271
  }
67082
67272
  function removeSkill(opts) {
67083
67273
  const name = opts.name.trim().toLowerCase();
@@ -67095,8 +67285,8 @@ function removeSkill(opts) {
67095
67285
  dir = getProjectSkillsDir(root);
67096
67286
  }
67097
67287
  const skillDir = join46(dir, name);
67098
- const path90 = skillFilePath(dir, name);
67099
- if (!existsSync52(path90) && !existsSync52(skillDir)) {
67288
+ const path91 = skillFilePath(dir, name);
67289
+ if (!existsSync52(path91) && !existsSync52(skillDir)) {
67100
67290
  return { ok: false, error: `Skill "${name}" not found in ${dir}` };
67101
67291
  }
67102
67292
  try {
@@ -67107,7 +67297,7 @@ function removeSkill(opts) {
67107
67297
  error: err instanceof Error ? err.message : String(err)
67108
67298
  };
67109
67299
  }
67110
- return { ok: true, path: path90 };
67300
+ return { ok: true, path: path91 };
67111
67301
  }
67112
67302
  var NAME_RE, BUILTIN_SKILL_MODULES, builtinsLoaded;
67113
67303
  var init_skillConfigIo = __esm({
@@ -67925,12 +68115,12 @@ function ensureHome() {
67925
68115
  }
67926
68116
  }
67927
68117
  function loadCompanionConfig() {
67928
- const path90 = getCompanionConfigPath();
67929
- if (!existsSync53(path90)) {
68118
+ const path91 = getCompanionConfigPath();
68119
+ if (!existsSync53(path91)) {
67930
68120
  return { projects: [] };
67931
68121
  }
67932
68122
  try {
67933
- const raw = JSON.parse(readFileSync41(path90, "utf8"));
68123
+ const raw = JSON.parse(readFileSync41(path91, "utf8"));
67934
68124
  const projects = Array.isArray(raw.projects) ? raw.projects.filter(
67935
68125
  (p3) => p3 && typeof p3.path === "string" && p3.path.trim() && typeof (p3.id ?? p3.name) === "string"
67936
68126
  ).map((p3) => ({
@@ -67968,16 +68158,16 @@ function loadOrCreateToken(explicit) {
67968
68158
  return { token: explicit.trim(), created: false };
67969
68159
  }
67970
68160
  ensureHome();
67971
- const path90 = getCompanionTokenPath();
67972
- if (existsSync53(path90)) {
67973
- const t = readFileSync41(path90, "utf8").trim();
68161
+ const path91 = getCompanionTokenPath();
68162
+ if (existsSync53(path91)) {
68163
+ const t = readFileSync41(path91, "utf8").trim();
67974
68164
  if (t) return { token: t, created: false };
67975
68165
  }
67976
68166
  const token = randomBytes7(24).toString("base64url");
67977
- writeFileSync25(path90, token + "\n", "utf8");
68167
+ writeFileSync25(path91, token + "\n", "utf8");
67978
68168
  try {
67979
68169
  const fs45 = __require("node:fs");
67980
- fs45.chmodSync?.(path90, 384);
68170
+ fs45.chmodSync?.(path91, 384);
67981
68171
  } catch {
67982
68172
  }
67983
68173
  return { token, created: true };
@@ -68002,17 +68192,17 @@ function mergeProjects(cfg, extraPaths) {
68002
68192
  byId.set(p3.id, p3);
68003
68193
  }
68004
68194
  for (const raw of extraPaths) {
68005
- const path90 = raw.trim();
68006
- if (!path90) continue;
68007
- let id3 = slugFromPath(path90);
68195
+ const path91 = raw.trim();
68196
+ if (!path91) continue;
68197
+ let id3 = slugFromPath(path91);
68008
68198
  let n = 2;
68009
- while (byId.has(id3) && byId.get(id3).path !== path90) {
68010
- id3 = `${slugFromPath(path90)}-${n++}`;
68199
+ while (byId.has(id3) && byId.get(id3).path !== path91) {
68200
+ id3 = `${slugFromPath(path91)}-${n++}`;
68011
68201
  }
68012
68202
  byId.set(id3, {
68013
68203
  id: id3,
68014
- name: slugFromPath(path90),
68015
- path: path90
68204
+ name: slugFromPath(path91),
68205
+ path: path91
68016
68206
  });
68017
68207
  }
68018
68208
  return [...byId.values()];
@@ -68987,9 +69177,9 @@ async function runCompanionServe(opts = {}) {
68987
69177
  return;
68988
69178
  }
68989
69179
  const url2 = parseUrl(req);
68990
- const path90 = url2.pathname.replace(/\/+$/, "") || "/";
69180
+ const path91 = url2.pathname.replace(/\/+$/, "") || "/";
68991
69181
  try {
68992
- if (req.method === "GET" && (path90 === "/health" || path90 === "/v1/health")) {
69182
+ if (req.method === "GET" && (path91 === "/health" || path91 === "/v1/health")) {
68993
69183
  sendJson2(res, 200, {
68994
69184
  ok: true,
68995
69185
  service: "zelari-companion",
@@ -69001,18 +69191,18 @@ async function runCompanionServe(opts = {}) {
69001
69191
  });
69002
69192
  return;
69003
69193
  }
69004
- if (path90.startsWith("/v1")) {
69194
+ if (path91.startsWith("/v1")) {
69005
69195
  if (!tokenMatches(token, getBearer(req))) {
69006
69196
  sendJson2(res, 401, { ok: false, error: "unauthorized" });
69007
69197
  return;
69008
69198
  }
69009
69199
  }
69010
- if (req.method === "GET" && path90 === "/v1/config") {
69200
+ if (req.method === "GET" && path91 === "/v1/config") {
69011
69201
  const snap = buildDesktopConfigSnapshot();
69012
69202
  sendJson2(res, 200, { ok: true, ...snap });
69013
69203
  return;
69014
69204
  }
69015
- if (req.method === "GET" && path90 === "/v1/projects") {
69205
+ if (req.method === "GET" && path91 === "/v1/projects") {
69016
69206
  sendJson2(res, 200, {
69017
69207
  ok: true,
69018
69208
  projects: projects.map((p3) => ({
@@ -69023,7 +69213,7 @@ async function runCompanionServe(opts = {}) {
69023
69213
  });
69024
69214
  return;
69025
69215
  }
69026
- if (req.method === "GET" && path90 === "/v1/runs") {
69216
+ if (req.method === "GET" && path91 === "/v1/runs") {
69027
69217
  sendJson2(res, 200, {
69028
69218
  ok: true,
69029
69219
  active: runs.getActive(),
@@ -69041,7 +69231,7 @@ async function runCompanionServe(opts = {}) {
69041
69231
  });
69042
69232
  return;
69043
69233
  }
69044
- if (req.method === "POST" && path90 === "/v1/runs") {
69234
+ if (req.method === "POST" && path91 === "/v1/runs") {
69045
69235
  const raw = await readBody(req);
69046
69236
  let body = {};
69047
69237
  try {
@@ -69089,7 +69279,7 @@ async function runCompanionServe(opts = {}) {
69089
69279
  });
69090
69280
  return;
69091
69281
  }
69092
- const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path90);
69282
+ const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path91);
69093
69283
  if (req.method === "GET" && eventsMatch) {
69094
69284
  const runId = eventsMatch[1];
69095
69285
  const run = runs.getRun(runId);
@@ -69154,7 +69344,7 @@ async function runCompanionServe(opts = {}) {
69154
69344
  }, 500);
69155
69345
  return;
69156
69346
  }
69157
- const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path90);
69347
+ const cancelMatch = /^\/v1\/runs\/([^/]+)\/cancel$/.exec(path91);
69158
69348
  if (req.method === "POST" && cancelMatch) {
69159
69349
  const runId = cancelMatch[1];
69160
69350
  const result = runs.cancel(runId);
@@ -69165,7 +69355,7 @@ async function runCompanionServe(opts = {}) {
69165
69355
  sendJson2(res, 200, { ok: true, cancelled: runId });
69166
69356
  return;
69167
69357
  }
69168
- const steerMatch = /^\/v1\/runs\/([^/]+)\/steer$/.exec(path90);
69358
+ const steerMatch = /^\/v1\/runs\/([^/]+)\/steer$/.exec(path91);
69169
69359
  if (req.method === "POST" && steerMatch) {
69170
69360
  const runId = steerMatch[1];
69171
69361
  const raw = await readBody(req);
@@ -69845,21 +70035,88 @@ var init_fixBudget = __esm({
69845
70035
  }
69846
70036
  });
69847
70037
 
70038
+ // src/cli/commands/inspectSession.ts
70039
+ var inspectSession_exports = {};
70040
+ __export(inspectSession_exports, {
70041
+ renderInspectReport: () => renderInspectReport,
70042
+ runInspectSession: () => runInspectSession
70043
+ });
70044
+ import path89 from "node:path";
70045
+ import { existsSync as existsSync56 } from "node:fs";
70046
+ function renderInspectReport(state3) {
70047
+ const lines = [
70048
+ `session ${state3.session.sessionId} status=${state3.session.status} turns=${state3.execution.turnsTotal}`
70049
+ ];
70050
+ const contractByTurn = new Map(state3.execution.contracts.map((c) => [c.turn, c]));
70051
+ for (const turn of state3.turns) {
70052
+ const contract = contractByTurn.get(turn.index);
70053
+ const verification = turn.verification ? `${turn.verification.verdict}${turn.verification.strict ? "" : " (non-strict)"}` : "unknown";
70054
+ const blockers = contract?.blockers ?? [];
70055
+ const completion = contract?.complete ? "complete" : blockers.length > 0 ? `incomplete \u2014 ${blockers.length} blockers: ${blockers.join(", ")}` : "incomplete";
70056
+ lines.push(
70057
+ ` turn ${turn.index} [${turn.outcome}] verification: ${verification} contract: ${completion}`
70058
+ );
70059
+ }
70060
+ lines.push("");
70061
+ lines.push("support lens:");
70062
+ const projections = state3.support.contextProjections;
70063
+ const last = projections[projections.length - 1];
70064
+ lines.push(
70065
+ ` context projections: ${projections.length}` + (last ? ` (last: ${last.contextChars} chars \u2192 ${last.returnedCount} items)` : "")
70066
+ );
70067
+ lines.push(` memory events: ${state3.support.memoryEvents}`);
70068
+ const saved = state3.support.tokensSavedByCompaction;
70069
+ lines.push(
70070
+ ` compactions: ${state3.support.compactions}${saved !== void 0 ? ` (${saved} tokens saved)` : ""}`
70071
+ );
70072
+ return lines.join("\n");
70073
+ }
70074
+ async function runInspectSession(opts) {
70075
+ const sessionsDir = resolveSessionsDir({ workspaceRoot: opts.cwd ?? process.cwd() });
70076
+ const sessionDir = path89.join(sessionsDir, opts.sessionId);
70077
+ const eventsPath = path89.join(sessionDir, "events.jsonl");
70078
+ if (!existsSync56(sessionDir)) {
70079
+ console.error(`zelari-code inspect: no session directory at ${sessionDir}`);
70080
+ return 1;
70081
+ }
70082
+ if (!existsSync56(eventsPath)) {
70083
+ console.error(`zelari-code inspect: session directory has no events.jsonl at ${eventsPath}`);
70084
+ return 1;
70085
+ }
70086
+ let state3;
70087
+ try {
70088
+ state3 = await readHarnessState(sessionDir);
70089
+ } catch (err) {
70090
+ const msg = err instanceof Error ? err.message : String(err);
70091
+ console.error(`zelari-code inspect: cannot read session at ${sessionDir}: ${msg}`);
70092
+ return 1;
70093
+ }
70094
+ console.log(opts.json ? JSON.stringify(state3, null, 2) : renderInspectReport(state3));
70095
+ return 0;
70096
+ }
70097
+ var init_inspectSession = __esm({
70098
+ "src/cli/commands/inspectSession.ts"() {
70099
+ "use strict";
70100
+ init_session();
70101
+ init_harnessState();
70102
+ }
70103
+ });
70104
+
69848
70105
  // src/cli/commands/inspect.ts
69849
70106
  var inspect_exports = {};
69850
70107
  __export(inspect_exports, {
69851
70108
  collectInspectReport: () => collectInspectReport,
69852
70109
  runInspect: () => runInspect
69853
70110
  });
69854
- import path89 from "node:path";
69855
- import { existsSync as existsSync56, readFileSync as readFileSync43, readdirSync as readdirSync12 } from "node:fs";
70111
+ import path90 from "node:path";
70112
+ import { existsSync as existsSync57, readFileSync as readFileSync43, readdirSync as readdirSync12 } from "node:fs";
69856
70113
  import { homedir as homedir17 } from "node:os";
69857
70114
  async function collectInspectReport(cwd = process.cwd()) {
69858
70115
  ensureBuiltinSkillsLoadedSync();
69859
70116
  const snap = listSkillsSnapshot(cwd);
69860
70117
  const mcp = listMcpServers(cwd);
69861
- const userMcpPath = path89.join(homedir17(), ".zelari-code", "mcp.json");
69862
- const projectMcpPath = path89.join(cwd, ".zelari", "mcp.json");
70118
+ const userMcpPath = path90.join(homedir17(), ".zelari-code", "mcp.json");
70119
+ const projectMcpPath = path90.join(cwd, ".zelari", "mcp.json");
69863
70120
  const globalHooks = globalHooksDir();
69864
70121
  const projectHooks = projectHooksDir(cwd);
69865
70122
  const projectTrusted = isFolderTrusted(cwd);
@@ -69885,11 +70142,11 @@ async function collectInspectReport(cwd = process.cwd()) {
69885
70142
  folders: listTrustedFolders()
69886
70143
  },
69887
70144
  configSources: [
69888
- { path: userMcpPath, exists: existsSync56(userMcpPath) },
69889
- { path: projectMcpPath, exists: existsSync56(projectMcpPath) },
69890
- { path: path89.join(homedir17(), ".zelari-code", "provider.json"), exists: existsSync56(path89.join(homedir17(), ".zelari-code", "provider.json")) },
69891
- { path: path89.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync56(path89.join(cwd, ".zelari", "AGENTS.md")) },
69892
- { path: path89.join(cwd, "AGENTS.md"), exists: existsSync56(path89.join(cwd, "AGENTS.md")) }
70145
+ { path: userMcpPath, exists: existsSync57(userMcpPath) },
70146
+ { path: projectMcpPath, exists: existsSync57(projectMcpPath) },
70147
+ { path: path90.join(homedir17(), ".zelari-code", "provider.json"), exists: existsSync57(path90.join(homedir17(), ".zelari-code", "provider.json")) },
70148
+ { path: path90.join(cwd, ".zelari", "AGENTS.md"), exists: existsSync57(path90.join(cwd, ".zelari", "AGENTS.md")) },
70149
+ { path: path90.join(cwd, "AGENTS.md"), exists: existsSync57(path90.join(cwd, "AGENTS.md")) }
69893
70150
  ],
69894
70151
  skills: {
69895
70152
  total: snap.skills.length,
@@ -69902,7 +70159,7 @@ async function collectInspectReport(cwd = process.cwd()) {
69902
70159
  user: mcp.servers.filter((s) => s.scope === "user").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
69903
70160
  project: mcp.servers.filter((s) => s.scope === "project").map((s) => ({ name: s.name, enabled: s.enabled !== false })),
69904
70161
  projectTrusted,
69905
- projectConfigExists: existsSync56(projectMcpPath)
70162
+ projectConfigExists: existsSync57(projectMcpPath)
69906
70163
  },
69907
70164
  hooks: {
69908
70165
  global: {
@@ -69929,12 +70186,12 @@ function listJsonFiles(dir) {
69929
70186
  }
69930
70187
  function findAgentsMd(cwd) {
69931
70188
  const candidates = [
69932
- path89.join(cwd, "AGENTS.md"),
69933
- path89.join(cwd, ".zelari", "AGENTS.md")
70189
+ path90.join(cwd, "AGENTS.md"),
70190
+ path90.join(cwd, ".zelari", "AGENTS.md")
69934
70191
  ];
69935
70192
  const found = [];
69936
70193
  for (const c of candidates) {
69937
- if (existsSync56(c)) {
70194
+ if (existsSync57(c)) {
69938
70195
  try {
69939
70196
  const text = readFileSync43(c, "utf8");
69940
70197
  found.push(`${c} (${text.length} bytes)`);
@@ -79561,8 +79818,8 @@ function normalizeDraft(raw, sourceUrl, provider, model) {
79561
79818
  let name = String(o.name ?? "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
79562
79819
  if (!name || !/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
79563
79820
  try {
79564
- const path90 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
79565
- name = path90 && /^[a-z0-9]/.test(path90) ? path90 : "imported-skill";
79821
+ const path91 = new URL(sourceUrl).pathname.split("/").filter(Boolean).pop()?.replace(/\.[a-z0-9]+$/i, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
79822
+ name = path91 && /^[a-z0-9]/.test(path91) ? path91 : "imported-skill";
79566
79823
  } catch {
79567
79824
  name = "imported-skill";
79568
79825
  }
@@ -79897,6 +80154,14 @@ function pickRootComponent() {
79897
80154
  process.exit(1);
79898
80155
  }
79899
80156
  if (argv.includes("--inspect") || argv.includes("inspect")) {
80157
+ const inspectAt = argv.findIndex((a) => a === "inspect" || a === "--inspect");
80158
+ const sessionId2 = inspectAt >= 0 ? argv.slice(inspectAt + 1).find((a) => !a.startsWith("-")) : void 0;
80159
+ if (sessionId2) {
80160
+ const { runInspectSession: runInspectSession2 } = (init_inspectSession(), __toCommonJS(inspectSession_exports));
80161
+ const json4 = argv.includes("--json");
80162
+ void runInspectSession2({ sessionId: sessionId2, json: json4 }).then((code) => process.exit(code));
80163
+ return { kind: "done" };
80164
+ }
79900
80165
  const { runInspect: runInspect2 } = (init_inspect(), __toCommonJS(inspect_exports));
79901
80166
  const json3 = argv.includes("--json");
79902
80167
  void runInspect2({ json: json3 }).then((code) => process.exit(code));