omnius 1.0.457 → 1.0.458

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)
@@ -573437,6 +573530,7 @@ var init_evidenceLedger = __esm({
573437
573530
  this.entries.set(path12, {
573438
573531
  ...existing,
573439
573532
  content: keepNew ? built.text : existing.content,
573533
+ contentHash: input.contentHash ?? existing.contentHash,
573440
573534
  fidelity: keepNew ? built.fidelity : existing.fidelity,
573441
573535
  ranges: mergeRanges([...existing.ranges, range]),
573442
573536
  lastReadTurn: turn,
@@ -573447,6 +573541,7 @@ var init_evidenceLedger = __esm({
573447
573541
  }
573448
573542
  this.entries.set(path12, {
573449
573543
  path: path12,
573544
+ contentHash: input.contentHash,
573450
573545
  readVersion: fileVersion,
573451
573546
  mtimeMs: input.mtimeMs ?? 0,
573452
573547
  lastReadTurn: turn,
@@ -573475,7 +573570,7 @@ var init_evidenceLedger = __esm({
573475
573570
  * like a manual chmod, device reconnect, or human edit), mark as stale.
573476
573571
  * Returns true if evidence was valid, false if became stale or stat failed.
573477
573572
  */
573478
- validateFreshness(path12) {
573573
+ validateFreshness(path12, statPath = path12) {
573479
573574
  const e2 = this.entries.get(path12);
573480
573575
  if (!e2)
573481
573576
  return false;
@@ -573484,7 +573579,7 @@ var init_evidenceLedger = __esm({
573484
573579
  if (e2.mtimeMs <= 0)
573485
573580
  return true;
573486
573581
  try {
573487
- const st = statSync36(path12, { throwIfNoEntry: false });
573582
+ const st = statSync36(statPath, { throwIfNoEntry: false });
573488
573583
  if (!st)
573489
573584
  return true;
573490
573585
  if (st.mtimeMs > e2.mtimeMs) {
@@ -573499,6 +573594,11 @@ var init_evidenceLedger = __esm({
573499
573594
  get(path12) {
573500
573595
  return this.entries.get(path12);
573501
573596
  }
573597
+ markStale(path12) {
573598
+ const e2 = this.entries.get(path12);
573599
+ if (e2)
573600
+ e2.stale = true;
573601
+ }
573502
573602
  size() {
573503
573603
  return this.entries.size;
573504
573604
  }
@@ -573521,12 +573621,12 @@ var init_evidenceLedger = __esm({
573521
573621
  ];
573522
573622
  let used = lines.join("\n").length;
573523
573623
  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)}):`;
573624
+ 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
573625
  const body = e2.stale ? "" : "\n" + indent(e2.content);
573526
573626
  const chunk = `${header}${body}
573527
573627
  `;
573528
573628
  if (used + chunk.length > maxChars) {
573529
- const stub = `▸ ${e2.path} @v${e2.readVersion} [${e2.stale ? "STALE" : "FRESH"}] (${rangeLabel(e2.ranges)})
573629
+ const stub = `▸ ${e2.path} @v${e2.readVersion} [${e2.stale ? "STALE" : `FRESH${e2.contentHash ? ` sha256=${e2.contentHash}` : ""}`}] (${rangeLabel(e2.ranges)})
573530
573630
  `;
573531
573631
  if (used + stub.length <= maxChars) {
573532
573632
  lines.push(stub);
@@ -574603,9 +574703,22 @@ ${input.error}`;
574603
574703
  }
574604
574704
  if (input.realFileMutation)
574605
574705
  diagnoses.add("project-file-mutation");
574606
- const focusDecisionJson = previewJson(input.focusDecision ?? {});
574607
- if (focusDecisionJson.includes("block_tool_call")) {
574608
- diagnoses.add("focus-supervisor-block");
574706
+ const focusDecision = input.focusDecision && typeof input.focusDecision === "object" ? input.focusDecision : null;
574707
+ const focusDecisionKind = typeof focusDecision?.["kind"] === "string" ? focusDecision["kind"] : null;
574708
+ if (focusDecisionKind === "block_tool_call") {
574709
+ if (focusDecision?.["success"] === true || input.success) {
574710
+ diagnoses.add("focus-supervisor-cached-evidence");
574711
+ } else {
574712
+ diagnoses.add("focus-supervisor-block");
574713
+ }
574714
+ } else {
574715
+ const focusDecisionJson = previewJson(input.focusDecision ?? {});
574716
+ if (focusDecisionJson.includes("block_tool_call")) {
574717
+ if (input.success)
574718
+ diagnoses.add("focus-supervisor-cached-evidence");
574719
+ else
574720
+ diagnoses.add("focus-supervisor-block");
574721
+ }
574609
574722
  }
574610
574723
  const focusJson = previewJson(input.focusSupervisor ?? {});
574611
574724
  if (focusJson.includes("block_tool_call") || focusJson.includes("requiredNextAction")) {
@@ -574736,7 +574849,8 @@ function writeContract(contractPath) {
574736
574849
  "",
574737
574850
  "- `shell-brace-whitespace`: a shell command used brace expansion containing whitespace, which can split an intended path set into unrelated shell words.",
574738
574851
  "- `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.",
574852
+ "- `focus-supervisor-block`: the tool result or supervisor state shows an unsuccessful blocked next action.",
574853
+ "- `focus-supervisor-cached-evidence`: the focus supervisor short-circuited a call successfully by reusing current cached evidence.",
574740
574854
  "- `background-task-id-missing`: task status lookup could not find the spawned task id.",
574741
574855
  "- `raw-discovery-dominates-context`: context-window metrics show low signal/noise around raw discovery output.",
574742
574856
  ""
@@ -577523,7 +577637,7 @@ RECOVERY: cd to the directory containing '${file}', run a plain install with no
577523
577637
  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
577638
  import { execFile as _execFile, spawn as _spawn } from "node:child_process";
577525
577639
  import { createHash as _createHash } from "node:crypto";
577526
- import { join as _pathJoin, resolve as _pathResolve } from "node:path";
577640
+ import { join as _pathJoin, relative as _pathRelative, resolve as _pathResolve } from "node:path";
577527
577641
  import { tmpdir as _osTmpdir } from "node:os";
577528
577642
  import { homedir as _osHomedir } from "node:os";
577529
577643
  import { z as z17 } from "zod";
@@ -578186,6 +578300,7 @@ var init_agenticRunner = __esm({
578186
578300
  init_lesson_bank();
578187
578301
  init_intervention_replay();
578188
578302
  init_world_state_regenerator();
578303
+ init_world_state_disk_scan();
578189
578304
  init_backward_pass_runner();
578190
578305
  init_world_state_plan_reconciler();
578191
578306
  init_process_async2();
@@ -581019,7 +581134,7 @@ ${result.error ?? ""}`;
581019
581134
  if (result.success !== true)
581020
581135
  return false;
581021
581136
  if (toolName === "file_read") {
581022
- return targetPaths.length > 0 && targetPaths.some((path12) => this._evidenceLedger.hasFresh(path12));
581137
+ return targetPaths.length > 0 && targetPaths.some((path12) => this._evidenceLedger.hasFresh(this._normalizeEvidencePath(path12)));
581023
581138
  }
581024
581139
  return toolName === "list_directory" || toolName === "find_files" || toolName === "grep_search";
581025
581140
  }
@@ -583128,6 +583243,133 @@ ${latest.output || ""}`.trim();
583128
583243
  return null;
583129
583244
  }
583130
583245
  }
583246
+ /**
583247
+ * Render a fresh, bounded workspace tree from disk for the active context
583248
+ * frame, and persist the fuller capped tree under .omnius/world-state/tree.
583249
+ * This is deliberately independent of tool history: it answers "what exists
583250
+ * where right now?" without forcing the model to list_directory for basic
583251
+ * orientation.
583252
+ */
583253
+ _renderWorkspaceTreeBlock(turn) {
583254
+ const rawEnabled = String(process.env["OMNIUS_CONTEXT_WORKSPACE_TREE"] ?? "1").trim().toLowerCase();
583255
+ if (rawEnabled === "0" || rawEnabled === "false" || rawEnabled === "off") {
583256
+ return null;
583257
+ }
583258
+ const intEnv = (name10, fallback, min, max) => {
583259
+ const parsed = Number.parseInt(process.env[name10] ?? "", 10);
583260
+ return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback;
583261
+ };
583262
+ const root = this.authoritativeWorkingDirectory();
583263
+ const maxFiles = intEnv("OMNIUS_CONTEXT_TREE_MAX_FILES", 1e3, 20, 2e3);
583264
+ const maxDirs = intEnv("OMNIUS_CONTEXT_TREE_MAX_DIRS", 1500, 20, 5e3);
583265
+ const contextLines = intEnv("OMNIUS_CONTEXT_TREE_LINES", 90, 20, 400);
583266
+ const artifactLines = intEnv("OMNIUS_CONTEXT_TREE_ARTIFACT_LINES", 1800, 100, 4e3);
583267
+ try {
583268
+ const scan = scanWorkspace({ root, maxFiles, maxDirs });
583269
+ const compactTree = renderWorkspaceTree(scan, { maxLines: contextLines });
583270
+ const fullTree = renderWorkspaceTree(scan, { maxLines: artifactLines });
583271
+ const totalBytes = scan.files.reduce((sum, file) => sum + file.bytes, 0);
583272
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
583273
+ const focusSnapshot = this._focusSupervisor?.snapshot();
583274
+ const recentActions = this._adversaryToolOutcomes.slice(-12).map((item) => ({
583275
+ turn: item.turn,
583276
+ tool: item.tool,
583277
+ success: item.succeeded,
583278
+ path: item.path,
583279
+ stateVersion: item.stateVersion,
583280
+ preview: item.preview.slice(0, 240)
583281
+ }));
583282
+ const recentActionLines = recentActions.slice(-6).map((item) => {
583283
+ const path12 = item.path ? ` path=${item.path}` : "";
583284
+ return `- turn=${item.turn} ${item.tool} ${item.success ? "ok" : "err"}${path12}: ${item.preview.replace(/\s+/g, " ").slice(0, 160)}`;
583285
+ });
583286
+ const treeDir = _pathJoin(this.omniusStateDir(), "world-state", "tree");
583287
+ const turnName = `turn-${String(turn).padStart(4, "0")}`;
583288
+ const latestTextPath = _pathJoin(treeDir, "latest.txt");
583289
+ const latestJsonPath = _pathJoin(treeDir, "latest.json");
583290
+ const turnTextPath = _pathJoin(treeDir, `${turnName}.txt`);
583291
+ const turnJsonPath = _pathJoin(treeDir, `${turnName}.json`);
583292
+ let artifactDisplay = latestTextPath;
583293
+ try {
583294
+ _fsMkdirSync(treeDir, { recursive: true });
583295
+ const header = [
583296
+ `WORKSPACE TREE SNAPSHOT`,
583297
+ `generated_at=${generatedAt}`,
583298
+ `turn=${turn}`,
583299
+ `session=${this._sessionId ?? ""}`,
583300
+ `root=${root}`,
583301
+ `files=${scan.files.length}${scan.truncated ? "+" : ""}`,
583302
+ `dirs=${scan.dirs.length}`,
583303
+ `bytes=${totalBytes}`,
583304
+ focusSnapshot?.directive?.requiredNextAction ? `supervisor_required_next_action=${focusSnapshot.directive.requiredNextAction}` : `supervisor_required_next_action=none`,
583305
+ recentActionLines.length ? `recent_actions=${recentActions.length}` : `recent_actions=0`,
583306
+ recentActionLines.length ? recentActionLines.join("\n") : "",
583307
+ ""
583308
+ ].join("\n");
583309
+ const text2 = `${header}${fullTree.text}
583310
+ `;
583311
+ const payload = {
583312
+ generatedAt,
583313
+ turn,
583314
+ sessionId: this._sessionId,
583315
+ root,
583316
+ supervisor: focusSnapshot ? {
583317
+ state: focusSnapshot.state,
583318
+ lastDecision: focusSnapshot.lastDecision,
583319
+ lastReason: focusSnapshot.lastReason,
583320
+ requiredNextAction: focusSnapshot.directive?.requiredNextAction ?? null,
583321
+ directiveReason: focusSnapshot.directive?.reason ?? null
583322
+ } : null,
583323
+ recentActions,
583324
+ stats: {
583325
+ files: scan.files.length,
583326
+ dirs: scan.dirs.length,
583327
+ totalBytes,
583328
+ truncated: scan.truncated || fullTree.truncated,
583329
+ dirsVisited: scan.dirsVisited
583330
+ },
583331
+ files: scan.files.map((file) => ({
583332
+ path: file.rel,
583333
+ bytes: file.bytes,
583334
+ mtimeMs: file.mtimeMs
583335
+ })),
583336
+ dirs: scan.dirs.map((dir) => ({
583337
+ path: dir.rel,
583338
+ mtimeMs: dir.mtimeMs
583339
+ }))
583340
+ };
583341
+ _fsWriteFileSync(latestTextPath, text2, "utf8");
583342
+ _fsWriteFileSync(turnTextPath, text2, "utf8");
583343
+ _fsWriteFileSync(latestJsonPath, JSON.stringify(payload, null, 2), "utf8");
583344
+ _fsWriteFileSync(turnJsonPath, JSON.stringify(payload, null, 2), "utf8");
583345
+ const rel = _pathRelative(root, latestTextPath).replace(/\\/g, "/");
583346
+ if (rel && !rel.startsWith(".."))
583347
+ artifactDisplay = rel;
583348
+ } catch {
583349
+ artifactDisplay = "(workspace tree artifact write failed; inline digest still current)";
583350
+ }
583351
+ return [
583352
+ "[WORKSPACE TREE — fresh per-turn disk crawl]",
583353
+ `generated_at=${generatedAt}`,
583354
+ `root=${root}`,
583355
+ `files=${scan.files.length}${scan.truncated ? "+" : ""} dirs=${scan.dirs.length} total_bytes=${totalBytes}`,
583356
+ `artifact=${artifactDisplay}`,
583357
+ focusSnapshot?.directive?.requiredNextAction ? `supervisor_required_next_action=${focusSnapshot.directive.requiredNextAction}` : `supervisor_required_next_action=none`,
583358
+ recentActionLines.length ? `recent_actions:
583359
+ ${recentActionLines.join("\n")}` : "recent_actions=none",
583360
+ "Use this tree for path orientation. It includes empty directories; it does not include file contents.",
583361
+ 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.",
583362
+ compactTree.text
583363
+ ].join("\n");
583364
+ } catch (error) {
583365
+ return [
583366
+ "[WORKSPACE TREE — fresh per-turn disk crawl]",
583367
+ `root=${root}`,
583368
+ `scan_failed=${error instanceof Error ? error.message : String(error)}`,
583369
+ "Do not assume the tree is current; use list_directory/find_files if path orientation is required."
583370
+ ].join("\n");
583371
+ }
583372
+ }
583131
583373
  /**
583132
583374
  * REG-2: Render a compact "files known to me" block from `_worldFacts`.
583133
583375
  * Goal: tell the model what it has already touched this run so it stops
@@ -583667,6 +583909,7 @@ ${chunk.content}`, {
583667
583909
  this._renderRuntimeRootBlock(),
583668
583910
  this._taskState.goal ? `Active task: (see user message above — the goal block defers to the user message as the authoritative source)` : null
583669
583911
  ].filter(Boolean).join("\n\n");
583912
+ const workspaceTreeBlock = this._renderWorkspaceTreeBlock(turn);
583670
583913
  const filesystemBlock = this._renderFilesystemStateBlock(turn);
583671
583914
  const artifactContractBlock = this._contractRegistry.format(15);
583672
583915
  const todoBlock = this._renderTodoStateBlock(turn);
@@ -583711,6 +583954,13 @@ ${this._lastPprMemoryLines.slice(0, 5).join("\n")}` : null;
583711
583954
  createdTurn: turn,
583712
583955
  ttlTurns: 1
583713
583956
  }),
583957
+ signalFromBlock("known_files", "turn.workspace-tree", workspaceTreeBlock, {
583958
+ id: "workspace-tree",
583959
+ dedupeKey: "turn.workspace-tree",
583960
+ priority: 84,
583961
+ createdTurn: turn,
583962
+ ttlTurns: 1
583963
+ }),
583714
583964
  signalFromBlock("known_files", "turn.artifact-contracts", artifactContractBlock, {
583715
583965
  id: "artifact-contracts",
583716
583966
  dedupeKey: "turn.artifact-contracts",
@@ -583799,7 +584049,7 @@ ${this._lastPprMemoryLines.slice(0, 5).join("\n")}` : null;
583799
584049
  // Raised from 10k to fit the durable read-evidence content (known_files)
583800
584050
  // that replaces the re-read loop; the per-kind budget below caps it.
583801
584051
  maxChars: 13e3,
583802
- kindCharBudget: { known_files: 6500 },
584052
+ kindCharBudget: { known_files: 8500 },
583803
584053
  includeDiagnostics: process.env["OMNIUS_CONTEXT_FABRIC_DIAGNOSTICS"] === "1"
583804
584054
  });
583805
584055
  this._lastContextFrameDiagnostics = frame.diagnostics;
@@ -584003,10 +584253,12 @@ ${blob}
584003
584253
  if (p2 && content.length >= 100) {
584004
584254
  try {
584005
584255
  this._evidenceLedger.recordRead({
584006
- path: p2,
584256
+ path: this._normalizeEvidencePath(p2),
584007
584257
  content,
584258
+ contentHash: this._extractFileReadSha256(content) ?? void 0,
584008
584259
  range: { start: 0, end: Number.POSITIVE_INFINITY },
584009
584260
  fileVersion: this._worldFacts.files.get(p2)?.writeCount ?? 0,
584261
+ mtimeMs: this._statMtimeMsForToolPath(p2),
584010
584262
  turn: this._taskState?.toolCallCount ?? 0
584011
584263
  });
584012
584264
  } catch {
@@ -587905,6 +588157,18 @@ ${memoryLines.join("\n")}`
587905
588157
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
587906
588158
  });
587907
588159
  }
588160
+ for (const tc of msg.toolCalls) {
588161
+ const patched = this._attachFreshExpectedHashesToEditArgs(tc.name, tc.arguments ?? {});
588162
+ if (patched.patchedCount > 0) {
588163
+ tc.arguments = patched.args;
588164
+ this.emit({
588165
+ type: "status",
588166
+ content: `Edit freshness: attached expected_hash to ${patched.patchedCount} ${tc.name} edit target${patched.patchedCount === 1 ? "" : "s"} from fresh file_read evidence`,
588167
+ turn,
588168
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
588169
+ });
588170
+ }
588171
+ }
587908
588172
  messages2.push({
587909
588173
  role: "assistant",
587910
588174
  content: msg.content || null,
@@ -588644,7 +588908,8 @@ Read the current file if needed, make a different concrete edit, or move to veri
588644
588908
  const readArgs = tc.arguments;
588645
588909
  const readPath2 = String(readArgs?.["path"] ?? readArgs?.["file"] ?? "");
588646
588910
  if (readPath2) {
588647
- const wasFresh = this._evidenceLedger.validateFreshness(readPath2);
588911
+ const evidencePath = this._normalizeEvidencePath(readPath2);
588912
+ const wasFresh = evidencePath && this._evidenceLedger.validateFreshness(evidencePath, this._absoluteToolPath(readPath2));
588648
588913
  if (!wasFresh) {
588649
588914
  recentToolResults.delete(toolFingerprint);
588650
588915
  dedupHitCount.delete(toolFingerprint);
@@ -588774,13 +589039,13 @@ ${cachedResult}`,
588774
589039
  error: this.unknownToolError(tc.name)
588775
589040
  };
588776
589041
  } else {
588777
- let validationError = null;
588778
- if (tool.inputSchema) {
589042
+ let validationError = this._validateFreshExpectedHashesForEdit(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
589043
+ if (!validationError && tool.inputSchema) {
588779
589044
  const parseResult = tool.inputSchema.safeParse(tc.arguments);
588780
589045
  if (!parseResult.success) {
588781
589046
  validationError = parseResult.error.issues.map((iss) => `${iss.path.join(".")}: ${iss.message}`).join("; ");
588782
589047
  }
588783
- } else if (tool.parameters && typeof tool.parameters === "object") {
589048
+ } else if (!validationError && tool.parameters && typeof tool.parameters === "object") {
588784
589049
  const required = tool.parameters.required;
588785
589050
  if (Array.isArray(required)) {
588786
589051
  const missing = required.filter((field) => tc.arguments[field] === void 0 || tc.arguments[field] === null);
@@ -589021,7 +589286,7 @@ Respond with EXACTLY this structure before your next tool call:
589021
589286
  lastWriteTurn: turn,
589022
589287
  writeCount: nextWriteCount
589023
589288
  });
589024
- this._evidenceLedger.markMutated(p2, nextWriteCount, turn);
589289
+ this._evidenceLedger.markMutated(this._normalizeEvidencePath(p2), nextWriteCount, turn);
589025
589290
  }
589026
589291
  if (paths.length > 0) {
589027
589292
  this._writesSinceLastTodoWrite += paths.length;
@@ -589065,11 +589330,12 @@ Respond with EXACTLY this structure before your next tool call:
589065
589330
  const start3 = rOffset === void 0 && rLimit === void 0 ? 0 : Math.max(1, rOffset ?? 1);
589066
589331
  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
589332
  this._evidenceLedger.recordRead({
589068
- path: p2,
589333
+ path: this._normalizeEvidencePath(p2),
589069
589334
  content: result.output,
589335
+ contentHash: typeof result.beforeHash === "string" ? result.beforeHash : void 0,
589070
589336
  range: { start: start3, end },
589071
589337
  fileVersion: this._worldFacts.files.get(p2)?.writeCount ?? 0,
589072
- mtimeMs: Date.now(),
589338
+ mtimeMs: this._statMtimeMsForToolPath(p2),
589073
589339
  turn
589074
589340
  });
589075
589341
  }
@@ -590290,6 +590556,7 @@ Evidence: ${evidencePreview}`.slice(0, 500);
590290
590556
  focusSupervisor: focusAfterToolResult,
590291
590557
  focusDecision: !focusDecision || focusDecision.kind === "pass" ? void 0 : {
590292
590558
  kind: focusDecision.kind,
590559
+ success: focusDecision.kind === "block_tool_call" ? focusDecision.success : void 0,
590293
590560
  state: focusDecision.directive.state,
590294
590561
  reason: focusDecision.directive.reason,
590295
590562
  requiredNextAction: focusDecision.directive.requiredNextAction
@@ -592764,13 +593031,14 @@ ${marker}` : marker);
592764
593031
  const displayOutput = this.unwrapToolOutputForDisplay(output);
592765
593032
  if (toolName === "file_read") {
592766
593033
  const path13 = this.extractPrimaryToolPath(args);
592767
- if (!path13 || !this._evidenceLedger.hasFresh(path13))
593034
+ const evidencePath = path13 ? this._normalizeEvidencePath(path13) : "";
593035
+ if (!evidencePath || !this._evidenceLedger.hasFresh(evidencePath))
592768
593036
  return output;
592769
- const entry = this._evidenceLedger.get(path13);
593037
+ const entry = this._evidenceLedger.get(evidencePath);
592770
593038
  return this.wrapToolOutputForModel(toolName, [
592771
593039
  "[DISCOVERY COMPACTED - file_read evidence captured]",
592772
593040
  `path=${path13}`,
592773
- `captured=${this.countTextLines(displayOutput)} lines, ${displayOutput.length} chars${entry ? `, fidelity=${entry.fidelity}` : ""}`,
593041
+ `captured=${this.countTextLines(displayOutput)} lines, ${displayOutput.length} chars${entry ? `, fidelity=${entry.fidelity}${entry.contentHash ? `, sha256=${entry.contentHash}` : ""}` : ""}`,
592774
593042
  'active_context="Evidence already gathered this run"',
592775
593043
  "Use the active evidence frame. Re-read only if that frame marks this file STALE or you need a distinct range."
592776
593044
  ].join("\n"));
@@ -592916,6 +593184,175 @@ ${marker}` : marker);
592916
593184
  return trimmed.replace(/\\/g, "/").replace(/^\.\/+/, "");
592917
593185
  }
592918
593186
  }
593187
+ _absoluteToolPath(path12) {
593188
+ return _pathResolve(this.authoritativeWorkingDirectory(), path12.trim() || ".");
593189
+ }
593190
+ _normalizeEvidencePath(path12) {
593191
+ const trimmed = path12.trim();
593192
+ if (!trimmed)
593193
+ return "";
593194
+ try {
593195
+ const root = this.authoritativeWorkingDirectory();
593196
+ const abs = _pathResolve(root, trimmed);
593197
+ const rel = _pathRelative(root, abs).replace(/\\/g, "/");
593198
+ if (rel && !rel.startsWith("..") && rel !== ".")
593199
+ return rel;
593200
+ if (rel === "")
593201
+ return ".";
593202
+ return abs.replace(/\\/g, "/");
593203
+ } catch {
593204
+ return trimmed.replace(/\\/g, "/").replace(/^\.\/+/, "");
593205
+ }
593206
+ }
593207
+ _statMtimeMsForToolPath(path12) {
593208
+ try {
593209
+ const st = _fsStatSync(this._absoluteToolPath(path12), {
593210
+ throwIfNoEntry: false
593211
+ });
593212
+ return st?.mtimeMs;
593213
+ } catch {
593214
+ return void 0;
593215
+ }
593216
+ }
593217
+ _normalizeExpectedHashValue(value2) {
593218
+ if (typeof value2 !== "string")
593219
+ return null;
593220
+ const raw = value2.trim();
593221
+ if (!raw)
593222
+ return null;
593223
+ const stripped = raw.replace(/^sha256[:=]/i, "").toLowerCase();
593224
+ return /^[a-f0-9]{64}$/.test(stripped) ? stripped : null;
593225
+ }
593226
+ _extractFileReadSha256(text2) {
593227
+ const match = text2.match(/\bsha256=([a-f0-9]{64})\b/i);
593228
+ return match ? match[1].toLowerCase() : null;
593229
+ }
593230
+ _rawExpectedHashValue(rec) {
593231
+ return rec["expected_hash"] ?? rec["expectedHash"] ?? rec["before_hash"] ?? rec["beforeHash"];
593232
+ }
593233
+ _freshReadHashForEditPath(path12) {
593234
+ const key = this._normalizeEvidencePath(path12);
593235
+ if (!key)
593236
+ return null;
593237
+ const entry = this._evidenceLedger.get(key);
593238
+ if (!entry || entry.stale || !entry.contentHash)
593239
+ return null;
593240
+ const statPath = this._absoluteToolPath(path12);
593241
+ if (!this._evidenceLedger.validateFreshness(key, statPath))
593242
+ return null;
593243
+ const fresh = this._evidenceLedger.get(key);
593244
+ if (!fresh || fresh.stale || !fresh.contentHash)
593245
+ return null;
593246
+ return {
593247
+ key,
593248
+ hash: fresh.contentHash,
593249
+ lastReadTurn: fresh.lastReadTurn
593250
+ };
593251
+ }
593252
+ _attachFreshExpectedHashesToEditArgs(toolName, args) {
593253
+ if (process.env["OMNIUS_ALLOW_UNVERIFIED_OLD_STRING_EDIT"] === "1") {
593254
+ return { args, patchedCount: 0 };
593255
+ }
593256
+ if (toolName === "file_edit") {
593257
+ if (this._normalizeExpectedHashValue(this._rawExpectedHashValue(args))) {
593258
+ return { args, patchedCount: 0 };
593259
+ }
593260
+ const path12 = this.extractPrimaryToolPath(args);
593261
+ const fresh = path12 ? this._freshReadHashForEditPath(path12) : null;
593262
+ if (!fresh)
593263
+ return { args, patchedCount: 0 };
593264
+ return { args: { ...args, expected_hash: fresh.hash }, patchedCount: 1 };
593265
+ }
593266
+ if (toolName !== "batch_edit" || !Array.isArray(args["edits"])) {
593267
+ return { args, patchedCount: 0 };
593268
+ }
593269
+ let patchedCount = 0;
593270
+ const edits = args["edits"].map((edit) => {
593271
+ if (!edit || typeof edit !== "object" || Array.isArray(edit))
593272
+ return edit;
593273
+ const rec = edit;
593274
+ if (this._normalizeExpectedHashValue(this._rawExpectedHashValue(rec))) {
593275
+ return rec;
593276
+ }
593277
+ const path12 = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : "";
593278
+ const fresh = path12 ? this._freshReadHashForEditPath(path12) : null;
593279
+ if (!fresh)
593280
+ return rec;
593281
+ patchedCount++;
593282
+ return { ...rec, expected_hash: fresh.hash };
593283
+ });
593284
+ return patchedCount > 0 ? { args: { ...args, edits }, patchedCount } : { args, patchedCount: 0 };
593285
+ }
593286
+ _validateFreshExpectedHashesForEdit(toolName, args) {
593287
+ if (process.env["OMNIUS_ALLOW_UNVERIFIED_OLD_STRING_EDIT"] === "1") {
593288
+ return null;
593289
+ }
593290
+ const targets = [];
593291
+ if (toolName === "file_edit") {
593292
+ const path12 = this.extractPrimaryToolPath(args);
593293
+ if (path12) {
593294
+ targets.push({
593295
+ label: path12,
593296
+ path: path12,
593297
+ expectedHash: this._normalizeExpectedHashValue(this._rawExpectedHashValue(args)),
593298
+ rawExpectedHash: this._rawExpectedHashValue(args)
593299
+ });
593300
+ }
593301
+ } else if (toolName === "batch_edit" && Array.isArray(args["edits"])) {
593302
+ args["edits"].forEach((edit, index) => {
593303
+ if (!edit || typeof edit !== "object" || Array.isArray(edit))
593304
+ return;
593305
+ const rec = edit;
593306
+ const path12 = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : "";
593307
+ if (!path12)
593308
+ return;
593309
+ targets.push({
593310
+ label: `edit #${index + 1} ${path12}`,
593311
+ path: path12,
593312
+ expectedHash: this._normalizeExpectedHashValue(this._rawExpectedHashValue(rec)),
593313
+ rawExpectedHash: this._rawExpectedHashValue(rec)
593314
+ });
593315
+ });
593316
+ } else {
593317
+ return null;
593318
+ }
593319
+ if (targets.length === 0)
593320
+ return null;
593321
+ const problems = [];
593322
+ for (const target of targets) {
593323
+ const fresh = this._freshReadHashForEditPath(target.path);
593324
+ if (target.rawExpectedHash !== void 0 && !target.expectedHash) {
593325
+ problems.push(`${target.label}: expected_hash is present but is not a valid 64-character SHA-256`);
593326
+ continue;
593327
+ }
593328
+ if (!fresh && target.expectedHash) {
593329
+ continue;
593330
+ }
593331
+ if (!fresh) {
593332
+ problems.push(`${target.label}: missing expected_hash and no fresh file_read hash is available for this file in the current run`);
593333
+ continue;
593334
+ }
593335
+ if (!target.expectedHash) {
593336
+ problems.push(`${target.label}: missing expected_hash; use sha256=${fresh.hash} from the fresh file_read evidence`);
593337
+ continue;
593338
+ }
593339
+ if (target.expectedHash !== fresh.hash) {
593340
+ problems.push(`${target.label}: expected_hash=${target.expectedHash} does not match fresh file_read sha256=${fresh.hash}`);
593341
+ }
593342
+ }
593343
+ if (problems.length === 0)
593344
+ return null;
593345
+ return [
593346
+ "[EDIT FRESHNESS CONTRACT]",
593347
+ "Exact replacement edits must be built from verified current file content, not remembered text.",
593348
+ "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.",
593349
+ "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.",
593350
+ "",
593351
+ ...problems.map((problem) => `- ${problem}`),
593352
+ "",
593353
+ "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."
593354
+ ].join("\n");
593355
+ }
592919
593356
  staleEditFamilyKey(toolName, pathKey, errorKind, targetHash, latestFileHash) {
592920
593357
  return [
592921
593358
  toolName,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.457",
3
+ "version": "1.0.458",
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.458",
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.458",
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",