omnius 1.0.534 → 1.0.535

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
@@ -6981,14 +6981,14 @@ var init_file_write = __esm({
6981
6981
  init_text_encoding();
6982
6982
  FileWriteTool = class {
6983
6983
  name = "file_write";
6984
- description = "Write UTF-8 text to a file, creating directories as needed. For content with heavy quoting, raw JSON, control characters, or large full-file payloads, pass content_base64 instead of shell heredocs/cat/tee.";
6984
+ description = "Create new files or deliberately replace small, empty, or placeholder files; use file_edit/file_patch for local changes. For content with heavy quoting, raw JSON, control characters, or large full-file payloads, pass content_base64 instead of shell heredocs/cat/tee.";
6985
6985
  parameters = {
6986
6986
  type: "object",
6987
6987
  properties: {
6988
6988
  path: { type: "string", description: "Absolute or relative file path" },
6989
6989
  content: {
6990
6990
  type: "string",
6991
- description: "UTF-8 text content to write. Use content_base64 when JSON escaping is fragile."
6991
+ description: "Complete UTF-8 file content. Do not send an existing large file merely to change one region; use file_edit/file_patch."
6992
6992
  },
6993
6993
  content_base64: {
6994
6994
  type: "string",
@@ -569322,11 +569322,6 @@ var init_personality = __esm({
569322
569322
  // packages/orchestrator/dist/critic.js
569323
569323
  function buildCriticGuidanceMessage(call, hits, opts = {}) {
569324
569324
  const argPreview = JSON.stringify(call.args ?? {}).slice(0, 200);
569325
- const actionReason = call.actionReason ? [
569326
- call.actionReason.scope ? `scope=${call.actionReason.scope}` : "",
569327
- call.actionReason.intent ? `intent=${call.actionReason.intent}` : "",
569328
- call.actionReason.evidence ? `evidence=${call.actionReason.evidence}` : ""
569329
- ].filter(Boolean).join("; ").slice(0, 500) : "";
569330
569325
  const cached = opts.cachedResult ? `
569331
569326
  Prior evidence preview:
569332
569327
  ${opts.cachedResult.slice(0, 700)}` : "";
@@ -569334,8 +569329,7 @@ ${opts.cachedResult.slice(0, 700)}` : "";
569334
569329
  return `[RUNTIME EVIDENCE CACHE — non-blocking]
569335
569330
  Observation: ${source}
569336
569331
  Call: ${call.tool}(${argPreview})
569337
- ` + (actionReason ? `Action reason: ${actionReason}
569338
- ` : "") + `State: exact prior evidence exists for these arguments in the current state version.
569332
+ State: exact prior evidence exists for these arguments in the current state version.
569339
569333
  Next action contract: let this result inform the next step once, then pivot to a concrete action.
569340
569334
  Suggested next actions: edit/write the implicated file, run verification, read a different specific file, or complete with evidence. Prefer not to repeat this exact call again unless the filesystem, browser, or page state changed.${cached}`;
569341
569335
  }
@@ -569344,10 +569338,13 @@ function buildCachedResultEnvelope(result) {
569344
569338
  ${result}`;
569345
569339
  }
569346
569340
  function evaluate2(inputs) {
569347
- const { proposedCall, fingerprint, isReadLike, recentToolResults, dedupHitCount, adversaryRedundantSignal } = inputs;
569348
- if (adversaryRedundantSignal) {
569341
+ const { proposedCall, fingerprint, recentToolResults, dedupHitCount, adversaryRedundantSignal } = inputs;
569342
+ const cacheEligible = proposedCall.tool === "memory_write" || NOOP_WRITE_CACHE_TOOLS.has(proposedCall.tool);
569343
+ if (adversaryRedundantSignal && cacheEligible) {
569349
569344
  const cached = recentToolResults.get(fingerprint);
569350
- const cachedResult = cached ? buildCachedResultEnvelope(cached.result) : void 0;
569345
+ if (!cached)
569346
+ return { decision: "pass" };
569347
+ const cachedResult = buildCachedResultEnvelope(cached.result);
569351
569348
  return {
569352
569349
  decision: "guidance",
569353
569350
  reason: "Adversary flagged this fingerprint as redundant",
@@ -569360,7 +569357,6 @@ function evaluate2(inputs) {
569360
569357
  compacted: cached?.compacted
569361
569358
  };
569362
569359
  }
569363
- const cacheEligible = isReadLike || proposedCall.tool === "shell" || proposedCall.tool === "memory_write" || NOOP_WRITE_CACHE_TOOLS.has(proposedCall.tool);
569364
569360
  if (cacheEligible) {
569365
569361
  const cached = recentToolResults.get(fingerprint);
569366
569362
  if (cached !== void 0) {
@@ -573004,13 +573000,13 @@ var init_reflectionBuffer = __esm({
573004
573000
  case "tool_misuse":
573005
573001
  return {
573006
573002
  whatFailed: `Wrong tool or arguments for the task. Tool ${lastFailedTool?.tool ?? "unknown"} failed: ${lastFailedTool?.error?.slice(0, 60) ?? lastError.slice(0, 60)}`,
573007
- whatToDoDifferently: `Try a different tool. If file_edit failed, try file_write. If shell failed with a complex command, break it into simpler steps. Read the file first before editing.`,
573003
+ whatToDoDifferently: `Correct the tool contract without broadening mutation scope. If file_edit failed, read the current target and use exact text or a line-range file_patch; do not fall back to file_write. If shell failed with a complex command, break it into simpler diagnostic steps.`,
573008
573004
  confidence: 0.8
573009
573005
  };
573010
573006
  case "repetition":
573011
573007
  return {
573012
573008
  whatFailed: `Got stuck in a loop after ${turnsSpent} turns trying ${failedApproaches.length} approaches. The same tools kept failing with similar errors.`,
573013
- whatToDoDifferently: "Stop and try a completely different strategy. If you were editing, try rewriting from scratch. If searching failed, try a broader or narrower query. Ask yourself: what assumption am I making that might be wrong?",
573009
+ whatToDoDifferently: "Stop and change the diagnosis, not the mutation radius. If you were editing, refresh the implicated range and patch only the verified fault; never rewrite an existing file from scratch as loop recovery. If searching failed, try a broader or narrower query. Ask which assumption is wrong.",
573014
573010
  confidence: 0.9
573015
573011
  };
573016
573012
  case "timeout":
@@ -576293,9 +576289,6 @@ function classifyFailureKind(text2, args) {
576293
576289
  }
576294
576290
  if (/Unknown tool\b|Unknown tool ['"`]/i.test(text2))
576295
576291
  return "missing_tool";
576296
- if (/\[TOOL ACTION REASON CONTRACT\]|must include action_reason|action_reason must/i.test(text2)) {
576297
- return "action_reason_missing";
576298
- }
576299
576292
  if (/\bexpected_hash\b|hash mismatch|fresh file_read|stale edit|stale target|FULL FILE REWRITE CONTRACT|old_string (?:not found|does not match|must be unique|appears \d+)/i.test(text2)) {
576300
576293
  return "stale_hash";
576301
576294
  }
@@ -576536,16 +576529,6 @@ var init_failure_taxonomy = __esm({
576536
576529
  ],
576537
576530
  createRecoveryCard: true
576538
576531
  },
576539
- action_reason_missing: {
576540
- severity: "warning",
576541
- retryable: true,
576542
- guidance: "A side-effectful tool call lacked the required public action_reason envelope. Retry only with compact task-state metadata, or use a read-only/control-plane tool if no mutation is intended.",
576543
- workboardEvidenceRequirements: [
576544
- "corrected action_reason envelope",
576545
- "same intended operation through the right tool"
576546
- ],
576547
- createRecoveryCard: false
576548
- },
576549
576532
  workboard_noop: {
576550
576533
  severity: "warning",
576551
576534
  retryable: true,
@@ -577277,7 +577260,7 @@ var init_context_fabric = __esm({
577277
577260
  const header = [
577278
577261
  "[ACTIVE CONTEXT FRAME]",
577279
577262
  options2.turn !== void 0 ? `turn: ${options2.turn}` : null,
577280
- "Scope: runtime state for the next action. Treat this as the single merged context intake; do not re-read or re-emit it unless state changes."
577263
+ "Scope: runtime state for the next action. Do not quote or re-emit this frame. Prior evidence is orientation; execute a narrow file_read or the declared verifier whenever current text/hash or post-mutation proof is required."
577281
577264
  ].filter(Boolean);
577282
577265
  let content = [...header, "", ...sectionLines].join("\n").trim();
577283
577266
  const controlChars = included.filter(isControlSignal).reduce((sum2, signal) => sum2 + signal.content.length, 0);
@@ -577471,7 +577454,7 @@ function retireDuplicateToolFailures(messages2) {
577471
577454
  }
577472
577455
  out[index] = {
577473
577456
  ...message2,
577474
- content: "[retired_duplicate_tool_failure] newer matching result appears later; do not repeat the same call."
577457
+ content: "[retired_duplicate_tool_failure] a newer matching failure appears later. Use that latest diagnostic; retry only after refreshing prerequisite evidence or correcting the arguments."
577475
577458
  };
577476
577459
  }
577477
577460
  return out;
@@ -577777,8 +577760,8 @@ var init_evidenceLedger = __esm({
577777
577760
  return null;
577778
577761
  const sorted = [...this.entries.values()].sort((a2, b) => b.lastReadTurn - a2.lastReadTurn);
577779
577762
  const lines = [
577780
- "Evidence already gathered this run you ALREADY have the content below.",
577781
- "Do NOT re-read a FRESH file; use this. Re-read ONLY entries marked STALE.",
577763
+ "Earlier file evidence from this run is shown below for orientation.",
577764
+ "Use a displayed full-fidelity snapshot when it is sufficient, but file_read is allowed and required when an edit/hash guard asks for current text, a displayed body is elided, or external state may have changed.",
577782
577765
  ""
577783
577766
  ];
577784
577767
  let used = lines.join("\n").length;
@@ -578253,8 +578236,6 @@ ${obs.stateDigest.slice(0, 1800)}
578253
578236
  `This is a loop. Reason about WHY — for THIS specific call, not generically.`,
578254
578237
  ls2.alreadyHave ? `Evidence the agent ALREADY obtained from a prior identical call:
578255
578238
  ${ls2.alreadyHave.slice(0, 900)}` : `(No cached result available for the repeated call.)`,
578256
- ls2.actionReason ? `Agent's stated action reason:
578257
- scope=${ls2.actionReason.scope ?? ""}; intent=${ls2.actionReason.intent ?? ""}; evidence=${ls2.actionReason.evidence ?? ""}; expected=${ls2.actionReason.expected_result ?? ""}`.slice(0, 700) : "",
578258
578239
  "",
578259
578240
  stateDigest.trim(),
578260
578241
  "",
@@ -578658,7 +578639,6 @@ function recordDebugToolEvent(input) {
578658
578639
  },
578659
578640
  ...input.focusSupervisor ? { focusSupervisor: input.focusSupervisor } : {},
578660
578641
  ...input.focusDecision ? { focusDecision: input.focusDecision } : {},
578661
- ...input.actionReason ? { actionReason: compactDebugValue(input.actionReason) } : {},
578662
578642
  ...input.repeatShortCircuit !== void 0 ? { repeatShortCircuit: input.repeatShortCircuit } : {},
578663
578643
  ...input.runtimeAuthored !== void 0 ? { runtimeAuthored: input.runtimeAuthored } : {},
578664
578644
  ...input.runtimeGuidance ? { runtimeGuidancePreview: trim(input.runtimeGuidance, 1200) } : {},
@@ -579347,30 +579327,6 @@ function trim(value2, max) {
579347
579327
  return `${value2.slice(0, max)}
579348
579328
  [truncated ${value2.length - max} chars]`;
579349
579329
  }
579350
- function compactDebugValue(value2, depth = 0) {
579351
- if (value2 == null)
579352
- return value2;
579353
- if (typeof value2 === "string") {
579354
- return value2.length > 500 ? `${value2.slice(0, 497)}...` : value2;
579355
- }
579356
- if (typeof value2 === "number" || typeof value2 === "boolean")
579357
- return value2;
579358
- if (Array.isArray(value2)) {
579359
- if (depth >= 2)
579360
- return `[array:${value2.length}]`;
579361
- return value2.slice(0, 8).map((entry) => compactDebugValue(entry, depth + 1));
579362
- }
579363
- if (typeof value2 === "object") {
579364
- if (depth >= 2)
579365
- return "[object]";
579366
- const out = {};
579367
- for (const [key, entry] of Object.entries(value2).slice(0, 12)) {
579368
- out[key] = compactDebugValue(entry, depth + 1);
579369
- }
579370
- return out;
579371
- }
579372
- return String(value2);
579373
- }
579374
579330
  function safeId2(value2) {
579375
579331
  return String(value2 || "unknown").replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 160);
579376
579332
  }
@@ -579554,17 +579510,6 @@ function meaningfulCachedEvidencePreview(text2) {
579554
579510
  }
579555
579511
  return preview.join(" | ").slice(0, 700);
579556
579512
  }
579557
- function actionReasonSummary(actionReason) {
579558
- if (!actionReason)
579559
- return "";
579560
- const parts = [
579561
- actionReason.scope ? `scope=${actionReason.scope}` : "",
579562
- actionReason.intent ? `intent=${actionReason.intent}` : "",
579563
- actionReason.evidence ? `evidence=${actionReason.evidence}` : "",
579564
- actionReason.expected_result ? `expected=${actionReason.expected_result}` : ""
579565
- ].filter(Boolean);
579566
- return parts.length > 0 ? `Action reason observed: ${parts.join("; ").slice(0, 700)}` : "";
579567
- }
579568
579513
  function normalizeShellForFamily(command, cwd4) {
579569
579514
  let s2 = String(command || "").replace(/\\\r?\n/g, " ").replace(/\s+/g, " ").trim();
579570
579515
  if (cwd4 && cwd4.length > 1) {
@@ -579875,7 +579820,6 @@ var init_focusSupervisor = __esm({
579875
579820
  `${reason}.`,
579876
579821
  `Required next action: ${prior.requiredNextAction}.`,
579877
579822
  `Blocked action family: ${family}.`,
579878
- actionReasonSummary(input.actionReason),
579879
579823
  "This block is not counted as a new ignored directive."
579880
579824
  ].filter(Boolean).join("\n"), false);
579881
579825
  }
@@ -579902,7 +579846,6 @@ var init_focusSupervisor = __esm({
579902
579846
  "[FOCUS SUPERVISOR BLOCK: LOOP CONTROL]",
579903
579847
  `Directive ${prior.id} was repeatedly ignored with ${family}.`,
579904
579848
  `Required next action: ${directive.requiredNextAction}.`,
579905
- actionReasonSummary(input.actionReason),
579906
579849
  "Stop trying this action-family repeatedly. Pivot to a materially different approach or web_search the blocker, then continue."
579907
579850
  ].filter(Boolean).join("\n"), false);
579908
579851
  }
@@ -579942,7 +579885,6 @@ var init_focusSupervisor = __esm({
579942
579885
  `[FOCUS SUPERVISOR BLOCK] The previous directive was ignored: ${prior.reason}`,
579943
579886
  `Required next action: ${directive.requiredNextAction}.`,
579944
579887
  `Blocked action family: ${family}.`,
579945
- actionReasonSummary(input.actionReason),
579946
579888
  directive.requiredNextAction === "creative_pivot_or_research" ? "Stop trying variants of what failed. The task is NOT over — pivot to a fundamentally different approach or web_search the exact blocker, then act." : "Take the required next action before trying another variant."
579947
579889
  ].filter(Boolean).join("\n"), false);
579948
579890
  }
@@ -579958,7 +579900,6 @@ var init_focusSupervisor = __esm({
579958
579900
  return this.block(directive, [
579959
579901
  input.stalePreflightMessage,
579960
579902
  "",
579961
- actionReasonSummary(input.actionReason),
579962
579903
  "[FOCUS SUPERVISOR] Required next action: file_read the authoritative current target once, then build a new edit from current evidence or report incomplete."
579963
579904
  ].filter(Boolean).join("\n"), false);
579964
579905
  }
@@ -579980,7 +579921,6 @@ var init_focusSupervisor = __esm({
579980
579921
  input.cachedResultFailed ? "[FOCUS SUPERVISOR: CACHED FAILURE]" : "[FOCUS SUPERVISOR: CACHED EVIDENCE]",
579981
579922
  `This ${input.toolName} action family has already produced evidence and should not be re-executed unchanged.`,
579982
579923
  `Required next action: ${directive.requiredNextAction}.`,
579983
- actionReasonSummary(input.actionReason),
579984
579924
  "",
579985
579925
  compactCachedEvidence(input.cachedResult)
579986
579926
  ].filter(Boolean).join("\n"), !input.cachedResultFailed);
@@ -585241,9 +585181,6 @@ import { join as _pathJoin, relative as _pathRelative, resolve as _pathResolve }
585241
585181
  import { tmpdir as _osTmpdir } from "node:os";
585242
585182
  import { homedir as _osHomedir } from "node:os";
585243
585183
  import { z as z17 } from "zod";
585244
- function cleanToolActionReasonField(value2) {
585245
- return String(value2 ?? "").replace(/\s+/g, " ").trim();
585246
- }
585247
585184
  function stripShellQuotedSegments(command) {
585248
585185
  let out = "";
585249
585186
  let quote2 = null;
@@ -585982,7 +585919,7 @@ function classifyThinkOutcome(raw) {
585982
585919
  }
585983
585920
  return null;
585984
585921
  }
585985
- var TOOL_SUBSETS, TOOL_AUTO_DEMOTE_TURNS, TOOL_ACTION_REASON_KEYS, TOOL_ACTION_REASON_SCOPES, READ_ONLY_ACTION_REASON_OPTIONAL_TOOLS, STATE_UPDATE_ACTION_REASON_SYNTHESIS_TOOLS, TOOL_ACTION_REASON_SCHEMA, SYSTEM_PROMPT, SYSTEM_PROMPT_MEDIUM, SYSTEM_PROMPT_SMALL, VISUAL_TOOLS, AUDIO_TOOLS, SOCIAL_TOOLS, SPATIAL_TOOLS, CODE_TOOLS, AgenticRunner, OllamaAgenticBackend;
585922
+ var TOOL_SUBSETS, TOOL_AUTO_DEMOTE_TURNS, LEGACY_ACTION_REASON_KEYS, SYSTEM_PROMPT, SYSTEM_PROMPT_MEDIUM, SYSTEM_PROMPT_SMALL, VISUAL_TOOLS, AUDIO_TOOLS, SOCIAL_TOOLS, SPATIAL_TOOLS, CODE_TOOLS, AgenticRunner, OllamaAgenticBackend;
585986
585923
  var init_agenticRunner = __esm({
585987
585924
  "packages/orchestrator/dist/agenticRunner.js"() {
585988
585925
  "use strict";
@@ -586080,75 +586017,7 @@ var init_agenticRunner = __esm({
586080
586017
  skill: ["skill_list", "skill_extract", "skill_execute", "skill_search"]
586081
586018
  };
586082
586019
  TOOL_AUTO_DEMOTE_TURNS = 10;
586083
- TOOL_ACTION_REASON_KEYS = /* @__PURE__ */ new Set([
586084
- "action_reason",
586085
- "actionReason",
586086
- "justification"
586087
- ]);
586088
- TOOL_ACTION_REASON_SCOPES = /* @__PURE__ */ new Set([
586089
- "discovery",
586090
- "targeted_patch",
586091
- "new_file",
586092
- "full_file_rewrite",
586093
- "verification",
586094
- "state_update",
586095
- "delegation",
586096
- "completion",
586097
- "other"
586098
- ]);
586099
- READ_ONLY_ACTION_REASON_OPTIONAL_TOOLS = /* @__PURE__ */ new Set([
586100
- "file_read",
586101
- "file_explore",
586102
- "list_directory",
586103
- "grep_search",
586104
- "grep",
586105
- "glob_find",
586106
- "find_files",
586107
- "todo_read",
586108
- "memory_read",
586109
- "memory_search",
586110
- "semantic_map",
586111
- "code_graph",
586112
- "graph_query",
586113
- "graph_traverse",
586114
- "web_search",
586115
- "web_fetch",
586116
- "tool_search",
586117
- "task_status",
586118
- "task_output"
586119
- ]);
586120
- STATE_UPDATE_ACTION_REASON_SYNTHESIS_TOOLS = /* @__PURE__ */ new Set([
586121
- "todo_write",
586122
- "working_notes"
586123
- ]);
586124
- TOOL_ACTION_REASON_SCHEMA = {
586125
- type: "object",
586126
- description: "Compact public action contract for this exact tool call. Do not include hidden chain-of-thought; use concise task-state facts.",
586127
- properties: {
586128
- task_anchor: {
586129
- type: "string",
586130
- description: "One compact phrase tying this call to the active user goal/current focus."
586131
- },
586132
- intent: {
586133
- type: "string",
586134
- description: "One sentence explaining why this tool is the next action."
586135
- },
586136
- evidence: {
586137
- type: "string",
586138
- description: "Fresh evidence or contract authorizing the action: file hash, focus directive, cached evidence, user request, or explicit absence."
586139
- },
586140
- expected_result: {
586141
- type: "string",
586142
- description: "The concrete new state or information expected from this call."
586143
- },
586144
- scope: {
586145
- type: "string",
586146
- enum: Array.from(TOOL_ACTION_REASON_SCOPES),
586147
- description: "Classify the action scope. Use targeted_patch for constrained edits; full_file_rewrite only for deliberate whole-file replacement."
586148
- }
586149
- },
586150
- required: ["task_anchor", "intent", "evidence", "expected_result", "scope"]
586151
- };
586020
+ LEGACY_ACTION_REASON_KEYS = /* @__PURE__ */ new Set(["action_reason", "actionReason"]);
586152
586021
  SYSTEM_PROMPT = loadPrompt("agentic/system-large.md");
586153
586022
  SYSTEM_PROMPT_MEDIUM = loadPrompt("agentic/system-medium.md");
586154
586023
  SYSTEM_PROMPT_SMALL = loadPrompt("agentic/system-small.md");
@@ -587365,10 +587234,8 @@ ${parts.join("\n")}
587365
587234
  if (ts.failedApproaches.length > 0) {
587366
587235
  lines.push(`recent_failed_approaches=${ts.failedApproaches.slice(-3).join(" | ").replace(/\s+/g, " ").slice(0, 420)}`);
587367
587236
  }
587368
- lines.push("tool_call_contract=Mutating/side-effectful tool calls require action_reason {task_anchor,intent,evidence,expected_result,scope}; read-only evidence tools may include it but must never be blocked only for missing metadata. Compact public facts only, no hidden chain-of-thought.");
587369
587237
  lines.push("edit_transport_contract=file_edit is for exact unique current text copied from file_read; file_patch is for line-range delete/replace/insert using file_read line numbers and expected_hash/current target evidence. A failed file_edit invalidates the edit premise; rebuild from current evidence instead of broadening into file_write unless the task is truly whole-file replacement.");
587370
587238
  lines.push("range_patch_contract=If patch_ready=true or current path+start_line+end_line+expected_hash are known, call file_patch. If read_required=true or a stale exact edit lacks current range/hash, call file_read once. Do not call file_read when patch_ready=true.");
587371
- lines.push(`scope_contract=${Array.from(TOOL_ACTION_REASON_SCOPES).join("|")}; use targeted_patch for constrained code edits and full_file_rewrite only for deliberate whole-file replacement.`);
587372
587239
  const gitContract = renderGitProgressActionContract(this._gitProgress);
587373
587240
  if (gitContract)
587374
587241
  lines.push(gitContract);
@@ -588238,7 +588105,8 @@ Emit AT MOST 6 tool calls per response. Smaller batches receive better feedback
588238
588105
  };
588239
588106
  const batchGuidance = _BATCH_GUIDANCE[this.options.modelTier ?? "large"] ?? _BATCH_GUIDANCE.large;
588240
588107
  const shellGuidance = "\n\n## Shell command patterns\n\nWhen investigating a build/test/install failure, RUN THE COMMAND WITHOUT TRUNCATION FIRST to see the full error.\nAvoid `| tail -N` / `| head -N` / `| sed -n '1,Np'` on commands you don't yet trust — they drop the part of the output where errors usually appear.\nPipefail is on by default in this orchestrator: a pipeline's exit code reflects the FIRST failing stage, not just the last. So `<cmd> | tail -80` returns non-zero if `<cmd>` failed.\nWhen you DO want to limit context (after diagnosis), prefer `<cmd> 2>&1 | tail -300` (or larger) so you keep enough output to see the actual error block.\nIf your command exited 0 but produced suspiciously short output (~< 800 chars), the orchestrator will inject a [HINT — pipe-to-truncator detected] block telling you to re-run without truncation.";
588241
- const basePromptWithBatching = basePrompt + batchGuidance + shellGuidance;
588108
+ const editTransportGuidance = "\n\n## Existing-file edit loop\n\nFor an existing non-trivial file, use this loop: (1) file_read the implicated range or whole small file, (2) file_patch a contiguous line range or file_edit exact current text, (3) inspect the resulting diff/changed range, and (4) run the declared verifier live. Use batch_edit only for multiple exact replacements built from one fresh read.\nfile_write is for new files, empty/placeholder files, and deliberate replacement of a genuinely small file. Never use file_write as a fallback for stale hashes, old_string mismatches, malformed patch arguments, or repeated edit failures. A 'different approach' means refresh evidence or change the diagnostic/patch shape; it does not mean broaden a local repair into a whole-file rewrite.\nPreviously observed or compacted results are orientation only. Re-run file_read whenever a freshness/hash guard asks for current text, and re-run the exact verifier after every relevant mutation; cached output never proves current file state or completion.";
588109
+ const basePromptWithBatching = basePrompt + batchGuidance + shellGuidance + editTransportGuidance;
588242
588110
  sections.push({
588243
588111
  label: "c_instr",
588244
588112
  content: basePromptWithBatching,
@@ -590072,11 +589940,8 @@ ${modelVisible}` : modelVisible || result.error || displayOutput || "";
590072
589940
  const realFileMutation = input.realFileMutation ?? this._isRealProjectMutation(input.toolName, input.result);
590073
589941
  const attemptedTargetPaths = this._extractToolTargetPaths(input.toolName, input.args, input.result);
590074
589942
  const realMutationPaths = input.realMutationPaths ?? (realFileMutation ? attemptedTargetPaths : []);
590075
- const shellActionReasonScope = input.toolName === "shell" ? input.actionReason?.scope : void 0;
590076
- const shellActionReasonDeclaresVerification = shellActionReasonScope === "verification";
590077
- const shellActionReasonDeclaresNonVerification = typeof shellActionReasonScope === "string" && shellActionReasonScope.length > 0 && shellActionReasonScope !== "verification";
590078
- const shellVerification = input.toolName === "shell" && (shellActionReasonDeclaresVerification || !shellActionReasonDeclaresNonVerification && input.result.runtimeAuthored !== true && this._shellResultLooksLikeVerification(input.args, input.result));
590079
- const shellVerificationFamily = shellVerification ? this._shellVerificationFamily(input.args, input.actionReason) : "";
589943
+ const shellVerification = input.toolName === "shell" && input.result.runtimeAuthored !== true && this._shellResultLooksLikeVerification(input.args, input.result);
589944
+ const shellVerificationFamily = shellVerification ? this._shellVerificationFamily(input.args) : "";
590080
589945
  if (input.result.success === true && this._isProjectEditTool(input.toolName) && !realFileMutation && input.result.alreadyApplied !== true) {
590081
589946
  return;
590082
589947
  }
@@ -590110,17 +589975,7 @@ ${modelVisible}` : modelVisible || result.error || displayOutput || "";
590110
589975
  }
590111
589976
  this._saveCompletionLedgerSafe();
590112
589977
  }
590113
- _shellVerificationFamily(args, actionReason) {
590114
- if (actionReason?.scope === "verification") {
590115
- const stableParts = [
590116
- actionReason.task_anchor,
590117
- actionReason.expected_result
590118
- ].map((part) => String(part ?? "").trim()).filter(Boolean);
590119
- const semanticKey = (stableParts.length > 0 ? stableParts : [String(actionReason.intent ?? "").trim()].filter(Boolean)).join(" | ");
590120
- const normalized2 = normalizeCompletionKey(semanticKey || "shell verification", "shell_verification").slice(0, 140);
590121
- if (normalized2)
590122
- return `shell-action:${normalized2}`;
590123
- }
589978
+ _shellVerificationFamily(args) {
590124
589979
  const command = String(args?.["command"] ?? args?.["cmd"] ?? "").trim();
590125
589980
  const normalized = normalizeShellCommand(command);
590126
589981
  return `shell:${(normalized || command || "verification").slice(0, 160)}`;
@@ -592443,13 +592298,11 @@ ${latest.output || ""}`.trim();
592443
592298
  success: item.succeeded,
592444
592299
  path: item.path,
592445
592300
  stateVersion: item.stateVersion,
592446
- preview: item.preview.slice(0, 240),
592447
- actionReason: item.actionReason ?? void 0
592301
+ preview: item.preview.slice(0, 240)
592448
592302
  }));
592449
592303
  const recentActionLines = recentActions.slice(-6).map((item) => {
592450
592304
  const path16 = item.path ? ` path=${item.path}` : "";
592451
- const reason = item.actionReason ? ` reason=${[item.actionReason.scope, item.actionReason.intent].filter(Boolean).join(":").replace(/\s+/g, " ").slice(0, 120)}` : "";
592452
- return `- turn=${item.turn} ${item.tool} ${item.success ? "ok" : "err"}${path16}${reason}: ${item.preview.replace(/\s+/g, " ").slice(0, 160)}`;
592305
+ return `- turn=${item.turn} ${item.tool} ${item.success ? "ok" : "err"}${path16}: ${item.preview.replace(/\s+/g, " ").slice(0, 160)}`;
592453
592306
  });
592454
592307
  const treeDir = _pathJoin(this.omniusStateDir(), "world-state", "tree");
592455
592308
  const turnName = `turn-${String(turn).padStart(4, "0")}`;
@@ -592751,10 +592604,10 @@ ${sections.join("\n")}` : sections.join("\n");
592751
592604
  }
592752
592605
  }
592753
592606
  const sections = [
592754
- "[KNOWLEDGE — recent tool results for reference. Re-reading a file after an edit is encouraged to capture the latest content; exact re-reads are NOT blocked and always return fresh, up-to-date data.]"
592607
+ "[KNOWLEDGE — prior observations for orientation only. A file_read requested for current text/hash and a verifier requested after mutation execute live; this history is never substituted for fresh evidence.]"
592755
592608
  ];
592756
592609
  if (compactedCount > 0) {
592757
- sections.push(`Compacted cached entries still count as already-known results (${compactedCount}); an exact repeat will be served from cache or skipped, not produce new information.`);
592610
+ sections.push(`Compacted historical entries: ${compactedCount}. They show what was observed earlier, not what is currently on disk. Re-run the narrow read or verifier when freshness matters.`);
592758
592611
  }
592759
592612
  if (filesRead.length > 0) {
592760
592613
  const unique2 = [...new Set(filesRead)].slice(0, 30);
@@ -593597,11 +593450,11 @@ ${blob}
593597
593450
  const meta = metaForResult(idx);
593598
593451
  let stub;
593599
593452
  if (p2) {
593600
- stub = `[Earlier read of ${p2} cleared its content is preserved in your ACTIVE CONTEXT FRAME (Evidence already gathered). Do NOT re-read it; use the frame.]`;
593453
+ stub = `[Earlier read of ${p2} compacted. A snapshot may appear in the ACTIVE CONTEXT FRAME; file_read the narrow current range again whenever exact text/hash or unelided content is needed.]`;
593601
593454
  } else {
593602
593455
  const firstLine = content.split("\n").find((l2) => l2.trim())?.trim().slice(0, 140) ?? "";
593603
593456
  const failed = /\berror\b|\bfail|✗|exit code [1-9]|traceback/i.test(content);
593604
- stub = `[${meta?.name ?? "tool"}(${meta?.argsPreview ?? ""}) result compacted to save context — ${failed ? "FAILED" : "ok"}${firstLine ? `: ${firstLine}` : ""}. Re-run ONLY if the underlying state has since changed.]`;
593457
+ stub = `[${meta?.name ?? "tool"}(${meta?.argsPreview ?? ""}) result compacted to save context — ${failed ? "FAILED" : "ok"}${firstLine ? `: ${firstLine}` : ""}. This is historical orientation, not current-state proof; re-run a verifier or narrow state read when freshness matters.]`;
593605
593458
  }
593606
593459
  messages2[idx] = { ...msg, content: stub };
593607
593460
  cleared++;
@@ -593688,7 +593541,7 @@ ${blob}
593688
593541
  * size. The hash is computed over the full canonical value.
593689
593542
  */
593690
593543
  _buildExactArgsKey(args) {
593691
- return Object.entries(args ?? {}).filter(([key]) => !TOOL_ACTION_REASON_KEYS.has(key)).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${this._formatExactArgValue(v)}`).join(",");
593544
+ return Object.entries(args ?? {}).filter(([key]) => !LEGACY_ACTION_REASON_KEYS.has(key)).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${this._formatExactArgValue(v)}`).join(",");
593692
593545
  }
593693
593546
  _buildToolFingerprint(name10, args) {
593694
593547
  const canonical = this.lookupRegisteredTool(name10)?.name ?? name10;
@@ -593980,7 +593833,7 @@ ${blob}
593980
593833
  if (seen.has(value2))
593981
593834
  return "#cycle";
593982
593835
  seen.add(value2);
593983
- const entries = Object.entries(value2).filter(([key]) => !TOOL_ACTION_REASON_KEYS.has(key)).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${this._canonicalArgValue(v, seen)}`);
593836
+ const entries = Object.entries(value2).filter(([key]) => !LEGACY_ACTION_REASON_KEYS.has(key)).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${this._canonicalArgValue(v, seen)}`);
593984
593837
  return `#object:{${entries.join(",")}}`;
593985
593838
  }
593986
593839
  return String(value2);
@@ -594170,8 +594023,7 @@ Rewrite it now for ${ctx3.model}.`;
594170
594023
  const unknownKeys = providedKeys.filter((key) => !props.has(key));
594171
594024
  const aliases = this.suggestArgumentAliases(missingRequired, providedKeys, args, props);
594172
594025
  const corrected = this.buildCorrectedArgsPreview(args, props, aliases);
594173
- const actionReasonOnlyFailure = validationError.includes("[TOOL ACTION REASON CONTRACT]");
594174
- const siblingMatches = actionReasonOnlyFailure ? [] : this.findSiblingToolSchemaMatches(toolName, providedKeys);
594026
+ const siblingMatches = this.findSiblingToolSchemaMatches(toolName, providedKeys);
594175
594027
  const lines = [
594176
594028
  `[RUNTIME TOOL ARGUMENT REPAIR]`,
594177
594029
  `Tool call failed before execution: ${toolName}`,
@@ -596480,7 +596332,7 @@ ${_staleSamples.join("\n")}` : ``,
596480
596332
  ``,
596481
596333
  ` (a) RUN-AND-READ: Run the EXACT command that fails (test/typecheck/build) and READ THE FULL ERROR MESSAGE LITERALLY. Do not summarize it from memory; paste it back into context. The current write hasn't been validated against any error.`,
596482
596334
  ``,
596483
- ` (b) DELETE-AND-RESTART: If the file has been rewritten ${_wtWorstCount} times, the current approach is wrong. Either delete the file and try a fundamentally different design, OR revert to a known-good earlier version (use git, working_notes, or memory_search to find one).`,
596335
+ ` (b) DIFF-AND-RESET-THE-PREMISE: Inspect git diff for ${_wtWorstPath}, then file_read only the implicated current range. Keep correct changes, identify the first wrong hunk, and repair that hunk with file_patch/file_edit. Do not delete or regenerate the file.`,
596484
596336
  ``,
596485
596337
  ` (c) ERROR-INFORMED LOCAL TRIAGE: use the exact failing local command and error line to identify one implicated path/symbol/config key, then inspect or change only that target. Do not search online for a codebase-local failure.`,
596486
596338
  ``,
@@ -597113,6 +596965,8 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
597113
596965
  };
597114
596966
  const ceCompactOutput = await this._contextEngine.compact(ceCompactInput);
597115
596967
  if (ceCompactOutput.compacted && ceCompactOutput.compactedMessages.length > 0 && ceCompactOutput.compactedMessages.length < compacted.length) {
596968
+ const beforeCount = compacted.length;
596969
+ const afterCount = ceCompactOutput.compactedMessages.length;
597116
596970
  messages2.length = 0;
597117
596971
  messages2.push(...ceCompactOutput.compactedMessages.map((m2) => ({
597118
596972
  role: m2.role,
@@ -597120,6 +596974,11 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
597120
596974
  ...m2.name ? { name: m2.name } : {}
597121
596975
  })));
597122
596976
  compacted = messages2;
596977
+ this.emit({
596978
+ type: "compaction",
596979
+ content: `Compacted ${beforeCount - afterCount} messages (context engine) | ${beforeCount} -> ${afterCount} messages retained`,
596980
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
596981
+ });
597123
596982
  }
597124
596983
  } catch {
597125
596984
  }
@@ -597746,7 +597605,6 @@ ${memoryLines.join("\n")}`
597746
597605
  executeSingle = async (tc) => {
597747
597606
  if (this.aborted)
597748
597607
  return null;
597749
- const actionReasonForAdvisory = this._toolActionReasonForCall(tc.name, tc.arguments ?? {});
597750
597608
  const cohortKey = this.buildArgCohortKey(tc.name, tc.arguments);
597751
597609
  const cohort = this._argCohorts.get(cohortKey);
597752
597610
  if (cohort && cohort.failure >= 3 && cohort.success === 0) {
@@ -598107,7 +597965,6 @@ Read the current file if needed, make a different concrete edit, or move to veri
598107
597965
  turn,
598108
597966
  toolName: tc.name,
598109
597967
  args: tc.arguments ?? {},
598110
- actionReason: actionReasonForAdvisory,
598111
597968
  fingerprint: toolFingerprint,
598112
597969
  isReadLike: false,
598113
597970
  stalePreflightMessage: staleEditBlock,
@@ -598173,7 +598030,6 @@ Read the current file if needed, make a different concrete edit, or move to veri
598173
598030
  turn,
598174
598031
  toolName: tc.name,
598175
598032
  args: tc.arguments ?? {},
598176
- actionReason: actionReasonForAdvisory,
598177
598033
  fingerprint: toolFingerprint,
598178
598034
  isReadLike: false,
598179
598035
  stalePreflightMessage: staleRewriteBlock,
@@ -598239,7 +598095,6 @@ Read the current file if needed, make a different concrete edit, or move to veri
598239
598095
  turn,
598240
598096
  toolName: tc.name,
598241
598097
  args: tc.arguments ?? {},
598242
- actionReason: actionReasonForAdvisory,
598243
598098
  fingerprint: toolFingerprint,
598244
598099
  isReadLike: false,
598245
598100
  stalePreflightMessage: editReversalBlock,
@@ -598373,8 +598228,7 @@ Read the current file if needed, make a different concrete edit, or move to veri
598373
598228
  } : evaluate2({
598374
598229
  proposedCall: {
598375
598230
  tool: tc.name,
598376
- args: tc.arguments ?? {},
598377
- actionReason: actionReasonForAdvisory ?? void 0
598231
+ args: tc.arguments ?? {}
598378
598232
  },
598379
598233
  fingerprint: toolFingerprint,
598380
598234
  isReadLike,
@@ -598424,7 +598278,6 @@ Read the current file if needed, make a different concrete edit, or move to veri
598424
598278
  tool: tc.name,
598425
598279
  target: _target,
598426
598280
  count: criticDecision.hitNumber,
598427
- actionReason: actionReasonForAdvisory ?? void 0,
598428
598281
  alreadyHave: _existingFp?.result
598429
598282
  }
598430
598283
  });
@@ -598529,7 +598382,6 @@ ${cachedResult}`,
598529
598382
  turn,
598530
598383
  toolName: tc.name,
598531
598384
  args: tc.arguments ?? {},
598532
- actionReason: actionReasonForAdvisory,
598533
598385
  completionStatus: completionStatusFromTaskCompleteArgs(tc.arguments),
598534
598386
  fingerprint: toolFingerprint,
598535
598387
  isReadLike,
@@ -598607,7 +598459,6 @@ ${cachedResult}`,
598607
598459
  const tool = resolvedTool?.tool;
598608
598460
  let result;
598609
598461
  let runtimeSystemGuidance = null;
598610
- let toolActionReason = actionReasonForAdvisory;
598611
598462
  if (repeatShortCircuit) {
598612
598463
  result = repeatShortCircuit;
598613
598464
  } else if (tc.arguments && "_raw" in tc.arguments) {
@@ -598623,12 +598474,12 @@ ${cachedResult}`,
598623
598474
  error: this.unknownToolError(tc.name)
598624
598475
  };
598625
598476
  } else {
598626
- let validationError = this._validateToolActionReason(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
598477
+ let validationError = null;
598627
598478
  if (!validationError) {
598628
- tc.arguments = this._stripToolActionReasonArgs(tc.arguments ?? {});
598479
+ tc.arguments = this._stripLegacyActionReasonArgs(tc.arguments ?? {});
598629
598480
  tc.arguments = this._normalizeToolArgsForExecution(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
598630
598481
  tc.arguments = this._autoDeriveDelegationMutationScope(resolvedTool?.name ?? tc.name, tc.arguments ?? {}, turn);
598631
- validationError = this._validateFileWriteOverwriteContract(resolvedTool?.name ?? tc.name, tc.arguments ?? {}, toolActionReason);
598482
+ validationError = this._validateFileWriteOverwriteContract(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
598632
598483
  }
598633
598484
  if (!validationError) {
598634
598485
  validationError = this._validateFreshExpectedHashesForEdit(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
@@ -598881,8 +598732,7 @@ Respond with EXACTLY this structure before your next tool call:
598881
598732
  toolName: resolvedTool?.name ?? tc.name,
598882
598733
  toolCallId: tc.id,
598883
598734
  args: tc.arguments,
598884
- result,
598885
- actionReason: toolActionReason
598735
+ result
598886
598736
  });
598887
598737
  if (observedStateMutation) {
598888
598738
  this._markAdversaryObservedStateMutation();
@@ -599634,24 +599484,12 @@ Respond with EXACTLY this structure before your next tool call:
599634
599484
  }
599635
599485
  const cacheableMemoryNoop = tc.name === "memory_write" && result.success && result.noop;
599636
599486
  const cacheableProjectWriteNoop = this._isProjectEditTool(tc.name) && result.success && !this._isRealProjectMutation(tc.name, result) && (result.noop === true || result.mutated === false);
599637
- const cacheableShellResult = tc.name === "shell" && result.runtimeAuthored !== true && !shellLikelyMutatesFilesystem;
599638
- if (isReadLike && result.success && result.runtimeAuthored !== true || cacheableShellResult || cacheableMemoryNoop || cacheableProjectWriteNoop) {
599487
+ if (cacheableMemoryNoop || cacheableProjectWriteNoop) {
599639
599488
  recentToolResults.set(toolFingerprint, {
599640
- result: tc.name === "shell" ? this._buildShellCacheResult(tc.arguments, result) : (
599641
- // Read-like results are SERVED BACK to the model when the
599642
- // repeat gate fires, so they must stay complete enough to
599643
- // edit against — truncating to 2KB cut the tail off normal
599644
- // source files (the "old_string not found" cascade). Large
599645
- // files are handled by branch-extract upstream, so 16KB
599646
- // here covers ordinary files without bloating the cache.
599647
- isReadLike ? this.sanitizeCachedToolResult(tc.name, result.output ?? "").slice(0, 16e3) : (result.output ?? "").slice(0, 2e3)
599648
- ),
599489
+ result: (result.output ?? "").slice(0, 2e3),
599649
599490
  compacted: false,
599650
599491
  mutationEpoch: fileMutationEpoch
599651
599492
  });
599652
- if (isReadLike && result.success) {
599653
- this._registerReadCoverage(tc.name, tc.arguments ?? {}, toolFingerprint);
599654
- }
599655
599493
  if (recentToolResults.size > 500) {
599656
599494
  const firstKey = recentToolResults.keys().next().value;
599657
599495
  if (firstKey !== void 0) {
@@ -600164,7 +600002,6 @@ Evidence: ${evidencePreview}`.slice(0, 500);
600164
600002
  turn,
600165
600003
  toolName: tc.name,
600166
600004
  args: tc.arguments ?? {},
600167
- actionReason: toolActionReason,
600168
600005
  fingerprint: toolFingerprint,
600169
600006
  success: result.success,
600170
600007
  output: result.output ?? result.llmContent ?? "",
@@ -600377,7 +600214,6 @@ ${delegateDir}` : delegateDir;
600377
600214
  reason: focusDecision.directive.reason,
600378
600215
  requiredNextAction: focusDecision.directive.requiredNextAction
600379
600216
  },
600380
- actionReason: toolActionReason ?? void 0,
600381
600217
  repeatShortCircuit: Boolean(repeatShortCircuit),
600382
600218
  runtimeAuthored: result.runtimeAuthored === true,
600383
600219
  runtimeGuidance: runtimeSystemGuidance,
@@ -600415,20 +600251,20 @@ ${delegateDir}` : delegateDir;
600415
600251
  if (sameToolFailStreak >= 5 && (this.options.modelTier === "small" || this.options.modelTier === "medium")) {
600416
600252
  this.enqueueRuntimeGuidance(`[BRANCH — evaluate alternatives before acting]
600417
600253
  Tool "${tc.name}" has failed ${sameToolFailStreak} times. STOP and enumerate:
600418
- Option A: [describe a completely different approach]
600419
- Option B: [describe another alternative]
600420
- Option C: [the simplest possible fallback]
600421
- Pick the BEST option and explain why, then execute it. Do NOT retry ${tc.name} with similar arguments.`);
600254
+ Option A: refresh the exact evidence or correct the current tool arguments
600255
+ Option B: use a different narrow diagnostic or targeted edit transport
600256
+ Option C: verify whether the desired change is already present
600257
+ Pick the BEST option and explain why, then execute it. Do NOT retry ${tc.name} with similar arguments, and do not broaden a local edit into file_write.`);
600422
600258
  sameToolFailStreak = 0;
600423
600259
  sameToolFailName = null;
600424
600260
  }
600425
600261
  if (consecutiveSameTool >= 2 && (this.options.modelTier === "small" || this.options.modelTier === "medium")) {
600426
600262
  this.enqueueRuntimeGuidance(`[PIVOT REQUIRED] You have failed ${consecutiveSameTool + 1} times in a row with ${tc.name}. Your current approach is not working. You MUST try something fundamentally different:
600427
- - If file_edit keeps failing: re-read the file first, then use the EXACT text from the file
600263
+ - If file_edit/file_patch keeps failing: re-read the implicated current range, then correct the exact text/range/hash or inspect git diff
600428
600264
  - If shell keeps failing: try a different command or check prerequisites
600429
600265
  - If grep_search finds nothing: try broader patterns or list_directory
600430
- - Consider using a completely different tool or strategy
600431
- Do NOT retry ${tc.name} with similar arguments.`);
600266
+ - A different strategy means a different diagnosis or narrow transport, never a whole-file rewrite fallback
600267
+ Do NOT retry ${tc.name} with similar arguments and do NOT use file_write to escape a targeted-edit failure.`);
600432
600268
  }
600433
600269
  if (!this._taskState.failedApproaches.includes(failDesc)) {
600434
600270
  this._taskState.failedApproaches.push(failDesc);
@@ -600602,7 +600438,6 @@ Then use file_read on individual FILES inside it.`);
600602
600438
  toolName: tc.name,
600603
600439
  argsKey: tc.arguments ? JSON.stringify(tc.arguments) : "",
600604
600440
  args: tc.arguments,
600605
- actionReason: toolActionReason,
600606
600441
  result,
600607
600442
  outputPreview: this._toolEvidencePreview(result, output, 1200, tc.name),
600608
600443
  realFileMutation,
@@ -603052,200 +602887,24 @@ ${marker}` : marker);
603052
602887
  }
603053
602888
  return matchesFresh(args);
603054
602889
  }
603055
- _toolActionReasonPolicy(toolName, args) {
603056
- if (process.env["OMNIUS_DISABLE_TOOL_ACTION_REASON"] === "1") {
603057
- return "disabled";
603058
- }
603059
- if ((process.env["VITEST"] || process.env["NODE_ENV"] === "test") && process.env["OMNIUS_REQUIRE_TOOL_ACTION_REASON"] !== "1") {
603060
- return "disabled";
603061
- }
603062
- if (this.options.artifactMode === "internal")
603063
- return "disabled";
603064
- if (toolName.startsWith("__"))
603065
- return "disabled";
603066
- return this._toolActionReasonIsOptional(toolName, args) ? "optional" : "required";
603067
- }
603068
- _requiresToolActionReason(toolName, args) {
603069
- return this._toolActionReasonPolicy(toolName, args) === "required";
603070
- }
603071
- _toolActionReasonIsOptional(toolName, args) {
603072
- const resolved = this.lookupRegisteredTool(toolName);
603073
- const canonical = resolved?.name ?? toolName;
603074
- if (READ_ONLY_ACTION_REASON_OPTIONAL_TOOLS.has(canonical))
603075
- return true;
603076
- const tool = resolved?.tool;
603077
- if (typeof tool?.isReadOnly === "function") {
603078
- try {
603079
- if (tool.isReadOnly(args ?? {}))
603080
- return true;
603081
- } catch {
603082
- }
603083
- }
603084
- return false;
603085
- }
603086
- _withToolActionReasonSchema(toolName, parameters) {
603087
- const policy = this._toolActionReasonPolicy(toolName);
603088
- if (policy === "disabled") {
603089
- return parameters ?? { type: "object", properties: {} };
603090
- }
602890
+ _publicToolParameters(parameters) {
603091
602891
  const base3 = parameters && typeof parameters === "object" && !Array.isArray(parameters) ? { ...parameters } : { type: "object" };
603092
602892
  const properties = base3["properties"] && typeof base3["properties"] === "object" && !Array.isArray(base3["properties"]) ? { ...base3["properties"] } : {};
603093
- properties["action_reason"] = TOOL_ACTION_REASON_SCHEMA;
603094
- const required = Array.isArray(base3["required"]) ? base3["required"].map(String) : [];
602893
+ for (const key of LEGACY_ACTION_REASON_KEYS)
602894
+ delete properties[key];
602895
+ const required = Array.isArray(base3["required"]) ? base3["required"].map(String).filter((key) => !LEGACY_ACTION_REASON_KEYS.has(key)) : [];
603095
602896
  return {
603096
602897
  ...base3,
603097
602898
  type: "object",
603098
602899
  properties,
603099
- required: policy === "required" ? [.../* @__PURE__ */ new Set([...required, "action_reason"])] : required
603100
- };
603101
- }
603102
- _extractToolActionReason(args) {
603103
- let raw = args["action_reason"] ?? args["actionReason"] ?? args["justification"];
603104
- if (typeof raw === "string") {
603105
- const trimmed = raw.trim();
603106
- if (!trimmed)
603107
- return null;
603108
- try {
603109
- raw = JSON.parse(trimmed);
603110
- } catch {
603111
- return { intent: trimmed };
603112
- }
603113
- }
603114
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
603115
- return null;
603116
- const rec = raw;
603117
- const reason = {
603118
- task_anchor: String(rec["task_anchor"] ?? rec["taskAnchor"] ?? "").trim(),
603119
- intent: String(rec["intent"] ?? "").trim(),
603120
- evidence: String(rec["evidence"] ?? "").trim(),
603121
- expected_result: String(rec["expected_result"] ?? rec["expectedResult"] ?? "").trim(),
603122
- scope: String(rec["scope"] ?? "").trim()
602900
+ required
603123
602901
  };
603124
- return reason;
603125
602902
  }
603126
- _toolActionReasonForCall(toolName, args) {
603127
- const explicit = this._extractToolActionReason(args);
603128
- if (explicit) {
603129
- return this._completeToolActionReason(toolName, args, explicit);
603130
- }
603131
- if (this._shouldSynthesizeMissingToolActionReason(toolName, args)) {
603132
- return this._synthesizeStateUpdateToolActionReason(toolName, args);
603133
- }
603134
- if (this._toolActionReasonPolicy(toolName, args) !== "optional")
603135
- return null;
603136
- return this._synthesizeReadOnlyToolActionReason(toolName, args);
603137
- }
603138
- _completeToolActionReason(toolName, args, partial) {
603139
- const canonical = this.lookupRegisteredTool(toolName)?.name ?? toolName;
603140
- const goal = this._taskState.currentStep || this._taskState.nextAction || this._taskState.goal || this._taskState.originalGoal || "current task";
603141
- const targetPaths = this._extractToolTargetPaths(canonical, args).slice(0, 3);
603142
- const targetText = targetPaths.length > 0 ? ` for ${targetPaths.join(", ")}` : "";
603143
- const scope = TOOL_ACTION_REASON_SCOPES.has(String(partial.scope ?? "")) ? String(partial.scope) : this._defaultToolActionReasonScope(canonical);
603144
- return {
603145
- task_anchor: cleanToolActionReasonField(partial.task_anchor) || String(goal).replace(/\s+/g, " ").trim().slice(0, 160),
603146
- intent: cleanToolActionReasonField(partial.intent) || `Run ${canonical}${targetText} for the active task.`,
603147
- evidence: cleanToolActionReasonField(partial.evidence) || "Runtime completed partial public action metadata from the tool call contract before execution.",
603148
- expected_result: cleanToolActionReasonField(partial.expected_result) || this._defaultToolActionExpectedResult(canonical, targetText),
603149
- scope
603150
- };
603151
- }
603152
- _defaultToolActionReasonScope(toolName) {
603153
- if (toolName === "file_edit" || toolName === "file_patch" || toolName === "batch_edit") {
603154
- return "targeted_patch";
603155
- }
603156
- if (toolName === "create_structured_file")
603157
- return "new_file";
603158
- if (toolName === "task_complete")
603159
- return "completion";
603160
- if (READ_ONLY_ACTION_REASON_OPTIONAL_TOOLS.has(toolName))
603161
- return "discovery";
603162
- return "other";
603163
- }
603164
- _defaultToolActionExpectedResult(toolName, targetText) {
603165
- switch (toolName) {
603166
- case "file_edit":
603167
- return `Requested exact edit is applied${targetText}, or a precise edit error is returned.`;
603168
- case "file_patch":
603169
- return `Requested line patch is applied${targetText}, or a precise patch error is returned.`;
603170
- case "batch_edit":
603171
- return `Requested edit batch is applied${targetText}, or per-edit failure evidence is returned.`;
603172
- case "file_write":
603173
- return `Requested file content is written${targetText}, or a precise write error is returned.`;
603174
- case "shell":
603175
- return "Command exits with output evidence for the next decision.";
603176
- default:
603177
- return `${toolName} completes and returns success or failure evidence.`;
603178
- }
603179
- }
603180
- _shouldSynthesizeMissingToolActionReason(toolName, _args) {
603181
- if (this._toolActionReasonPolicy(toolName, _args) === "disabled") {
603182
- return false;
603183
- }
603184
- const canonical = this.lookupRegisteredTool(toolName)?.name ?? toolName;
603185
- return STATE_UPDATE_ACTION_REASON_SYNTHESIS_TOOLS.has(canonical);
603186
- }
603187
- _synthesizeStateUpdateToolActionReason(toolName, args) {
603188
- const canonical = this.lookupRegisteredTool(toolName)?.name ?? toolName;
603189
- const goal = this._taskState.currentStep || this._taskState.nextAction || this._taskState.goal || this._taskState.originalGoal || "current task";
603190
- const todoCount = Array.isArray(args["todos"]) ? args["todos"].length : null;
603191
- return {
603192
- task_anchor: String(goal).replace(/\s+/g, " ").trim().slice(0, 160),
603193
- intent: `Update visible task state with ${canonical}.`,
603194
- evidence: todoCount === null ? "Model omitted public action_reason; runtime synthesized bookkeeping metadata without authorizing project mutation." : `Model supplied ${todoCount} todo item(s); runtime synthesized bookkeeping metadata without authorizing project mutation.`,
603195
- expected_result: "Session checklist reflects the current task state.",
603196
- scope: "state_update"
603197
- };
603198
- }
603199
- _synthesizeReadOnlyToolActionReason(toolName, args) {
603200
- const canonical = this.lookupRegisteredTool(toolName)?.name ?? toolName;
603201
- const goal = this._taskState.currentStep || this._taskState.nextAction || this._taskState.goal || this._taskState.originalGoal || "current task";
603202
- const targets = this._extractToolTargetPaths(canonical, args).slice(0, 3);
603203
- const targetText = targets.length > 0 ? ` for ${targets.join(", ")}` : "";
603204
- return {
603205
- task_anchor: String(goal).replace(/\s+/g, " ").trim().slice(0, 160),
603206
- intent: `Run read-only ${canonical}${targetText} to gather current evidence.`,
603207
- evidence: "Read-only metadata fallback; no project mutation is authorized by this action.",
603208
- expected_result: `Current ${canonical} evidence for the next decision.`,
603209
- scope: "discovery"
603210
- };
603211
- }
603212
- _validateToolActionReason(toolName, args) {
603213
- const policy = this._toolActionReasonPolicy(toolName, args);
603214
- if (policy === "disabled")
603215
- return null;
603216
- const raw = args["action_reason"] ?? args["actionReason"] ?? args["justification"];
603217
- if (!raw || typeof raw === "string" && !raw.trim()) {
603218
- if (policy === "optional")
603219
- return null;
603220
- if (this._shouldSynthesizeMissingToolActionReason(toolName, args)) {
603221
- return null;
603222
- }
603223
- return [
603224
- "[TOOL ACTION REASON CONTRACT]",
603225
- `This side-effectful tool call must include action_reason: { task_anchor, intent, evidence, expected_result, scope }.`,
603226
- `Use compact public facts only; do not include hidden chain-of-thought.`,
603227
- `scope must be one of: ${Array.from(TOOL_ACTION_REASON_SCOPES).join(", ")}.`
603228
- ].join("\n");
603229
- }
603230
- const partialReason = this._extractToolActionReason(args);
603231
- if (!partialReason) {
603232
- if (policy === "optional")
603233
- return null;
603234
- return "[TOOL ACTION REASON CONTRACT] action_reason must be an object with compact public task-state fields.";
603235
- }
603236
- const reason = this._completeToolActionReason(toolName, args, partialReason);
603237
- if (!TOOL_ACTION_REASON_SCOPES.has(reason.scope)) {
603238
- if (policy === "optional")
603239
- return null;
603240
- return `[TOOL ACTION REASON CONTRACT] action_reason.scope="${reason.scope}" is invalid. Use one of: ${Array.from(TOOL_ACTION_REASON_SCOPES).join(", ")}.`;
603241
- }
603242
- return null;
603243
- }
603244
- _stripToolActionReasonArgs(args) {
602903
+ _stripLegacyActionReasonArgs(args) {
603245
602904
  let changed = false;
603246
602905
  const next = {};
603247
602906
  for (const [key, value2] of Object.entries(args)) {
603248
- if (TOOL_ACTION_REASON_KEYS.has(key)) {
602907
+ if (LEGACY_ACTION_REASON_KEYS.has(key)) {
603249
602908
  changed = true;
603250
602909
  continue;
603251
602910
  }
@@ -603362,7 +603021,7 @@ ${marker}` : marker);
603362
603021
  }
603363
603022
  return next;
603364
603023
  }
603365
- _validateFileWriteOverwriteContract(toolName, args, actionReason) {
603024
+ _validateFileWriteOverwriteContract(toolName, args) {
603366
603025
  if (toolName !== "file_write")
603367
603026
  return null;
603368
603027
  if (process.env["OMNIUS_ALLOW_UNVERIFIED_FILE_WRITE_OVERWRITE"] === "1") {
@@ -603384,20 +603043,13 @@ ${marker}` : marker);
603384
603043
  }
603385
603044
  if (!exists2)
603386
603045
  return null;
603387
- if (actionReason && actionReason.scope !== "full_file_rewrite") {
603388
- return [
603389
- "[FULL FILE REWRITE CONTRACT]",
603390
- `file_write targets an existing file: ${path16}.`,
603391
- `For constrained repairs, use file_patch, batch_edit, or file_edit instead of rewriting the full file.`,
603392
- `If a whole-file replacement is truly intended, set action_reason.scope="full_file_rewrite" and use expected_hash from a fresh file_read.`
603393
- ].join("\n");
603394
- }
603395
603046
  if (!this._toolCallUsesFreshFileReadEvidence(toolName, args)) {
603396
603047
  const fresh = this._freshReadHashForEditPath(path16);
603397
603048
  return [
603398
603049
  "[FULL FILE REWRITE CONTRACT]",
603399
603050
  `file_write would overwrite existing file ${path16}, but it is not anchored to fresh file_read evidence.`,
603400
- fresh ? `Use expected_hash=${fresh.hash} from the current active evidence, or prefer file_patch/batch_edit for a constrained change.` : `First file_read ${path16}, then prefer file_patch/batch_edit; only retry file_write with expected_hash if a whole-file rewrite is necessary.`
603051
+ "file_write is reserved for new, empty/placeholder, or genuinely small files needing deliberate total replacement; it is never recovery for a failed local edit.",
603052
+ fresh ? `Use expected_hash=${fresh.hash} from the current active evidence, or prefer file_patch/batch_edit for a constrained change.` : `First file_read ${path16}, then prefer file_patch/batch_edit; only retry file_write with expected_hash if a whole-file replacement is necessary.`
603401
603053
  ].join("\n");
603402
603054
  }
603403
603055
  return null;
@@ -605157,9 +604809,9 @@ ${postCompactRestore.join("\n")}`);
605157
604809
 
605158
604810
  **IMPORTANT:** You MUST invoke tools through the function calling interface. Do NOT write tool names as text, markdown, or code blocks — that does nothing. Call tools using the structured tool/function API.` : "";
605159
604811
  const readFilesList = Array.from(this._fileRegistry.entries()).filter(([, e2]) => e2.accessCount > 0).map(([p2]) => p2).slice(0, 10);
605160
- const antiRepetitionReminder = (tier === "small" || tier === "medium") && readFilesList.length > 0 ? `
604812
+ const fileFreshnessReminder = (tier === "small" || tier === "medium") && readFilesList.length > 0 ? `
605161
604813
 
605162
- **DO NOT RE-READ THESE FILES** (you already have their contents): ${readFilesList.join(", ")}. Use the information above to make progress. Reading the same file again wastes a turn.` : "";
604814
+ **PREVIOUSLY OBSERVED FILES:** ${readFilesList.join(", ")}. Treat restored or summarized text as orientation, not guaranteed current bytes. Before editing, make a narrow live read when the body is elided/truncated, the file may have changed, a hash or edit anchor is stale, or exact current text is otherwise required. Avoid only redundant reads when the latest complete authoritative content is still visible and unchanged.` : "";
605163
604815
  const ephemeralSkillPackReminder = this._ephemeralSkillPackContext ? `
605164
604816
 
605165
604817
  [Ephemeral skill-pack restore — current run only, do not persist]
@@ -605177,11 +604829,11 @@ ${scopedStickyDynamicContext}` : "";
605177
604829
  ${fullSummary}
605178
604830
  </compaction-summary>
605179
604831
 
605180
- [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${antiRepetitionReminder}${stickyDynamicContextReminder}${ephemeralSkillPackReminder}${toolCallingReminder}` : `[Context compacted${strategyLabel} — summary of earlier work]
604832
+ [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${fileFreshnessReminder}${stickyDynamicContextReminder}${ephemeralSkillPackReminder}${toolCallingReminder}` : `[Context compacted${strategyLabel} — summary of earlier work]
605181
604833
 
605182
604834
  ${fullSummary}
605183
604835
 
605184
- [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${antiRepetitionReminder}${stickyDynamicContextReminder}${ephemeralSkillPackReminder}${toolCallingReminder}`
604836
+ [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${fileFreshnessReminder}${stickyDynamicContextReminder}${ephemeralSkillPackReminder}${toolCallingReminder}`
605185
604837
  };
605186
604838
  this.persistCheckpoint(fullSummary);
605187
604839
  let narrowedHead = [...head];
@@ -605242,10 +604894,11 @@ ${telegramPersonaHead}` : stripped
605242
604894
  const tokenEst = Math.ceil(content.length / 4);
605243
604895
  if (recoveredTokens + tokenEst > fileRecoveryBudget)
605244
604896
  break;
604897
+ const truncated = content.length > 8e3;
605245
604898
  result.push({
605246
604899
  role: "system",
605247
- content: `<recovered-file path="${filePath}" status="${entry.modified ? "modified" : "read"}">
605248
- ${content.slice(0, 8e3)}
604900
+ content: `<recovered-file path="${filePath}" status="${entry.modified ? "modified" : "read"}" truncated="${truncated}">
604901
+ ${content.slice(0, 8e3)}${truncated ? "\n[truncated recovery; use a narrow live read for omitted/current text]" : ""}
605249
604902
  </recovered-file>`
605250
604903
  });
605251
604904
  recoveredFiles.push(filePath);
@@ -605532,7 +605185,6 @@ ${trimmedNew}`;
605532
605185
  fingerprint,
605533
605186
  stateVersion: this._adversaryStateVersion,
605534
605187
  succeeded: input.result.success === true,
605535
- actionReason: input.actionReason,
605536
605188
  ...outcomeEvidence
605537
605189
  });
605538
605190
  while (this._recentToolOutcomes.length > 40) {
@@ -605678,7 +605330,6 @@ ${trimmedNew}`;
605678
605330
  args = JSON.parse(tc.function.arguments);
605679
605331
  } catch {
605680
605332
  }
605681
- const actionReason = this._toolActionReasonForCall(name10, args);
605682
605333
  const argsKey = this._buildExactArgsKey(args);
605683
605334
  const fingerprint = this._buildToolFingerprint(name10, args);
605684
605335
  const prior = this._adversaryToolOutcomes.find((o2) => o2.succeeded && o2.tool === name10 && o2.fingerprint === fingerprint && o2.stateVersion === this._adversaryStateVersion && o2.turn < turn);
@@ -605692,7 +605343,6 @@ ${trimmedNew}`;
605692
605343
  tool: name10,
605693
605344
  target: argsKey.slice(0, 180),
605694
605345
  count: repeatCount,
605695
- actionReason: actionReason ?? void 0,
605696
605346
  alreadyHave: prior.preview
605697
605347
  }
605698
605348
  }, `possible repeated action ${name10} after prior same-state success`);
@@ -606926,7 +606576,7 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
606926
606576
  function: {
606927
606577
  name: tool.name,
606928
606578
  description: compressDesc ? (desc.split(/\.\s/)[0]?.slice(0, 120) ?? desc.slice(0, 120)) + "." : desc,
606929
- parameters: this._withToolActionReasonSchema(tool.name, tool.parameters)
606579
+ parameters: this._publicToolParameters(tool.parameters)
606930
606580
  }
606931
606581
  };
606932
606582
  });
@@ -606948,7 +606598,7 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
606948
606598
 
606949
606599
  Available tools (${deferred.length}):
606950
606600
  ${catalog}`,
606951
- parameters: this._withToolActionReasonSchema("tool_search", {
606601
+ parameters: this._publicToolParameters({
606952
606602
  type: "object",
606953
606603
  properties: {
606954
606604
  query: {
@@ -606999,7 +606649,7 @@ ${catalog}`,
606999
606649
  lines.push(`Aliases: ${tool.aliases.join(", ")}`);
607000
606650
  }
607001
606651
  lines.push(`${getDesc(tool)}${customToolDetails(tool)}`);
607002
- lines.push(`Parameters: ${JSON.stringify(this._withToolActionReasonSchema(tool.name, tool.parameters))}`);
606652
+ lines.push(`Parameters: ${JSON.stringify(this._publicToolParameters(tool.parameters))}`);
607003
606653
  }
607004
606654
  }
607005
606655
  if (alreadyAvailable.length > 0) {
@@ -607051,7 +606701,7 @@ ${catalog}`,
607051
606701
  for (const t2 of matches)
607052
606702
  activatedToolsRef.add(t2.name);
607053
606703
  const result = matches.map((t2) => {
607054
- const paramsStr = JSON.stringify(this._withToolActionReasonSchema(t2.name, t2.parameters), null, 2);
606704
+ const paramsStr = JSON.stringify(this._publicToolParameters(t2.parameters), null, 2);
607055
606705
  const aliases = t2.aliases?.length ? `
607056
606706
  Aliases: ${t2.aliases.join(", ")}` : "";
607057
606707
  return `## ${t2.name}${aliases}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.534",
3
+ "version": "1.0.535",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.534",
9
+ "version": "1.0.535",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.534",
3
+ "version": "1.0.535",
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",
@@ -85,4 +85,4 @@ Always provide explicit type parameters for generic array methods:
85
85
 
86
86
  ### Read Before Editing
87
87
 
88
- Always read a file with `file_read` before editing it with `file_edit` or `file_write`. Editing a file you have not read will lose any content between the version in your training data and the actual current file.
88
+ Always read the current target before editing. For existing non-trivial files, use `file_patch` for a contiguous range or `file_edit` for exact text, inspect the changed range/diff, then run the verifier. Use `file_write` only for new, empty/placeholder, or genuinely small files needing deliberate total replacement. Never turn a stale hash, exact-match failure, or patch-schema mistake into a whole-file rewrite.
@@ -73,9 +73,9 @@ If you anticipate a large result before calling a tool, prefer narrow flags firs
73
73
  ## Available Tools
74
74
 
75
75
  - file_read: Read file contents (always read before editing). Supports path, offset, limit.
76
- - file_write: Create or overwrite a file with complete UTF-8 text. If full-file content is hard to JSON-escape, use content_base64 instead of shell cat/tee/heredoc.
77
- - file_edit: Make a precise string replacement in a file (preferred over rewriting). Uses old_string/new_string. old_string must be unique unless replace_all=true. Use old_string_base64/new_string_base64 if exact text is hard to JSON-escape.
78
- - file_patch: Edit specific line ranges in large files. Modes: replace (swap lines), insert_before, insert_after, delete. Use dry_run to preview. Use new_content_base64 if replacement text is hard to JSON-escape. Best for large files (500+ lines) where string matching is fragile.
76
+ - file_write: Create new files or deliberately replace small, empty, or placeholder files. It is not a recovery path for failed targeted edits.
77
+ - file_edit: Replace exact current text copied from a fresh read. Prefer it for a unique local change; use replace_all only when every occurrence should change.
78
+ - file_patch: Replace, insert, or delete a specific line range from a fresh read. Prefer it whenever the implicated existing-file region is contiguous.
79
79
  - batch_edit: Apply multiple exact string replacements atomically across files. Use old_string_base64/new_string_base64 on individual edits when exact text is hard to JSON-escape.
80
80
  - find_files: Find files by name pattern (glob). Searches recursively, excludes node_modules/.git.
81
81
  - grep_search: Search file contents with regex. Returns matching lines with paths and line numbers.
@@ -111,7 +111,7 @@ Order: web_search (find) → web_fetch (read) → web_crawl (if JS/multi-page)
111
111
 
112
112
  - Shell is for commands, builds, tests, and system operations. Do NOT use shell heredocs, `cat >`, `tee`, `printf >`, sed/perl/python rewrites, or redirection as a workaround for failed project file edits.
113
113
  - If file_write/file_edit/file_patch/batch_edit reports malformed JSON or encoding trouble, the tool call did not reach the filesystem. Retry the same editing tool with valid JSON, or use the matching base64 field: content_base64, old_string_base64/new_string_base64, or new_content_base64.
114
- - For existing files, read first and preserve version checks: file_edit/file_patch/batch_edit for targeted changes; file_write with overwrite=true and expected_hash only for intentional full rewrites.
114
+ - For existing files, use read -> targeted patch/edit -> diff/readback -> live verifier. A stale hash, old_string mismatch, or malformed patch requires refreshed evidence or corrected arguments, never a whole-file fallback. Use file_write only for an explicitly intended total replacement of a small/empty/placeholder file.
115
115
 
116
116
  ## Tool Selection Discipline
117
117
 
@@ -61,9 +61,9 @@ Tool results over ~100KB are NOT truncated. The orchestrator saves the full payl
61
61
  ## Tools
62
62
 
63
63
  - file_read: Read file contents (always read before editing)
64
- - file_write: Create or overwrite a file with complete UTF-8 text. Use content_base64 when full-file content is hard to JSON-escape.
65
- - file_edit: Precise string replacement (preferred over rewriting). old_string must be unique. Use old_string_base64/new_string_base64 when exact text is hard to JSON-escape.
66
- - file_patch: Edit specific line ranges in large files. Use new_content_base64 when replacement text is hard to JSON-escape.
64
+ - file_write: Create new files or deliberately replace small, empty, or placeholder files. Never use it as fallback after a targeted edit failure.
65
+ - file_edit: Replace exact text copied from a fresh read. Prefer this for a unique local change.
66
+ - file_patch: Replace, insert, or delete a specific line range from a fresh read. Prefer this for contiguous existing-file changes.
67
67
  - batch_edit: Multiple exact replacements across files in one atomic call. Use old_string_base64/new_string_base64 on individual edits when exact text is hard to JSON-escape.
68
68
  - find_files: Find files by glob pattern
69
69
  - grep_search: Search file contents with regex
@@ -124,7 +124,7 @@ For login, form filling, or clicking: call browser_action with action=navigate F
124
124
  - batch_edit: Multiple edits across files in one call
125
125
  - skill_list / skill_execute / skill_build: Discover, load, and generate skills (use on-demand)
126
126
 
127
- File editing discipline: Do NOT use shell heredocs, `cat >`, `tee`, `printf >`, sed/perl/python rewrites, or redirection as a workaround for failed project file edits. If an edit tool reports malformed JSON or content encoding trouble, retry file_write/file_edit/file_patch/batch_edit with valid JSON or the matching base64 field. Shell is for commands, builds, tests, and system operations.
127
+ File editing discipline: Existing-file repairs follow read current target -> file_patch/file_edit -> inspect changed range/diff -> live verifier. Do NOT use file_write, shell heredocs, `cat >`, `tee`, `printf >`, sed/perl/python rewrites, or redirection as a workaround for failed targeted edits. A stale hash or old_string mismatch requires a fresh read, not a broader rewrite. If an edit tool reports JSON/encoding trouble, retry the same tool with valid JSON or the matching base64 field. Shell is for commands, builds, tests, and system operations.
128
128
 
129
129
  Tool selection discipline: Use the narrowest structured tool that preserves diagnostics — file/search/code-graph tools for repository discovery, web_fetch/web_download/browser_action for web work, and repl_exec for multi-step data processing. Use shell when the command itself is the verifier or work product: tests, builds, package managers, git, system operations, and small native scripts. Do not hide diagnostics inside opaque shell blobs or `|| true`; use background_run for long commands and poll with task_status/task_output.
130
130
 
@@ -44,7 +44,7 @@ EVIDENCE RULE (most important): NEVER claim something works or is true unless a
44
44
 
45
45
  Tools: file_read, file_write, file_edit, file_patch, batch_edit, file_explore, working_notes, shell, task_complete, find_files, grep_search, symbol_search, impact_analysis, code_neighbors, web_search, web_fetch, nexus, todo_write, todo_read, debate (multi-agent vote on hard sub-decisions, use after 3+ failed approaches), replay_with_intervention (DoVer-style turn replay with corrective directive)
46
46
 
47
- File edits: Use file_write/file_edit/file_patch/batch_edit for project files, not shell heredocs, `cat >`, `tee`, `printf >`, sed/perl/python rewrites, or redirection. If file_write/file_edit/file_patch/batch_edit says malformed JSON or content encoding failed, retry the same edit tool with valid JSON or base64 fields: content_base64, old_string_base64/new_string_base64, or new_content_base64. Shell is for tests/builds/commands.
47
+ File edits: For existing files, read the current target, use file_patch for a line range or file_edit for exact text, inspect the changed range, then run the verifier. Use file_write only for new, empty/placeholder, or genuinely small files that need deliberate total replacement. Never broaden a failed patch or stale-hash edit into a whole-file write. Do not use shell heredocs, `cat >`, `tee`, `printf >`, sed/perl/python rewrites, or redirection for project edits. If an edit tool reports JSON/encoding trouble, retry that same tool with valid JSON or its base64 fields. Shell is for tests/builds/commands.
48
48
 
49
49
  Python dependencies: First check project bootstrap files (`pyproject.toml`, lockfiles, `requirements*.txt`, `Makefile`, `scripts/bootstrap*`, `scripts/setup*`). Do not use system `pip install` for project work. Use `.venv`: `python3 -m venv .venv` then `.venv/bin/python -m pip install ...` (Windows: `.venv\\Scripts\\python.exe -m pip ...`). If no bootstrap exists, create a small `.venv` bootstrap command/script for repeatable setup.
50
50
 
@@ -135,7 +135,7 @@ Build is FEEDBACK, not a task. A build/test command (make, cargo, npm test, tsc,
135
135
  3. Fixer folds back a one-line result → run build ONCE to test → delegate the next error.
136
136
  4. Keep YOUR context small: hold what's done + what's left; delegate the fixes. Don't re-read files you already read. A failing build never terminates you.
137
137
 
138
- CRITICAL NEVER repeat a tool call with the same arguments. If you already read a file, use the data you have. If you already ran a command, use the output. Calling the same tool twice with identical arguments wastes turns and produces the same result.
138
+ Repeat policy: Do not repeat discovery calls merely to re-orient. Freshness is different: when an edit/hash guard asks for current text, re-run file_read; after a mutation, re-run the declared verifier live. Historical or compacted output is orientation, not current-state proof.
139
139
 
140
140
  Long document generation (reports, SOWs, proposals, contracts):
141
141
  NEVER write the entire document in one file_write. DECOMPOSE: