omnius 1.0.457 → 1.0.459

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.
package/dist/index.js CHANGED
@@ -14315,7 +14315,7 @@ var init_file_edit = __esm({
14315
14315
  path: { type: "string", description: "Absolute or relative file path" },
14316
14316
  old_string: {
14317
14317
  type: "string",
14318
- description: "The exact string to search for and replace. Must be unique in the file (or use replace_all). Use old_string_base64 when JSON escaping is fragile."
14318
+ description: "The exact current string to search for and replace, copied from the most recent file_read for this file. Must be unique in the file (or use replace_all). Use old_string_base64 when JSON escaping is fragile."
14319
14319
  },
14320
14320
  old_string_base64: {
14321
14321
  type: "string",
@@ -14340,14 +14340,14 @@ var init_file_edit = __esm({
14340
14340
  },
14341
14341
  expected_hash: {
14342
14342
  type: "string",
14343
- description: "Optional SHA-256 from the most recent file_read. If provided, edit is refused when the file changed."
14343
+ description: "SHA-256 from the most recent file_read for this file. The runner refuses unverified exact replacements that omit or mismatch this hash."
14344
14344
  },
14345
14345
  dry_run: {
14346
14346
  type: "boolean",
14347
14347
  description: "Validate the edit and preview the diff without modifying disk."
14348
14348
  }
14349
14349
  },
14350
- required: ["path"],
14350
+ required: ["path", "expected_hash"],
14351
14351
  allOf: [
14352
14352
  { anyOf: [{ required: ["old_string"] }, { required: ["old_string_base64"] }] },
14353
14353
  { anyOf: [{ required: ["new_string"] }, { required: ["new_string_base64"] }] }
@@ -28570,7 +28570,7 @@ var init_batch_edit = __esm({
28570
28570
  },
28571
28571
  old_string: {
28572
28572
  type: "string",
28573
- description: "Exact string to find and replace (must be unique unless replace_all). Use old_string_base64 when JSON escaping is fragile."
28573
+ description: "Exact current string to find and replace, copied from the most recent file_read for this file. Must be unique unless replace_all. Use old_string_base64 when JSON escaping is fragile."
28574
28574
  },
28575
28575
  old_string_base64: {
28576
28576
  type: "string",
@@ -28595,10 +28595,10 @@ var init_batch_edit = __esm({
28595
28595
  },
28596
28596
  expected_hash: {
28597
28597
  type: "string",
28598
- description: "SHA-256 from the most recent file_read for this file. All edits for a file must use the same hash if provided."
28598
+ description: "SHA-256 from the most recent file_read for this file. All edits for a file must use the same verified hash."
28599
28599
  }
28600
28600
  },
28601
- required: ["path"],
28601
+ required: ["path", "expected_hash"],
28602
28602
  allOf: [
28603
28603
  { anyOf: [{ required: ["old_string"] }, { required: ["old_string_base64"] }] },
28604
28604
  { anyOf: [{ required: ["new_string"] }, { required: ["new_string_base64"] }] }
@@ -566824,6 +566824,7 @@ function shouldIgnore(relPath, base3, patterns) {
566824
566824
  function scanWorkspace(opts) {
566825
566825
  const root = opts.root;
566826
566826
  const maxFiles = Math.max(1, Math.min(2e3, opts.maxFiles ?? 200));
566827
+ const maxDirs = Math.max(1, Math.min(5e3, opts.maxDirs ?? 1e3));
566827
566828
  const noGitignore = opts.noGitignore === true;
566828
566829
  const noDefaults = opts.noDefaults === true;
566829
566830
  const patterns = [];
@@ -566834,6 +566835,7 @@ function scanWorkspace(opts) {
566834
566835
  if (opts.extraIgnore)
566835
566836
  patterns.push(...opts.extraIgnore);
566836
566837
  const files = [];
566838
+ const dirs = [];
566837
566839
  let totalBytes = 0;
566838
566840
  let truncated = false;
566839
566841
  let dirsVisited = 0;
@@ -566849,6 +566851,17 @@ function scanWorkspace(opts) {
566849
566851
  continue;
566850
566852
  visited.add(dir);
566851
566853
  dirsVisited++;
566854
+ if (dirs.length < maxDirs) {
566855
+ try {
566856
+ const st = statSync32(dir);
566857
+ dirs.push({
566858
+ abs: dir,
566859
+ rel: relative8(root, dir) || ".",
566860
+ mtimeMs: st.mtimeMs
566861
+ });
566862
+ } catch {
566863
+ }
566864
+ }
566852
566865
  let entries = [];
566853
566866
  try {
566854
566867
  entries = readdirSync30(dir);
@@ -566883,7 +566896,87 @@ function scanWorkspace(opts) {
566883
566896
  }
566884
566897
  }
566885
566898
  files.sort((a2, b) => a2.rel.localeCompare(b.rel));
566886
- return { files, totalBytes, truncated, dirsVisited };
566899
+ dirs.sort((a2, b) => a2.rel.localeCompare(b.rel));
566900
+ return { files, dirs, totalBytes, truncated, dirsVisited };
566901
+ }
566902
+ function humanBytes(n2) {
566903
+ if (n2 < 1024)
566904
+ return `${n2}B`;
566905
+ if (n2 < 1024 * 1024)
566906
+ return `${(n2 / 1024).toFixed(1)}KB`;
566907
+ return `${(n2 / 1024 / 1024).toFixed(1)}MB`;
566908
+ }
566909
+ function ensureTreeNode(root, rel) {
566910
+ if (!rel || rel === ".")
566911
+ return root;
566912
+ let node = root;
566913
+ for (const part of rel.split("/").filter(Boolean)) {
566914
+ let child = node.children.get(part);
566915
+ if (!child) {
566916
+ child = { name: part, children: /* @__PURE__ */ new Map(), directory: true };
566917
+ node.children.set(part, child);
566918
+ }
566919
+ node = child;
566920
+ }
566921
+ return node;
566922
+ }
566923
+ function renderWorkspaceTree(scan, opts = {}) {
566924
+ const maxLines = Math.max(10, Math.min(2e3, opts.maxLines ?? 120));
566925
+ const includeFileSizes = opts.includeFileSizes !== false;
566926
+ const root = {
566927
+ name: ".",
566928
+ children: /* @__PURE__ */ new Map(),
566929
+ directory: true
566930
+ };
566931
+ for (const dir of scan.dirs)
566932
+ ensureTreeNode(root, dir.rel).directory = true;
566933
+ for (const file of scan.files) {
566934
+ const parts = file.rel.split("/").filter(Boolean);
566935
+ const name10 = parts.pop();
566936
+ if (!name10)
566937
+ continue;
566938
+ const parent = ensureTreeNode(root, parts.join("/"));
566939
+ parent.children.set(name10, {
566940
+ name: name10,
566941
+ children: /* @__PURE__ */ new Map(),
566942
+ file,
566943
+ directory: false
566944
+ });
566945
+ }
566946
+ const lines = ["./"];
566947
+ let truncated = false;
566948
+ const walk2 = (node, depth) => {
566949
+ const children2 = [...node.children.values()].sort((a2, b) => {
566950
+ if (a2.directory !== b.directory)
566951
+ return a2.directory ? -1 : 1;
566952
+ return a2.name.localeCompare(b.name);
566953
+ });
566954
+ for (const child of children2) {
566955
+ if (lines.length >= maxLines) {
566956
+ truncated = true;
566957
+ return;
566958
+ }
566959
+ const indent2 = " ".repeat(depth);
566960
+ if (child.directory) {
566961
+ lines.push(`${indent2}${child.name}/`);
566962
+ walk2(child, depth + 1);
566963
+ if (truncated)
566964
+ return;
566965
+ } else {
566966
+ const size = includeFileSizes && child.file ? ` (${humanBytes(child.file.bytes)})` : "";
566967
+ lines.push(`${indent2}${child.name}${size}`);
566968
+ }
566969
+ }
566970
+ };
566971
+ walk2(root, 1);
566972
+ if (truncated || scan.truncated) {
566973
+ if (lines.length >= maxLines)
566974
+ lines[lines.length - 1] = "... [workspace tree truncated]";
566975
+ else
566976
+ lines.push("... [workspace tree truncated]");
566977
+ truncated = true;
566978
+ }
566979
+ return { text: lines.join("\n"), lineCount: lines.length, truncated };
566887
566980
  }
566888
566981
  var UNIVERSAL_DEFAULTS;
566889
566982
  var init_world_state_disk_scan = __esm({
@@ -567578,9 +567671,9 @@ function regenerate(opts) {
567578
567671
  lines.push(` No files found in workspace (excluding ignored paths).`);
567579
567672
  } else {
567580
567673
  const totalBytes = files.reduce((sum, f2) => sum + f2.bytes, 0);
567581
- lines.push(` ${files.length} file(s)${truncated ? " (truncated — more exist)" : ""}, total ${humanBytes(totalBytes)}.`);
567674
+ lines.push(` ${files.length} file(s)${truncated ? " (truncated — more exist)" : ""}, total ${humanBytes2(totalBytes)}.`);
567582
567675
  if (lastWriteFile) {
567583
- lines.push(` Most recently modified: ${lastWriteFile.rel} (${humanBytes(lastWriteFile.bytes)}, ${ago(nowMs - lastWriteFile.mtimeMs)}).`);
567676
+ lines.push(` Most recently modified: ${lastWriteFile.rel} (${humanBytes2(lastWriteFile.bytes)}, ${ago(nowMs - lastWriteFile.mtimeMs)}).`);
567584
567677
  }
567585
567678
  const topDirs = /* @__PURE__ */ new Map();
567586
567679
  for (const f2 of files) {
@@ -567676,7 +567769,7 @@ function reconciledMark(t2) {
567676
567769
  return "BLOCK";
567677
567770
  }
567678
567771
  }
567679
- function humanBytes(n2) {
567772
+ function humanBytes2(n2) {
567680
567773
  if (n2 < 1024)
567681
567774
  return `${n2}B`;
567682
567775
  if (n2 < 1024 * 1024)
@@ -572253,6 +572346,20 @@ var init_streaming_executor = __esm({
572253
572346
  setExecutor(fn) {
572254
572347
  this.executeFn = fn;
572255
572348
  }
572349
+ /** Rename a queued streaming tool entry when the provider reveals its real id late. */
572350
+ rename(oldId, newId) {
572351
+ if (!oldId || !newId || oldId === newId)
572352
+ return;
572353
+ const entry = this.tools.get(oldId);
572354
+ if (!entry || this.tools.has(newId))
572355
+ return;
572356
+ this.tools.delete(oldId);
572357
+ entry.id = newId;
572358
+ this.tools.set(newId, entry);
572359
+ const idx = this.insertionOrder.indexOf(oldId);
572360
+ if (idx >= 0)
572361
+ this.insertionOrder[idx] = newId;
572362
+ }
572256
572363
  /** Update the parsed-input concurrency classifier. */
572257
572364
  setConcurrencyResolver(fn) {
572258
572365
  this.config.concurrencyResolver = fn;
@@ -572490,7 +572597,7 @@ var init_streaming_executor = __esm({
572490
572597
  entry.state = "executing";
572491
572598
  entry.startedAt = Date.now();
572492
572599
  const exec7 = this.executeFn;
572493
- entry.promise = exec7(entry.name, entry.args).then((result) => {
572600
+ entry.promise = exec7(entry.id, entry.name, entry.args).then((result) => {
572494
572601
  entry.state = "completed";
572495
572602
  entry.result = result;
572496
572603
  entry.completedAt = Date.now();
@@ -573437,6 +573544,7 @@ var init_evidenceLedger = __esm({
573437
573544
  this.entries.set(path12, {
573438
573545
  ...existing,
573439
573546
  content: keepNew ? built.text : existing.content,
573547
+ contentHash: input.contentHash ?? existing.contentHash,
573440
573548
  fidelity: keepNew ? built.fidelity : existing.fidelity,
573441
573549
  ranges: mergeRanges([...existing.ranges, range]),
573442
573550
  lastReadTurn: turn,
@@ -573447,6 +573555,7 @@ var init_evidenceLedger = __esm({
573447
573555
  }
573448
573556
  this.entries.set(path12, {
573449
573557
  path: path12,
573558
+ contentHash: input.contentHash,
573450
573559
  readVersion: fileVersion,
573451
573560
  mtimeMs: input.mtimeMs ?? 0,
573452
573561
  lastReadTurn: turn,
@@ -573475,7 +573584,7 @@ var init_evidenceLedger = __esm({
573475
573584
  * like a manual chmod, device reconnect, or human edit), mark as stale.
573476
573585
  * Returns true if evidence was valid, false if became stale or stat failed.
573477
573586
  */
573478
- validateFreshness(path12) {
573587
+ validateFreshness(path12, statPath = path12) {
573479
573588
  const e2 = this.entries.get(path12);
573480
573589
  if (!e2)
573481
573590
  return false;
@@ -573484,7 +573593,7 @@ var init_evidenceLedger = __esm({
573484
573593
  if (e2.mtimeMs <= 0)
573485
573594
  return true;
573486
573595
  try {
573487
- const st = statSync36(path12, { throwIfNoEntry: false });
573596
+ const st = statSync36(statPath, { throwIfNoEntry: false });
573488
573597
  if (!st)
573489
573598
  return true;
573490
573599
  if (st.mtimeMs > e2.mtimeMs) {
@@ -573499,6 +573608,11 @@ var init_evidenceLedger = __esm({
573499
573608
  get(path12) {
573500
573609
  return this.entries.get(path12);
573501
573610
  }
573611
+ markStale(path12) {
573612
+ const e2 = this.entries.get(path12);
573613
+ if (e2)
573614
+ e2.stale = true;
573615
+ }
573502
573616
  size() {
573503
573617
  return this.entries.size;
573504
573618
  }
@@ -573521,12 +573635,12 @@ var init_evidenceLedger = __esm({
573521
573635
  ];
573522
573636
  let used = lines.join("\n").length;
573523
573637
  for (const e2 of sorted) {
573524
- const header = e2.stale ? `▸ ${e2.path} @v${e2.readVersion} [STALE — changed since read; re-read needed] (${rangeLabel(e2.ranges)})` : `▸ ${e2.path} @v${e2.readVersion} [FRESH] (${rangeLabel(e2.ranges)}):`;
573638
+ const header = e2.stale ? `▸ ${e2.path} @v${e2.readVersion} [STALE — changed since read; re-read needed] (${rangeLabel(e2.ranges)})` : `▸ ${e2.path} @v${e2.readVersion} [FRESH${e2.contentHash ? ` sha256=${e2.contentHash}` : ""}] (${rangeLabel(e2.ranges)}):`;
573525
573639
  const body = e2.stale ? "" : "\n" + indent(e2.content);
573526
573640
  const chunk = `${header}${body}
573527
573641
  `;
573528
573642
  if (used + chunk.length > maxChars) {
573529
- const stub = `▸ ${e2.path} @v${e2.readVersion} [${e2.stale ? "STALE" : "FRESH"}] (${rangeLabel(e2.ranges)})
573643
+ const stub = `▸ ${e2.path} @v${e2.readVersion} [${e2.stale ? "STALE" : `FRESH${e2.contentHash ? ` sha256=${e2.contentHash}` : ""}`}] (${rangeLabel(e2.ranges)})
573530
573644
  `;
573531
573645
  if (used + stub.length <= maxChars) {
573532
573646
  lines.push(stub);
@@ -574603,9 +574717,22 @@ ${input.error}`;
574603
574717
  }
574604
574718
  if (input.realFileMutation)
574605
574719
  diagnoses.add("project-file-mutation");
574606
- const focusDecisionJson = previewJson(input.focusDecision ?? {});
574607
- if (focusDecisionJson.includes("block_tool_call")) {
574608
- diagnoses.add("focus-supervisor-block");
574720
+ const focusDecision = input.focusDecision && typeof input.focusDecision === "object" ? input.focusDecision : null;
574721
+ const focusDecisionKind = typeof focusDecision?.["kind"] === "string" ? focusDecision["kind"] : null;
574722
+ if (focusDecisionKind === "block_tool_call") {
574723
+ if (focusDecision?.["success"] === true || input.success) {
574724
+ diagnoses.add("focus-supervisor-cached-evidence");
574725
+ } else {
574726
+ diagnoses.add("focus-supervisor-block");
574727
+ }
574728
+ } else {
574729
+ const focusDecisionJson = previewJson(input.focusDecision ?? {});
574730
+ if (focusDecisionJson.includes("block_tool_call")) {
574731
+ if (input.success)
574732
+ diagnoses.add("focus-supervisor-cached-evidence");
574733
+ else
574734
+ diagnoses.add("focus-supervisor-block");
574735
+ }
574609
574736
  }
574610
574737
  const focusJson = previewJson(input.focusSupervisor ?? {});
574611
574738
  if (focusJson.includes("block_tool_call") || focusJson.includes("requiredNextAction")) {
@@ -574627,8 +574754,87 @@ function diagnoseContextDump(record) {
574627
574754
  if (record.metrics.estimatedTokens > 1e5) {
574628
574755
  diagnoses.add("large-context-window");
574629
574756
  }
574757
+ if (hasToolResultBindingMismatch(record)) {
574758
+ diagnoses.add("tool-result-binding-mismatch");
574759
+ }
574630
574760
  return [...diagnoses].sort();
574631
574761
  }
574762
+ function hasToolResultBindingMismatch(record) {
574763
+ const messages2 = Array.isArray(record.request?.["messages"]) ? record.request["messages"] : [];
574764
+ if (messages2.length === 0)
574765
+ return false;
574766
+ const callsById = /* @__PURE__ */ new Map();
574767
+ for (const message2 of messages2) {
574768
+ if (message2["role"] !== "assistant")
574769
+ continue;
574770
+ const calls = Array.isArray(message2["tool_calls"]) ? message2["tool_calls"] : [];
574771
+ for (const call of calls) {
574772
+ const id2 = typeof call["id"] === "string" ? call["id"] : "";
574773
+ const fn = call["function"] && typeof call["function"] === "object" ? call["function"] : {};
574774
+ const name10 = typeof fn["name"] === "string" ? fn["name"] : "";
574775
+ const argsRaw = typeof fn["arguments"] === "string" ? fn["arguments"] : "{}";
574776
+ let args = {};
574777
+ try {
574778
+ args = JSON.parse(argsRaw);
574779
+ } catch {
574780
+ args = {};
574781
+ }
574782
+ if (id2)
574783
+ callsById.set(id2, { name: name10, args });
574784
+ }
574785
+ }
574786
+ for (const message2 of messages2) {
574787
+ if (message2["role"] !== "tool")
574788
+ continue;
574789
+ const id2 = typeof message2["tool_call_id"] === "string" ? message2["tool_call_id"] : "";
574790
+ const call = id2 ? callsById.get(id2) : void 0;
574791
+ if (!call)
574792
+ continue;
574793
+ const requested = extractToolTarget(call.args);
574794
+ if (!requested)
574795
+ continue;
574796
+ const content = typeof message2["content"] === "string" ? message2["content"] : "";
574797
+ const reported = extractReportedToolTarget(content);
574798
+ if (!reported)
574799
+ continue;
574800
+ if (!targetsCompatible(requested, reported))
574801
+ return true;
574802
+ }
574803
+ return false;
574804
+ }
574805
+ function extractToolTarget(args) {
574806
+ const value2 = args["path"] ?? args["file"] ?? args["file_path"] ?? args["filepath"] ?? args["directory"] ?? args["cwd"];
574807
+ return typeof value2 === "string" ? value2.trim() : "";
574808
+ }
574809
+ function extractReportedToolTarget(content) {
574810
+ const patterns = [
574811
+ /^target=([^\n\r]+)/m,
574812
+ /^path=([^\n\r]+)/m,
574813
+ /\bfile\s+([^\s]+)\s+already contains/i,
574814
+ /\bEdited\s+([^\s]+)\s+at line/i,
574815
+ /\bat\s+([^\s]+)\s+\(sha256\b/i,
574816
+ /\bRefusing to (?:edit|overwrite existing file)\s+([^\s]+)/i
574817
+ ];
574818
+ for (const pattern of patterns) {
574819
+ const match = content.match(pattern);
574820
+ const raw = match?.[1]?.trim();
574821
+ if (raw)
574822
+ return raw.replace(/[),.;:]+$/, "");
574823
+ }
574824
+ return "";
574825
+ }
574826
+ function targetsCompatible(requested, reported) {
574827
+ const left = normalizeTargetForCompare(requested);
574828
+ const right = normalizeTargetForCompare(reported);
574829
+ if (!left || !right)
574830
+ return true;
574831
+ if (left === right)
574832
+ return true;
574833
+ return left.endsWith(`/${right}`) || right.endsWith(`/${left}`);
574834
+ }
574835
+ function normalizeTargetForCompare(value2) {
574836
+ return value2.trim().replace(/^["']|["']$/g, "").replace(/\\/g, "/").replace(/\/+/g, "/").replace(/\/$/, "");
574837
+ }
574632
574838
  function summarizeToolEvent(toolName, success, diagnoses, command) {
574633
574839
  const status = success ? "success" : "failure";
574634
574840
  const parts = [`${toolName} ${status}`];
@@ -574736,9 +574942,11 @@ function writeContract(contractPath) {
574736
574942
  "",
574737
574943
  "- `shell-brace-whitespace`: a shell command used brace expansion containing whitespace, which can split an intended path set into unrelated shell words.",
574738
574944
  "- `successful-command-needs-post-state-audit`: the command exited zero but its shape is risky enough that filesystem state should be inspected.",
574739
- "- `focus-supervisor-block`: the tool result or supervisor state shows a blocked next action.",
574945
+ "- `focus-supervisor-block`: the tool result or supervisor state shows an unsuccessful blocked next action.",
574946
+ "- `focus-supervisor-cached-evidence`: the focus supervisor short-circuited a call successfully by reusing current cached evidence.",
574740
574947
  "- `background-task-id-missing`: task status lookup could not find the spawned task id.",
574741
574948
  "- `raw-discovery-dominates-context`: context-window metrics show low signal/noise around raw discovery output.",
574949
+ "- `tool-result-binding-mismatch`: the outbound model transcript contains a tool result whose reported target does not match the assistant tool call it is attached to.",
574742
574950
  ""
574743
574951
  ].join("\n");
574744
574952
  writeFileSync49(contractPath, body, "utf-8");
@@ -577523,7 +577731,7 @@ RECOVERY: cd to the directory containing '${file}', run a plain install with no
577523
577731
  import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync, writeFileSync as _fsWriteFileSync, appendFileSync as _fsAppendFileSync, unlinkSync as _fsUnlinkSync, mkdirSync as _fsMkdirSync, statSync as _fsStatSync, readdirSync as _fsReaddirSync } from "node:fs";
577524
577732
  import { execFile as _execFile, spawn as _spawn } from "node:child_process";
577525
577733
  import { createHash as _createHash } from "node:crypto";
577526
- import { join as _pathJoin, resolve as _pathResolve } from "node:path";
577734
+ import { join as _pathJoin, relative as _pathRelative, resolve as _pathResolve } from "node:path";
577527
577735
  import { tmpdir as _osTmpdir } from "node:os";
577528
577736
  import { homedir as _osHomedir } from "node:os";
577529
577737
  import { z as z17 } from "zod";
@@ -578016,27 +578224,39 @@ function sanitizeHistoryThink(messages2) {
578016
578224
  const sanitized = [];
578017
578225
  for (let i2 = 0; i2 < stripped.length; i2++) {
578018
578226
  const current = stripped[i2];
578019
- const next = stripped[i2 + 1];
578020
- if (isOrphanDuplicateAssistantToolAnchor(current, next, referencedToolCallIds)) {
578227
+ if (isOrphanDuplicateAssistantToolAnchor(stripped, i2, referencedToolCallIds)) {
578021
578228
  continue;
578022
578229
  }
578023
578230
  sanitized.push(current);
578024
578231
  }
578025
578232
  return sanitized;
578026
578233
  }
578027
- function isOrphanDuplicateAssistantToolAnchor(current, next, referencedToolCallIds) {
578028
- if (!next)
578234
+ function isOrphanDuplicateAssistantToolAnchor(messages2, index, referencedToolCallIds) {
578235
+ const current = messages2[index];
578236
+ if (!current)
578029
578237
  return false;
578030
- if (current.role !== "assistant" || next.role !== "assistant")
578238
+ if (current.role !== "assistant")
578031
578239
  return false;
578032
- if (!Array.isArray(current.tool_calls) || !Array.isArray(next.tool_calls)) {
578240
+ if (!Array.isArray(current.tool_calls) || current.tool_calls.length === 0) {
578033
578241
  return false;
578034
578242
  }
578035
- if (current.tool_calls.length === 0 || next.tool_calls.length === 0) {
578243
+ if (assistantVisibleText(current).length > 0)
578036
578244
  return false;
578245
+ let next;
578246
+ for (let i2 = index + 1; i2 < Math.min(messages2.length, index + 8); i2++) {
578247
+ const candidate = messages2[i2];
578248
+ if (candidate.role === "system")
578249
+ continue;
578250
+ if (candidate.role !== "assistant")
578251
+ return false;
578252
+ next = candidate;
578253
+ break;
578037
578254
  }
578038
- if (assistantVisibleText(current).length > 0)
578255
+ if (!next)
578039
578256
  return false;
578257
+ if (!Array.isArray(next.tool_calls) || next.tool_calls.length === 0) {
578258
+ return false;
578259
+ }
578040
578260
  if (toolCallsFingerprint(current.tool_calls) !== toolCallsFingerprint(next.tool_calls)) {
578041
578261
  return false;
578042
578262
  }
@@ -578186,6 +578406,7 @@ var init_agenticRunner = __esm({
578186
578406
  init_lesson_bank();
578187
578407
  init_intervention_replay();
578188
578408
  init_world_state_regenerator();
578409
+ init_world_state_disk_scan();
578189
578410
  init_backward_pass_runner();
578190
578411
  init_world_state_plan_reconciler();
578191
578412
  init_process_async2();
@@ -581019,7 +581240,7 @@ ${result.error ?? ""}`;
581019
581240
  if (result.success !== true)
581020
581241
  return false;
581021
581242
  if (toolName === "file_read") {
581022
- return targetPaths.length > 0 && targetPaths.some((path12) => this._evidenceLedger.hasFresh(path12));
581243
+ return targetPaths.length > 0 && targetPaths.some((path12) => this._evidenceLedger.hasFresh(this._normalizeEvidencePath(path12)));
581023
581244
  }
581024
581245
  return toolName === "list_directory" || toolName === "find_files" || toolName === "grep_search";
581025
581246
  }
@@ -583128,6 +583349,133 @@ ${latest.output || ""}`.trim();
583128
583349
  return null;
583129
583350
  }
583130
583351
  }
583352
+ /**
583353
+ * Render a fresh, bounded workspace tree from disk for the active context
583354
+ * frame, and persist the fuller capped tree under .omnius/world-state/tree.
583355
+ * This is deliberately independent of tool history: it answers "what exists
583356
+ * where right now?" without forcing the model to list_directory for basic
583357
+ * orientation.
583358
+ */
583359
+ _renderWorkspaceTreeBlock(turn) {
583360
+ const rawEnabled = String(process.env["OMNIUS_CONTEXT_WORKSPACE_TREE"] ?? "1").trim().toLowerCase();
583361
+ if (rawEnabled === "0" || rawEnabled === "false" || rawEnabled === "off") {
583362
+ return null;
583363
+ }
583364
+ const intEnv = (name10, fallback, min, max) => {
583365
+ const parsed = Number.parseInt(process.env[name10] ?? "", 10);
583366
+ return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback;
583367
+ };
583368
+ const root = this.authoritativeWorkingDirectory();
583369
+ const maxFiles = intEnv("OMNIUS_CONTEXT_TREE_MAX_FILES", 1e3, 20, 2e3);
583370
+ const maxDirs = intEnv("OMNIUS_CONTEXT_TREE_MAX_DIRS", 1500, 20, 5e3);
583371
+ const contextLines = intEnv("OMNIUS_CONTEXT_TREE_LINES", 90, 20, 400);
583372
+ const artifactLines = intEnv("OMNIUS_CONTEXT_TREE_ARTIFACT_LINES", 1800, 100, 4e3);
583373
+ try {
583374
+ const scan = scanWorkspace({ root, maxFiles, maxDirs });
583375
+ const compactTree = renderWorkspaceTree(scan, { maxLines: contextLines });
583376
+ const fullTree = renderWorkspaceTree(scan, { maxLines: artifactLines });
583377
+ const totalBytes = scan.files.reduce((sum, file) => sum + file.bytes, 0);
583378
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
583379
+ const focusSnapshot = this._focusSupervisor?.snapshot();
583380
+ const recentActions = this._adversaryToolOutcomes.slice(-12).map((item) => ({
583381
+ turn: item.turn,
583382
+ tool: item.tool,
583383
+ success: item.succeeded,
583384
+ path: item.path,
583385
+ stateVersion: item.stateVersion,
583386
+ preview: item.preview.slice(0, 240)
583387
+ }));
583388
+ const recentActionLines = recentActions.slice(-6).map((item) => {
583389
+ const path12 = item.path ? ` path=${item.path}` : "";
583390
+ return `- turn=${item.turn} ${item.tool} ${item.success ? "ok" : "err"}${path12}: ${item.preview.replace(/\s+/g, " ").slice(0, 160)}`;
583391
+ });
583392
+ const treeDir = _pathJoin(this.omniusStateDir(), "world-state", "tree");
583393
+ const turnName = `turn-${String(turn).padStart(4, "0")}`;
583394
+ const latestTextPath = _pathJoin(treeDir, "latest.txt");
583395
+ const latestJsonPath = _pathJoin(treeDir, "latest.json");
583396
+ const turnTextPath = _pathJoin(treeDir, `${turnName}.txt`);
583397
+ const turnJsonPath = _pathJoin(treeDir, `${turnName}.json`);
583398
+ let artifactDisplay = latestTextPath;
583399
+ try {
583400
+ _fsMkdirSync(treeDir, { recursive: true });
583401
+ const header = [
583402
+ `WORKSPACE TREE SNAPSHOT`,
583403
+ `generated_at=${generatedAt}`,
583404
+ `turn=${turn}`,
583405
+ `session=${this._sessionId ?? ""}`,
583406
+ `root=${root}`,
583407
+ `files=${scan.files.length}${scan.truncated ? "+" : ""}`,
583408
+ `dirs=${scan.dirs.length}`,
583409
+ `bytes=${totalBytes}`,
583410
+ focusSnapshot?.directive?.requiredNextAction ? `supervisor_required_next_action=${focusSnapshot.directive.requiredNextAction}` : `supervisor_required_next_action=none`,
583411
+ recentActionLines.length ? `recent_actions=${recentActions.length}` : `recent_actions=0`,
583412
+ recentActionLines.length ? recentActionLines.join("\n") : "",
583413
+ ""
583414
+ ].join("\n");
583415
+ const text2 = `${header}${fullTree.text}
583416
+ `;
583417
+ const payload = {
583418
+ generatedAt,
583419
+ turn,
583420
+ sessionId: this._sessionId,
583421
+ root,
583422
+ supervisor: focusSnapshot ? {
583423
+ state: focusSnapshot.state,
583424
+ lastDecision: focusSnapshot.lastDecision,
583425
+ lastReason: focusSnapshot.lastReason,
583426
+ requiredNextAction: focusSnapshot.directive?.requiredNextAction ?? null,
583427
+ directiveReason: focusSnapshot.directive?.reason ?? null
583428
+ } : null,
583429
+ recentActions,
583430
+ stats: {
583431
+ files: scan.files.length,
583432
+ dirs: scan.dirs.length,
583433
+ totalBytes,
583434
+ truncated: scan.truncated || fullTree.truncated,
583435
+ dirsVisited: scan.dirsVisited
583436
+ },
583437
+ files: scan.files.map((file) => ({
583438
+ path: file.rel,
583439
+ bytes: file.bytes,
583440
+ mtimeMs: file.mtimeMs
583441
+ })),
583442
+ dirs: scan.dirs.map((dir) => ({
583443
+ path: dir.rel,
583444
+ mtimeMs: dir.mtimeMs
583445
+ }))
583446
+ };
583447
+ _fsWriteFileSync(latestTextPath, text2, "utf8");
583448
+ _fsWriteFileSync(turnTextPath, text2, "utf8");
583449
+ _fsWriteFileSync(latestJsonPath, JSON.stringify(payload, null, 2), "utf8");
583450
+ _fsWriteFileSync(turnJsonPath, JSON.stringify(payload, null, 2), "utf8");
583451
+ const rel = _pathRelative(root, latestTextPath).replace(/\\/g, "/");
583452
+ if (rel && !rel.startsWith(".."))
583453
+ artifactDisplay = rel;
583454
+ } catch {
583455
+ artifactDisplay = "(workspace tree artifact write failed; inline digest still current)";
583456
+ }
583457
+ return [
583458
+ "[WORKSPACE TREE — fresh per-turn disk crawl]",
583459
+ `generated_at=${generatedAt}`,
583460
+ `root=${root}`,
583461
+ `files=${scan.files.length}${scan.truncated ? "+" : ""} dirs=${scan.dirs.length} total_bytes=${totalBytes}`,
583462
+ `artifact=${artifactDisplay}`,
583463
+ focusSnapshot?.directive?.requiredNextAction ? `supervisor_required_next_action=${focusSnapshot.directive.requiredNextAction}` : `supervisor_required_next_action=none`,
583464
+ recentActionLines.length ? `recent_actions:
583465
+ ${recentActionLines.join("\n")}` : "recent_actions=none",
583466
+ "Use this tree for path orientation. It includes empty directories; it does not include file contents.",
583467
+ compactTree.truncated || scan.truncated ? "Inline tree is truncated; read the artifact path above for the fuller capped tree before broad rediscovery." : "Inline tree is complete within the configured scan cap.",
583468
+ compactTree.text
583469
+ ].join("\n");
583470
+ } catch (error) {
583471
+ return [
583472
+ "[WORKSPACE TREE — fresh per-turn disk crawl]",
583473
+ `root=${root}`,
583474
+ `scan_failed=${error instanceof Error ? error.message : String(error)}`,
583475
+ "Do not assume the tree is current; use list_directory/find_files if path orientation is required."
583476
+ ].join("\n");
583477
+ }
583478
+ }
583131
583479
  /**
583132
583480
  * REG-2: Render a compact "files known to me" block from `_worldFacts`.
583133
583481
  * Goal: tell the model what it has already touched this run so it stops
@@ -583667,6 +584015,7 @@ ${chunk.content}`, {
583667
584015
  this._renderRuntimeRootBlock(),
583668
584016
  this._taskState.goal ? `Active task: (see user message above — the goal block defers to the user message as the authoritative source)` : null
583669
584017
  ].filter(Boolean).join("\n\n");
584018
+ const workspaceTreeBlock = this._renderWorkspaceTreeBlock(turn);
583670
584019
  const filesystemBlock = this._renderFilesystemStateBlock(turn);
583671
584020
  const artifactContractBlock = this._contractRegistry.format(15);
583672
584021
  const todoBlock = this._renderTodoStateBlock(turn);
@@ -583711,6 +584060,13 @@ ${this._lastPprMemoryLines.slice(0, 5).join("\n")}` : null;
583711
584060
  createdTurn: turn,
583712
584061
  ttlTurns: 1
583713
584062
  }),
584063
+ signalFromBlock("known_files", "turn.workspace-tree", workspaceTreeBlock, {
584064
+ id: "workspace-tree",
584065
+ dedupeKey: "turn.workspace-tree",
584066
+ priority: 84,
584067
+ createdTurn: turn,
584068
+ ttlTurns: 1
584069
+ }),
583714
584070
  signalFromBlock("known_files", "turn.artifact-contracts", artifactContractBlock, {
583715
584071
  id: "artifact-contracts",
583716
584072
  dedupeKey: "turn.artifact-contracts",
@@ -583799,7 +584155,7 @@ ${this._lastPprMemoryLines.slice(0, 5).join("\n")}` : null;
583799
584155
  // Raised from 10k to fit the durable read-evidence content (known_files)
583800
584156
  // that replaces the re-read loop; the per-kind budget below caps it.
583801
584157
  maxChars: 13e3,
583802
- kindCharBudget: { known_files: 6500 },
584158
+ kindCharBudget: { known_files: 8500 },
583803
584159
  includeDiagnostics: process.env["OMNIUS_CONTEXT_FABRIC_DIAGNOSTICS"] === "1"
583804
584160
  });
583805
584161
  this._lastContextFrameDiagnostics = frame.diagnostics;
@@ -584003,10 +584359,12 @@ ${blob}
584003
584359
  if (p2 && content.length >= 100) {
584004
584360
  try {
584005
584361
  this._evidenceLedger.recordRead({
584006
- path: p2,
584362
+ path: this._normalizeEvidencePath(p2),
584007
584363
  content,
584364
+ contentHash: this._extractFileReadSha256(content) ?? void 0,
584008
584365
  range: { start: 0, end: Number.POSITIVE_INFINITY },
584009
584366
  fileVersion: this._worldFacts.files.get(p2)?.writeCount ?? 0,
584367
+ mtimeMs: this._statMtimeMsForToolPath(p2),
584010
584368
  turn: this._taskState?.toolCallCount ?? 0
584011
584369
  });
584012
584370
  } catch {
@@ -587905,6 +588263,18 @@ ${memoryLines.join("\n")}`
587905
588263
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
587906
588264
  });
587907
588265
  }
588266
+ for (const tc of msg.toolCalls) {
588267
+ const patched = this._attachFreshExpectedHashesToEditArgs(tc.name, tc.arguments ?? {});
588268
+ if (patched.patchedCount > 0) {
588269
+ tc.arguments = patched.args;
588270
+ this.emit({
588271
+ type: "status",
588272
+ content: `Edit freshness: attached expected_hash to ${patched.patchedCount} ${tc.name} edit target${patched.patchedCount === 1 ? "" : "s"} from fresh file_read evidence`,
588273
+ turn,
588274
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
588275
+ });
588276
+ }
588277
+ }
587908
588278
  messages2.push({
587909
588279
  role: "assistant",
587910
588280
  content: msg.content || null,
@@ -588644,7 +589014,8 @@ Read the current file if needed, make a different concrete edit, or move to veri
588644
589014
  const readArgs = tc.arguments;
588645
589015
  const readPath2 = String(readArgs?.["path"] ?? readArgs?.["file"] ?? "");
588646
589016
  if (readPath2) {
588647
- const wasFresh = this._evidenceLedger.validateFreshness(readPath2);
589017
+ const evidencePath = this._normalizeEvidencePath(readPath2);
589018
+ const wasFresh = evidencePath && this._evidenceLedger.validateFreshness(evidencePath, this._absoluteToolPath(readPath2));
588648
589019
  if (!wasFresh) {
588649
589020
  recentToolResults.delete(toolFingerprint);
588650
589021
  dedupHitCount.delete(toolFingerprint);
@@ -588774,13 +589145,13 @@ ${cachedResult}`,
588774
589145
  error: this.unknownToolError(tc.name)
588775
589146
  };
588776
589147
  } else {
588777
- let validationError = null;
588778
- if (tool.inputSchema) {
589148
+ let validationError = this._validateFreshExpectedHashesForEdit(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
589149
+ if (!validationError && tool.inputSchema) {
588779
589150
  const parseResult = tool.inputSchema.safeParse(tc.arguments);
588780
589151
  if (!parseResult.success) {
588781
589152
  validationError = parseResult.error.issues.map((iss) => `${iss.path.join(".")}: ${iss.message}`).join("; ");
588782
589153
  }
588783
- } else if (tool.parameters && typeof tool.parameters === "object") {
589154
+ } else if (!validationError && tool.parameters && typeof tool.parameters === "object") {
588784
589155
  const required = tool.parameters.required;
588785
589156
  if (Array.isArray(required)) {
588786
589157
  const missing = required.filter((field) => tc.arguments[field] === void 0 || tc.arguments[field] === null);
@@ -589021,7 +589392,7 @@ Respond with EXACTLY this structure before your next tool call:
589021
589392
  lastWriteTurn: turn,
589022
589393
  writeCount: nextWriteCount
589023
589394
  });
589024
- this._evidenceLedger.markMutated(p2, nextWriteCount, turn);
589395
+ this._evidenceLedger.markMutated(this._normalizeEvidencePath(p2), nextWriteCount, turn);
589025
589396
  }
589026
589397
  if (paths.length > 0) {
589027
589398
  this._writesSinceLastTodoWrite += paths.length;
@@ -589065,11 +589436,12 @@ Respond with EXACTLY this structure before your next tool call:
589065
589436
  const start3 = rOffset === void 0 && rLimit === void 0 ? 0 : Math.max(1, rOffset ?? 1);
589066
589437
  const end = rOffset === void 0 && rLimit === void 0 ? Number.POSITIVE_INFINITY : rLimit !== void 0 ? Math.max(1, rOffset ?? 1) + Math.max(0, rLimit) - 1 : Number.POSITIVE_INFINITY;
589067
589438
  this._evidenceLedger.recordRead({
589068
- path: p2,
589439
+ path: this._normalizeEvidencePath(p2),
589069
589440
  content: result.output,
589441
+ contentHash: typeof result.beforeHash === "string" ? result.beforeHash : void 0,
589070
589442
  range: { start: start3, end },
589071
589443
  fileVersion: this._worldFacts.files.get(p2)?.writeCount ?? 0,
589072
- mtimeMs: Date.now(),
589444
+ mtimeMs: this._statMtimeMsForToolPath(p2),
589073
589445
  turn
589074
589446
  });
589075
589447
  }
@@ -590290,6 +590662,7 @@ Evidence: ${evidencePreview}`.slice(0, 500);
590290
590662
  focusSupervisor: focusAfterToolResult,
590291
590663
  focusDecision: !focusDecision || focusDecision.kind === "pass" ? void 0 : {
590292
590664
  kind: focusDecision.kind,
590665
+ success: focusDecision.kind === "block_tool_call" ? focusDecision.success : void 0,
590293
590666
  state: focusDecision.directive.state,
590294
590667
  reason: focusDecision.directive.reason,
590295
590668
  requiredNextAction: focusDecision.directive.requiredNextAction
@@ -590540,10 +590913,12 @@ Then use file_read on individual FILES inside it.`);
590540
590913
  const rawToolCalls = msg.toolCalls;
590541
590914
  if (this.options.streamEnabled && this._streamingExecutor.hasTools) {
590542
590915
  const streamFpInFlight = /* @__PURE__ */ new Map();
590543
- this._streamingExecutor.setExecutor(async (name10, args) => {
590544
- let matchTc = rawToolCalls.find((tc) => tc.name === name10 && JSON.stringify(tc.arguments) === JSON.stringify(args)) ?? rawToolCalls.find((tc) => tc.name === name10);
590916
+ this._streamingExecutor.setExecutor(async (id2, name10, args) => {
590917
+ const argsFingerprint = this._buildToolFingerprint(name10, args);
590918
+ const exactArgMatches = rawToolCalls.filter((tc) => tc.name === name10 && this._buildToolFingerprint(tc.name, tc.arguments ?? {}) === argsFingerprint);
590919
+ let matchTc = rawToolCalls.find((tc) => tc.id === id2) ?? (exactArgMatches.length === 1 ? exactArgMatches[0] : void 0);
590545
590920
  if (!matchTc) {
590546
- const synthId = globalThis.crypto?.randomUUID?.() || `call_${Date.now()}`;
590921
+ const synthId = id2 || globalThis.crypto?.randomUUID?.() || `call_${Date.now()}`;
590547
590922
  matchTc = { id: synthId, name: name10, arguments: args };
590548
590923
  messages2.push({
590549
590924
  role: "assistant",
@@ -592764,13 +593139,14 @@ ${marker}` : marker);
592764
593139
  const displayOutput = this.unwrapToolOutputForDisplay(output);
592765
593140
  if (toolName === "file_read") {
592766
593141
  const path13 = this.extractPrimaryToolPath(args);
592767
- if (!path13 || !this._evidenceLedger.hasFresh(path13))
593142
+ const evidencePath = path13 ? this._normalizeEvidencePath(path13) : "";
593143
+ if (!evidencePath || !this._evidenceLedger.hasFresh(evidencePath))
592768
593144
  return output;
592769
- const entry = this._evidenceLedger.get(path13);
593145
+ const entry = this._evidenceLedger.get(evidencePath);
592770
593146
  return this.wrapToolOutputForModel(toolName, [
592771
593147
  "[DISCOVERY COMPACTED - file_read evidence captured]",
592772
593148
  `path=${path13}`,
592773
- `captured=${this.countTextLines(displayOutput)} lines, ${displayOutput.length} chars${entry ? `, fidelity=${entry.fidelity}` : ""}`,
593149
+ `captured=${this.countTextLines(displayOutput)} lines, ${displayOutput.length} chars${entry ? `, fidelity=${entry.fidelity}${entry.contentHash ? `, sha256=${entry.contentHash}` : ""}` : ""}`,
592774
593150
  'active_context="Evidence already gathered this run"',
592775
593151
  "Use the active evidence frame. Re-read only if that frame marks this file STALE or you need a distinct range."
592776
593152
  ].join("\n"));
@@ -592916,6 +593292,175 @@ ${marker}` : marker);
592916
593292
  return trimmed.replace(/\\/g, "/").replace(/^\.\/+/, "");
592917
593293
  }
592918
593294
  }
593295
+ _absoluteToolPath(path12) {
593296
+ return _pathResolve(this.authoritativeWorkingDirectory(), path12.trim() || ".");
593297
+ }
593298
+ _normalizeEvidencePath(path12) {
593299
+ const trimmed = path12.trim();
593300
+ if (!trimmed)
593301
+ return "";
593302
+ try {
593303
+ const root = this.authoritativeWorkingDirectory();
593304
+ const abs = _pathResolve(root, trimmed);
593305
+ const rel = _pathRelative(root, abs).replace(/\\/g, "/");
593306
+ if (rel && !rel.startsWith("..") && rel !== ".")
593307
+ return rel;
593308
+ if (rel === "")
593309
+ return ".";
593310
+ return abs.replace(/\\/g, "/");
593311
+ } catch {
593312
+ return trimmed.replace(/\\/g, "/").replace(/^\.\/+/, "");
593313
+ }
593314
+ }
593315
+ _statMtimeMsForToolPath(path12) {
593316
+ try {
593317
+ const st = _fsStatSync(this._absoluteToolPath(path12), {
593318
+ throwIfNoEntry: false
593319
+ });
593320
+ return st?.mtimeMs;
593321
+ } catch {
593322
+ return void 0;
593323
+ }
593324
+ }
593325
+ _normalizeExpectedHashValue(value2) {
593326
+ if (typeof value2 !== "string")
593327
+ return null;
593328
+ const raw = value2.trim();
593329
+ if (!raw)
593330
+ return null;
593331
+ const stripped = raw.replace(/^sha256[:=]/i, "").toLowerCase();
593332
+ return /^[a-f0-9]{64}$/.test(stripped) ? stripped : null;
593333
+ }
593334
+ _extractFileReadSha256(text2) {
593335
+ const match = text2.match(/\bsha256=([a-f0-9]{64})\b/i);
593336
+ return match ? match[1].toLowerCase() : null;
593337
+ }
593338
+ _rawExpectedHashValue(rec) {
593339
+ return rec["expected_hash"] ?? rec["expectedHash"] ?? rec["before_hash"] ?? rec["beforeHash"];
593340
+ }
593341
+ _freshReadHashForEditPath(path12) {
593342
+ const key = this._normalizeEvidencePath(path12);
593343
+ if (!key)
593344
+ return null;
593345
+ const entry = this._evidenceLedger.get(key);
593346
+ if (!entry || entry.stale || !entry.contentHash)
593347
+ return null;
593348
+ const statPath = this._absoluteToolPath(path12);
593349
+ if (!this._evidenceLedger.validateFreshness(key, statPath))
593350
+ return null;
593351
+ const fresh = this._evidenceLedger.get(key);
593352
+ if (!fresh || fresh.stale || !fresh.contentHash)
593353
+ return null;
593354
+ return {
593355
+ key,
593356
+ hash: fresh.contentHash,
593357
+ lastReadTurn: fresh.lastReadTurn
593358
+ };
593359
+ }
593360
+ _attachFreshExpectedHashesToEditArgs(toolName, args) {
593361
+ if (process.env["OMNIUS_ALLOW_UNVERIFIED_OLD_STRING_EDIT"] === "1") {
593362
+ return { args, patchedCount: 0 };
593363
+ }
593364
+ if (toolName === "file_edit") {
593365
+ if (this._normalizeExpectedHashValue(this._rawExpectedHashValue(args))) {
593366
+ return { args, patchedCount: 0 };
593367
+ }
593368
+ const path12 = this.extractPrimaryToolPath(args);
593369
+ const fresh = path12 ? this._freshReadHashForEditPath(path12) : null;
593370
+ if (!fresh)
593371
+ return { args, patchedCount: 0 };
593372
+ return { args: { ...args, expected_hash: fresh.hash }, patchedCount: 1 };
593373
+ }
593374
+ if (toolName !== "batch_edit" || !Array.isArray(args["edits"])) {
593375
+ return { args, patchedCount: 0 };
593376
+ }
593377
+ let patchedCount = 0;
593378
+ const edits = args["edits"].map((edit) => {
593379
+ if (!edit || typeof edit !== "object" || Array.isArray(edit))
593380
+ return edit;
593381
+ const rec = edit;
593382
+ if (this._normalizeExpectedHashValue(this._rawExpectedHashValue(rec))) {
593383
+ return rec;
593384
+ }
593385
+ const path12 = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : "";
593386
+ const fresh = path12 ? this._freshReadHashForEditPath(path12) : null;
593387
+ if (!fresh)
593388
+ return rec;
593389
+ patchedCount++;
593390
+ return { ...rec, expected_hash: fresh.hash };
593391
+ });
593392
+ return patchedCount > 0 ? { args: { ...args, edits }, patchedCount } : { args, patchedCount: 0 };
593393
+ }
593394
+ _validateFreshExpectedHashesForEdit(toolName, args) {
593395
+ if (process.env["OMNIUS_ALLOW_UNVERIFIED_OLD_STRING_EDIT"] === "1") {
593396
+ return null;
593397
+ }
593398
+ const targets = [];
593399
+ if (toolName === "file_edit") {
593400
+ const path12 = this.extractPrimaryToolPath(args);
593401
+ if (path12) {
593402
+ targets.push({
593403
+ label: path12,
593404
+ path: path12,
593405
+ expectedHash: this._normalizeExpectedHashValue(this._rawExpectedHashValue(args)),
593406
+ rawExpectedHash: this._rawExpectedHashValue(args)
593407
+ });
593408
+ }
593409
+ } else if (toolName === "batch_edit" && Array.isArray(args["edits"])) {
593410
+ args["edits"].forEach((edit, index) => {
593411
+ if (!edit || typeof edit !== "object" || Array.isArray(edit))
593412
+ return;
593413
+ const rec = edit;
593414
+ const path12 = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : "";
593415
+ if (!path12)
593416
+ return;
593417
+ targets.push({
593418
+ label: `edit #${index + 1} ${path12}`,
593419
+ path: path12,
593420
+ expectedHash: this._normalizeExpectedHashValue(this._rawExpectedHashValue(rec)),
593421
+ rawExpectedHash: this._rawExpectedHashValue(rec)
593422
+ });
593423
+ });
593424
+ } else {
593425
+ return null;
593426
+ }
593427
+ if (targets.length === 0)
593428
+ return null;
593429
+ const problems = [];
593430
+ for (const target of targets) {
593431
+ const fresh = this._freshReadHashForEditPath(target.path);
593432
+ if (target.rawExpectedHash !== void 0 && !target.expectedHash) {
593433
+ problems.push(`${target.label}: expected_hash is present but is not a valid 64-character SHA-256`);
593434
+ continue;
593435
+ }
593436
+ if (!fresh && target.expectedHash) {
593437
+ continue;
593438
+ }
593439
+ if (!fresh) {
593440
+ problems.push(`${target.label}: missing expected_hash and no fresh file_read hash is available for this file in the current run`);
593441
+ continue;
593442
+ }
593443
+ if (!target.expectedHash) {
593444
+ problems.push(`${target.label}: missing expected_hash; use sha256=${fresh.hash} from the fresh file_read evidence`);
593445
+ continue;
593446
+ }
593447
+ if (target.expectedHash !== fresh.hash) {
593448
+ problems.push(`${target.label}: expected_hash=${target.expectedHash} does not match fresh file_read sha256=${fresh.hash}`);
593449
+ }
593450
+ }
593451
+ if (problems.length === 0)
593452
+ return null;
593453
+ return [
593454
+ "[EDIT FRESHNESS CONTRACT]",
593455
+ "Exact replacement edits must be built from verified current file content, not remembered text.",
593456
+ "Before file_edit or batch_edit can run, each target must carry expected_hash from file_read. If the runner has fresh read evidence, it will attach that hash automatically; otherwise the edit tool verifies the provided hash against disk before mutating.",
593457
+ "For multiple edits to the same file, use one batch_edit against the same expected_hash so all replacements validate atomically before the file changes.",
593458
+ "",
593459
+ ...problems.map((problem) => `- ${problem}`),
593460
+ "",
593461
+ "Next action: file_read the target file or range, then retry the edit with old_string copied exactly from that read and expected_hash set to the read's sha256."
593462
+ ].join("\n");
593463
+ }
592919
593464
  staleEditFamilyKey(toolName, pathKey, errorKind, targetHash, latestFileHash) {
592920
593465
  return [
592921
593466
  toolName,
@@ -596763,14 +597308,17 @@ ${description}`
596763
597308
  }
596764
597309
  }
596765
597310
  const acc = toolCallAccumulators.get(idx);
597311
+ if (chunk.toolCallId && chunk.toolCallId !== acc.id) {
597312
+ const previousId = acc.id;
597313
+ acc.id = chunk.toolCallId;
597314
+ this._streamingExecutor.rename(previousId, acc.id);
597315
+ }
596766
597316
  if (chunk.toolCallName) {
596767
597317
  acc.name = chunk.toolCallName;
596768
597318
  if (!this._streamingExecutor.getStates().has(acc.id) && acc.name) {
596769
597319
  this._streamingExecutor.queue(acc.id, acc.name);
596770
597320
  }
596771
597321
  }
596772
- if (chunk.toolCallId)
596773
- acc.id = chunk.toolCallId;
596774
597322
  if (chunk.toolCallArgs) {
596775
597323
  acc.args += chunk.toolCallArgs;
596776
597324
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.457",
3
+ "version": "1.0.459",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.457",
9
+ "version": "1.0.459",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
@@ -4138,9 +4138,9 @@
4138
4138
  }
4139
4139
  },
4140
4140
  "node_modules/iconv-lite": {
4141
- "version": "0.7.2",
4142
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
4143
- "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
4141
+ "version": "0.7.3",
4142
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
4143
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
4144
4144
  "license": "MIT",
4145
4145
  "dependencies": {
4146
4146
  "safer-buffer": ">= 2.1.2 < 3.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.457",
3
+ "version": "1.0.459",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",