omnius 1.0.460 → 1.0.462

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
@@ -563781,6 +563781,15 @@ function isSuccessfulVerificationEvidence(entry) {
563781
563781
  function isFailedVerificationEvidence(entry) {
563782
563782
  return entry.role === "verification" && entry.success === false;
563783
563783
  }
563784
+ function isSuccessfulPostMutationObservation(entry) {
563785
+ if (entry.success !== true)
563786
+ return false;
563787
+ if (entry.role === "blocker")
563788
+ return false;
563789
+ if (isMutationEvidence(entry))
563790
+ return false;
563791
+ return entry.kind === "tool_result" || entry.kind === "runtime_observation" || entry.kind === "delivery_result";
563792
+ }
563784
563793
  function verificationFamily(entry) {
563785
563794
  if (entry.role !== "verification")
563786
563795
  return null;
@@ -563807,6 +563816,7 @@ function finalizeCompletionLedgerTruth(ledger) {
563807
563816
  let unresolved = [...ledger.unresolved];
563808
563817
  let lastVerificationInvalidatingMutation = -1;
563809
563818
  let lastVerification = -1;
563819
+ let lastCriticApprovedVerification = -1;
563810
563820
  const lastSuccessfulVerificationByFamily = /* @__PURE__ */ new Map();
563811
563821
  ledger.evidence.forEach((entry, index) => {
563812
563822
  if (isVerificationInvalidatingMutation(entry))
@@ -563818,6 +563828,21 @@ function finalizeCompletionLedgerTruth(ledger) {
563818
563828
  lastSuccessfulVerificationByFamily.set(family, index);
563819
563829
  }
563820
563830
  });
563831
+ for (const critique2 of ledger.critiques) {
563832
+ if (critique2.verdict !== "approve")
563833
+ continue;
563834
+ if (lastVerificationInvalidatingMutation >= 0 && critique2.evidenceSequence <= lastVerificationInvalidatingMutation) {
563835
+ continue;
563836
+ }
563837
+ const reviewedEvidence = ledger.evidence.slice(Math.max(0, lastVerificationInvalidatingMutation + 1), Math.max(0, critique2.evidenceSequence));
563838
+ if (reviewedEvidence.some(isSuccessfulPostMutationObservation)) {
563839
+ lastCriticApprovedVerification = Math.max(lastCriticApprovedVerification, critique2.evidenceSequence - 1);
563840
+ }
563841
+ }
563842
+ const effectiveLastVerification = Math.max(lastVerification, lastCriticApprovedVerification);
563843
+ if (effectiveLastVerification >= 0 && effectiveLastVerification >= lastVerificationInvalidatingMutation) {
563844
+ unresolved = unresolved.filter((item) => item.source !== "verification_missing" && item.source !== "verification_freshness");
563845
+ }
563821
563846
  ledger.evidence.forEach((entry, index) => {
563822
563847
  if (isFailedVerificationEvidence(entry)) {
563823
563848
  const family = verificationFamily(entry);
@@ -563830,12 +563855,13 @@ function finalizeCompletionLedgerTruth(ledger) {
563830
563855
  unresolved = appendUnresolved(unresolved, `Stale edit failure remains unresolved: ${entry.summary}`, entry.id);
563831
563856
  }
563832
563857
  });
563833
- if (lastVerificationInvalidatingMutation >= 0 && lastVerification < 0) {
563858
+ if (lastVerificationInvalidatingMutation >= 0 && effectiveLastVerification < 0) {
563834
563859
  unresolved = appendUnresolved(unresolved, "File changes occurred without any successful verification evidence.", "verification_missing");
563835
- } else if (lastVerification >= 0 && lastVerificationInvalidatingMutation > lastVerification) {
563860
+ } else if (effectiveLastVerification >= 0 && lastVerificationInvalidatingMutation > effectiveLastVerification) {
563836
563861
  unresolved = appendUnresolved(unresolved, "File changes occurred after the last successful verification; final verification is stale.", "verification_freshness");
563837
563862
  }
563838
- const status = ledger.status === "blocked" || ledger.status === "request_changes" ? ledger.status : unresolved.length > 0 ? "incomplete_verification" : ledger.status;
563863
+ const latestCritique = ledger.critiques.at(-1);
563864
+ const status = ledger.status === "blocked" || ledger.status === "request_changes" ? ledger.status : unresolved.length > 0 ? "incomplete_verification" : latestCritique?.verdict === "approve" ? "approved" : ledger.status === "incomplete_verification" ? "open" : ledger.status;
563839
563865
  return {
563840
563866
  ...ledger,
563841
563867
  unresolved,
@@ -566100,6 +566126,11 @@ var init_personality = __esm({
566100
566126
  // packages/orchestrator/dist/critic.js
566101
566127
  function buildCriticGuidanceMessage(call, hits, opts = {}) {
566102
566128
  const argPreview = JSON.stringify(call.args ?? {}).slice(0, 200);
566129
+ const actionReason = call.actionReason ? [
566130
+ call.actionReason.scope ? `scope=${call.actionReason.scope}` : "",
566131
+ call.actionReason.intent ? `intent=${call.actionReason.intent}` : "",
566132
+ call.actionReason.evidence ? `evidence=${call.actionReason.evidence}` : ""
566133
+ ].filter(Boolean).join("; ").slice(0, 500) : "";
566103
566134
  const cached = opts.cachedResult ? `
566104
566135
  Prior evidence preview:
566105
566136
  ${opts.cachedResult.slice(0, 700)}` : "";
@@ -566107,7 +566138,8 @@ ${opts.cachedResult.slice(0, 700)}` : "";
566107
566138
  return `[RUNTIME EVIDENCE CACHE — non-blocking]
566108
566139
  Observation: ${source}
566109
566140
  Call: ${call.tool}(${argPreview})
566110
- State: exact prior evidence exists for these arguments in the current state version.
566141
+ ` + (actionReason ? `Action reason: ${actionReason}
566142
+ ` : "") + `State: exact prior evidence exists for these arguments in the current state version.
566111
566143
  Next action contract: let this result inform the next step once, then pivot to a concrete action.
566112
566144
  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}`;
566113
566145
  }
@@ -573987,6 +574019,8 @@ ${obs.stateDigest.slice(0, 1800)}
573987
574019
  `This is a loop. Reason about WHY — for THIS specific call, not generically.`,
573988
574020
  ls2.alreadyHave ? `Evidence the agent ALREADY obtained from a prior identical call:
573989
574021
  ${ls2.alreadyHave.slice(0, 900)}` : `(No cached result available for the repeated call.)`,
574022
+ ls2.actionReason ? `Agent's stated action reason:
574023
+ scope=${ls2.actionReason.scope ?? ""}; intent=${ls2.actionReason.intent ?? ""}; evidence=${ls2.actionReason.evidence ?? ""}; expected=${ls2.actionReason.expected_result ?? ""}`.slice(0, 700) : "",
573990
574024
  "",
573991
574025
  stateDigest.trim(),
573992
574026
  "",
@@ -574388,6 +574422,7 @@ function recordDebugToolEvent(input) {
574388
574422
  },
574389
574423
  ...input.focusSupervisor ? { focusSupervisor: input.focusSupervisor } : {},
574390
574424
  ...input.focusDecision ? { focusDecision: input.focusDecision } : {},
574425
+ ...input.actionReason ? { actionReason: compactDebugValue(input.actionReason) } : {},
574391
574426
  ...input.repeatShortCircuit !== void 0 ? { repeatShortCircuit: input.repeatShortCircuit } : {},
574392
574427
  ...input.runtimeAuthored !== void 0 ? { runtimeAuthored: input.runtimeAuthored } : {},
574393
574428
  ...input.runtimeGuidance ? { runtimeGuidancePreview: trim(input.runtimeGuidance, 1200) } : {},
@@ -575057,6 +575092,30 @@ function trim(value2, max) {
575057
575092
  return `${value2.slice(0, max)}
575058
575093
  [truncated ${value2.length - max} chars]`;
575059
575094
  }
575095
+ function compactDebugValue(value2, depth = 0) {
575096
+ if (value2 == null)
575097
+ return value2;
575098
+ if (typeof value2 === "string") {
575099
+ return value2.length > 500 ? `${value2.slice(0, 497)}...` : value2;
575100
+ }
575101
+ if (typeof value2 === "number" || typeof value2 === "boolean")
575102
+ return value2;
575103
+ if (Array.isArray(value2)) {
575104
+ if (depth >= 2)
575105
+ return `[array:${value2.length}]`;
575106
+ return value2.slice(0, 8).map((entry) => compactDebugValue(entry, depth + 1));
575107
+ }
575108
+ if (typeof value2 === "object") {
575109
+ if (depth >= 2)
575110
+ return "[object]";
575111
+ const out = {};
575112
+ for (const [key, entry] of Object.entries(value2).slice(0, 12)) {
575113
+ out[key] = compactDebugValue(entry, depth + 1);
575114
+ }
575115
+ return out;
575116
+ }
575117
+ return String(value2);
575118
+ }
575060
575119
  function safeId2(value2) {
575061
575120
  return String(value2 || "unknown").replace(/[^a-zA-Z0-9_.:-]/g, "_").slice(0, 160);
575062
575121
  }
@@ -575098,6 +575157,9 @@ function violatesDirective(directive, input) {
575098
575157
  }
575099
575158
  if (input.toolName === "ask_user")
575100
575159
  return false;
575160
+ if (directive.requiredNextAction === "read_authoritative_target" && input.usesAuthoritativeTargetEvidence && isEditTool(input.toolName)) {
575161
+ return false;
575162
+ }
575101
575163
  const family = actionFamily(input.toolName, input.args);
575102
575164
  if (directive.requiredNextAction !== "update_todos") {
575103
575165
  if (directive.forbiddenActionFamilies.includes(family))
@@ -575185,6 +575247,17 @@ function compactCachedEvidence(text2) {
575185
575247
  "Use the cached evidence now, or request a distinct offset/limit/query if genuinely needed."
575186
575248
  ].filter(Boolean).join("\n");
575187
575249
  }
575250
+ function actionReasonSummary(actionReason) {
575251
+ if (!actionReason)
575252
+ return "";
575253
+ const parts = [
575254
+ actionReason.scope ? `scope=${actionReason.scope}` : "",
575255
+ actionReason.intent ? `intent=${actionReason.intent}` : "",
575256
+ actionReason.evidence ? `evidence=${actionReason.evidence}` : "",
575257
+ actionReason.expected_result ? `expected=${actionReason.expected_result}` : ""
575258
+ ].filter(Boolean);
575259
+ return parts.length > 0 ? `Action reason observed: ${parts.join("; ").slice(0, 700)}` : "";
575260
+ }
575188
575261
  function actionFamily(toolName, args) {
575189
575262
  if (isEditTool(toolName)) {
575190
575263
  const path12 = primaryPath(args);
@@ -575408,8 +575481,9 @@ var init_focusSupervisor = __esm({
575408
575481
  `${reason}.`,
575409
575482
  `Required next action: ${prior.requiredNextAction}.`,
575410
575483
  `Blocked action family: ${family}.`,
575484
+ actionReasonSummary(input.actionReason),
575411
575485
  "This block is not counted as a new ignored directive."
575412
- ].join("\n"), false);
575486
+ ].filter(Boolean).join("\n"), false);
575413
575487
  }
575414
575488
  return this.pass();
575415
575489
  }
@@ -575450,8 +575524,9 @@ var init_focusSupervisor = __esm({
575450
575524
  `[FOCUS SUPERVISOR BLOCK] The previous directive was ignored: ${prior.reason}`,
575451
575525
  `Required next action: ${directive.requiredNextAction}.`,
575452
575526
  `Blocked action family: ${family}.`,
575527
+ actionReasonSummary(input.actionReason),
575453
575528
  directive.requiredNextAction === "report_incomplete" ? "Stop trying tool variants. Report incomplete/blocked with the concrete evidence." : "Take the required next action before trying another variant."
575454
- ].join("\n"), false);
575529
+ ].filter(Boolean).join("\n"), false);
575455
575530
  }
575456
575531
  }
575457
575532
  if (input.stalePreflightMessage) {
@@ -575462,9 +575537,12 @@ var init_focusSupervisor = __esm({
575462
575537
  requiredNextAction: "read_authoritative_target",
575463
575538
  forbiddenActionFamilies: [family]
575464
575539
  });
575465
- return this.block(directive, `${input.stalePreflightMessage}
575466
-
575467
- [FOCUS SUPERVISOR] Required next action: file_read the authoritative current target once, then build a new edit from current evidence or report incomplete.`, false);
575540
+ return this.block(directive, [
575541
+ input.stalePreflightMessage,
575542
+ "",
575543
+ actionReasonSummary(input.actionReason),
575544
+ "[FOCUS SUPERVISOR] Required next action: file_read the authoritative current target once, then build a new edit from current evidence or report incomplete."
575545
+ ].filter(Boolean).join("\n"), false);
575468
575546
  }
575469
575547
  const duplicateHitCount = input.duplicateHitCount ?? 0;
575470
575548
  if (input.cachedResult && duplicateHitCount > 0) {
@@ -575484,9 +575562,10 @@ var init_focusSupervisor = __esm({
575484
575562
  input.cachedResultFailed ? "[FOCUS SUPERVISOR: CACHED FAILURE]" : "[FOCUS SUPERVISOR: CACHED EVIDENCE]",
575485
575563
  `This ${input.toolName} action family has already produced evidence and should not be re-executed unchanged.`,
575486
575564
  `Required next action: ${directive.requiredNextAction}.`,
575565
+ actionReasonSummary(input.actionReason),
575487
575566
  "",
575488
575567
  compactCachedEvidence(input.cachedResult)
575489
- ].join("\n"), !input.cachedResultFailed);
575568
+ ].filter(Boolean).join("\n"), !input.cachedResultFailed);
575490
575569
  }
575491
575570
  return this.inject(directive, `[FOCUS SUPERVISOR] ${directive.reason}. Required next action: ${directive.requiredNextAction}.`);
575492
575571
  }
@@ -575540,6 +575619,10 @@ var init_focusSupervisor = __esm({
575540
575619
  this.lastReason = "cached/no-op tool result did not perform the required recovery action";
575541
575620
  return;
575542
575621
  }
575622
+ if (input.success && input.usedAuthoritativeTargetEvidence && isEditTool(input.toolName) && this.directive?.requiredNextAction === "read_authoritative_target") {
575623
+ this.clearSatisfiedDirective("verified edit used authoritative target evidence", input.turn);
575624
+ return;
575625
+ }
575543
575626
  if (input.toolName === "file_read" && input.success) {
575544
575627
  if (this.directive?.requiredNextAction === "read_authoritative_target") {
575545
575628
  this.clearSatisfiedDirective("authoritative evidence refreshed", input.turn);
@@ -578420,7 +578503,7 @@ function classifyThinkOutcome(raw) {
578420
578503
  }
578421
578504
  return null;
578422
578505
  }
578423
- var TOOL_SUBSETS, TOOL_AUTO_DEMOTE_TURNS, SYSTEM_PROMPT, SYSTEM_PROMPT_MEDIUM, SYSTEM_PROMPT_SMALL, VISUAL_TOOLS, AUDIO_TOOLS, SOCIAL_TOOLS, SPATIAL_TOOLS, CODE_TOOLS, AgenticRunner, OllamaAgenticBackend;
578506
+ var TOOL_SUBSETS, TOOL_AUTO_DEMOTE_TURNS, TOOL_ACTION_REASON_KEYS, TOOL_ACTION_REASON_SCOPES, TOOL_ACTION_REASON_SCHEMA, SYSTEM_PROMPT, SYSTEM_PROMPT_MEDIUM, SYSTEM_PROMPT_SMALL, VISUAL_TOOLS, AUDIO_TOOLS, SOCIAL_TOOLS, SPATIAL_TOOLS, CODE_TOOLS, AgenticRunner, OllamaAgenticBackend;
578424
578507
  var init_agenticRunner = __esm({
578425
578508
  "packages/orchestrator/dist/agenticRunner.js"() {
578426
578509
  "use strict";
@@ -578509,6 +578592,56 @@ var init_agenticRunner = __esm({
578509
578592
  skill: ["skill_list", "skill_extract", "skill_execute", "skill_search"]
578510
578593
  };
578511
578594
  TOOL_AUTO_DEMOTE_TURNS = 10;
578595
+ TOOL_ACTION_REASON_KEYS = /* @__PURE__ */ new Set([
578596
+ "action_reason",
578597
+ "actionReason",
578598
+ "justification"
578599
+ ]);
578600
+ TOOL_ACTION_REASON_SCOPES = /* @__PURE__ */ new Set([
578601
+ "discovery",
578602
+ "targeted_patch",
578603
+ "new_file",
578604
+ "full_file_rewrite",
578605
+ "verification",
578606
+ "state_update",
578607
+ "delegation",
578608
+ "completion",
578609
+ "other"
578610
+ ]);
578611
+ TOOL_ACTION_REASON_SCHEMA = {
578612
+ type: "object",
578613
+ description: "Compact public action contract for this exact tool call. Do not include hidden chain-of-thought; use concise task-state facts.",
578614
+ properties: {
578615
+ task_anchor: {
578616
+ type: "string",
578617
+ description: "One compact phrase tying this call to the active user goal/current focus."
578618
+ },
578619
+ intent: {
578620
+ type: "string",
578621
+ description: "One sentence explaining why this tool is the next action."
578622
+ },
578623
+ evidence: {
578624
+ type: "string",
578625
+ description: "Fresh evidence or contract authorizing the action: file hash, focus directive, cached evidence, user request, or explicit absence."
578626
+ },
578627
+ expected_result: {
578628
+ type: "string",
578629
+ description: "The concrete new state or information expected from this call."
578630
+ },
578631
+ scope: {
578632
+ type: "string",
578633
+ enum: Array.from(TOOL_ACTION_REASON_SCOPES),
578634
+ description: "Classify the action scope. Use targeted_patch for constrained edits; full_file_rewrite only for deliberate whole-file replacement."
578635
+ }
578636
+ },
578637
+ required: [
578638
+ "task_anchor",
578639
+ "intent",
578640
+ "evidence",
578641
+ "expected_result",
578642
+ "scope"
578643
+ ]
578644
+ };
578512
578645
  SYSTEM_PROMPT = loadPrompt("agentic/system-large.md");
578513
578646
  SYSTEM_PROMPT_MEDIUM = loadPrompt("agentic/system-medium.md");
578514
578647
  SYSTEM_PROMPT_SMALL = loadPrompt("agentic/system-small.md");
@@ -579063,6 +579196,15 @@ var init_agenticRunner = __esm({
579063
579196
  _workboardDir() {
579064
579197
  return this.options.workboardDir || this._workingDirectory || process.cwd();
579065
579198
  }
579199
+ _todoWriteAvailable() {
579200
+ return this.tools.has("todo_write");
579201
+ }
579202
+ _availablePreferredTools(candidates) {
579203
+ const available = candidates.filter((name10) => this.tools.has(name10));
579204
+ if (available.length > 0)
579205
+ return available;
579206
+ return this.tools.has("task_complete") ? ["task_complete"] : [];
579207
+ }
579066
579208
  getOrCreateWorkboard() {
579067
579209
  const goal = this._taskState.originalGoal || this._taskState.goal || "";
579068
579210
  if (this._workboard) {
@@ -579318,7 +579460,7 @@ ${parts.join("\n")}
579318
579460
  cardId: integration.id,
579319
579461
  title: integration.title,
579320
579462
  reason: "implementation evidence exists; exercise the changed runtime path before more discovery",
579321
- preferredTools: ["shell"]
579463
+ preferredTools: this._availablePreferredTools(["shell"])
579322
579464
  };
579323
579465
  }
579324
579466
  if (hasVerificationEvidence && verification) {
@@ -579326,7 +579468,10 @@ ${parts.join("\n")}
579326
579468
  cardId: verification.id,
579327
579469
  title: verification.title,
579328
579470
  reason: "verification evidence exists; reconcile outcome, todos, and blockers",
579329
- preferredTools: ["todo_write", "task_complete"]
579471
+ preferredTools: this._availablePreferredTools([
579472
+ "todo_write",
579473
+ "task_complete"
579474
+ ])
579330
579475
  };
579331
579476
  }
579332
579477
  if (discoveryEvidence > 0 && !hasImplementationEvidence && implementation2) {
@@ -579334,7 +579479,12 @@ ${parts.join("\n")}
579334
579479
  cardId: implementation2.id,
579335
579480
  title: implementation2.title,
579336
579481
  reason: "discovery evidence is already present; land the smallest concrete repair",
579337
- preferredTools: ["file_write", "file_edit", "batch_edit", "file_patch"]
579482
+ preferredTools: this._availablePreferredTools([
579483
+ "file_patch",
579484
+ "batch_edit",
579485
+ "file_edit",
579486
+ "file_write"
579487
+ ])
579338
579488
  };
579339
579489
  }
579340
579490
  const active = snapshot.cards.find((card) => card.status === "in_progress") ?? snapshot.cards.find((card) => card.status === "needs_changes") ?? snapshot.cards.find((card) => card.status === "open") ?? discovery;
@@ -579344,13 +579494,24 @@ ${parts.join("\n")}
579344
579494
  cardId: active.id,
579345
579495
  title: active.title,
579346
579496
  reason: active.id === "discover-current-state" ? "no authoritative target evidence is recorded yet" : "workboard card is the next unresolved card",
579347
- preferredTools: active.id === "discover-current-state" ? ["file_read", "grep_search", "list_directory", "shell"] : ["file_write", "file_edit", "batch_edit", "file_patch", "shell"]
579497
+ preferredTools: active.id === "discover-current-state" ? this._availablePreferredTools([
579498
+ "file_read",
579499
+ "grep_search",
579500
+ "list_directory",
579501
+ "shell"
579502
+ ]) : this._availablePreferredTools([
579503
+ "file_patch",
579504
+ "batch_edit",
579505
+ "file_edit",
579506
+ "file_write",
579507
+ "shell"
579508
+ ])
579348
579509
  };
579349
579510
  }
579350
579511
  _focusToolsForRequiredAction(action) {
579351
579512
  switch (action) {
579352
579513
  case "update_todos":
579353
- return "todo_write with a changed plan/status, or a real file edit if that is the active work";
579514
+ return this._todoWriteAvailable() ? "todo_write with a changed plan/status, or a real file edit if that is the active work" : "todo_write is unavailable in this tool surface; continue with the intended substantive action, or task_complete with evidence if done";
579354
579515
  case "read_authoritative_target":
579355
579516
  return "use cached evidence or one authoritative file_read/read-only shell, then act";
579356
579517
  case "use_cached_evidence":
@@ -579358,7 +579519,7 @@ ${parts.join("\n")}
579358
579519
  case "disambiguate_edit_match":
579359
579520
  return "file_edit or batch_edit with replace_all=true for identical global changes, or with a more unique old_string copied from current file context";
579360
579521
  case "edit_different_target":
579361
- return "file_write, file_edit, batch_edit, or file_patch on a different supported target";
579522
+ return "file_patch, batch_edit, file_edit, or hash-guarded file_write on a different supported target";
579362
579523
  case "run_verification":
579363
579524
  return "shell running the verification/runtime command";
579364
579525
  case "report_blocked":
@@ -579370,6 +579531,25 @@ ${parts.join("\n")}
579370
579531
  }
579371
579532
  _renderNextActionContract(turn) {
579372
579533
  const lines = ["[NEXT ACTION CONTRACT]", `turn=${turn}`];
579534
+ const ts = this._taskState;
579535
+ const goal = (ts.originalGoal || ts.goal || "").replace(/\s+/g, " ").trim();
579536
+ if (goal)
579537
+ lines.push(`goal_anchor=${goal.slice(0, 360)}`);
579538
+ if (ts.currentStep) {
579539
+ lines.push(`current_focus=${ts.currentStep.replace(/\s+/g, " ").slice(0, 220)}`);
579540
+ }
579541
+ if (ts.nextAction) {
579542
+ lines.push(`declared_next_action=${ts.nextAction.replace(/\s+/g, " ").slice(0, 220)}`);
579543
+ }
579544
+ lines.push(`progress_anchor=completed_steps:${ts.completedSteps.length} failed_approaches:${ts.failedApproaches.length} modified_files:${ts.modifiedFiles.size} tool_calls:${ts.toolCallCount}`);
579545
+ if (ts.completedSteps.length > 0) {
579546
+ lines.push(`recent_completed=${ts.completedSteps.slice(-3).join(" | ").replace(/\s+/g, " ").slice(0, 420)}`);
579547
+ }
579548
+ if (ts.failedApproaches.length > 0) {
579549
+ lines.push(`recent_failed_approaches=${ts.failedApproaches.slice(-3).join(" | ").replace(/\s+/g, " ").slice(0, 420)}`);
579550
+ }
579551
+ lines.push("tool_call_contract=Each tool call must include action_reason {task_anchor,intent,evidence,expected_result,scope}; compact public facts only, no hidden chain-of-thought.");
579552
+ 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.`);
579373
579553
  const focus = this._focusSupervisor?.snapshot().directive ?? null;
579374
579554
  if (focus) {
579375
579555
  lines.push(`focus_required_next_action=${focus.requiredNextAction}`);
@@ -579385,7 +579565,9 @@ ${parts.join("\n")}
579385
579565
  const integration = byId.get("integrate-and-run");
579386
579566
  const verification = byId.get("verify-observed-outcome");
579387
579567
  lines.push(`workboard_current_card=${action.cardId} (${action.title})`);
579388
- lines.push(`workboard_preferred_tools=${action.preferredTools.join(", ")}`);
579568
+ if (action.preferredTools.length > 0) {
579569
+ lines.push(`workboard_preferred_tools=${action.preferredTools.join(", ")}`);
579570
+ }
579389
579571
  lines.push(`workboard_reason=${action.reason}`);
579390
579572
  lines.push(`workboard_evidence_counts=discover:${this._workboardEvidenceCount(discovery)} implement:${this._workboardEvidenceCount(implementation2)} integrate:${this._workboardEvidenceCount(integration)} verify:${this._workboardEvidenceCount(verification)}`);
579391
579573
  if (this._workboardHasEditEvidence(implementation2)) {
@@ -579411,7 +579593,7 @@ ${parts.join("\n")}
579411
579593
  }
579412
579594
  if (lines.length === 2)
579413
579595
  return null;
579414
- lines.push("not_progress=repeat unchanged todo_write; repeat broad discovery already represented in this frame; keep discovery active after a real mutation");
579596
+ lines.push(this._todoWriteAvailable() ? "not_progress=repeat unchanged todo_write; repeat broad discovery already represented in this frame; keep discovery active after a real mutation" : "not_progress=repeat broad discovery already represented in this frame; keep discovery active after a real mutation");
579415
579597
  return lines.join("\n");
579416
579598
  }
579417
579599
  _workboardTargetCardId(snapshot, toolName, result, meta = {}) {
@@ -580140,10 +580322,23 @@ ${parts.join("\n")}
580140
580322
  const pressureCue = pressureCheck(task);
580141
580323
  const rawPrompt = getSystemPromptForTier(this.options.modelTier);
580142
580324
  const basePrompt = rawPrompt + pressureCue;
580325
+ const todoBatchGuidance = this._todoWriteAvailable() ? " Use todo_write between batches to mark progress." : "";
580143
580326
  const _BATCH_GUIDANCE = {
580144
- small: "\n\n## Response batching\n\nEmit AT MOST 2 tool calls per response. After observing their results, plan the next 2 in your following response. Smaller batches let the orchestrator deliver cache/failure/progress signals to you between actions. Tool calls beyond the cap are dropped. Use todo_write between batches to mark progress.",
580145
- medium: "\n\n## Response batching\n\nEmit AT MOST 4 tool calls per response. After observing their results, plan the next batch in your following response. Smaller batches let the orchestrator deliver cache/failure/progress signals to you between actions. Tool calls beyond the cap are dropped. Use todo_write between batches to mark progress.",
580146
- large: "\n\n## Response batching\n\nEmit AT MOST 6 tool calls per response. Smaller batches receive better feedback (cache/failure/progress signals between actions). Tool calls beyond the cap are dropped. Use todo_write between batches."
580327
+ small: `
580328
+
580329
+ ## Response batching
580330
+
580331
+ Emit AT MOST 2 tool calls per response. After observing their results, plan the next 2 in your following response. Smaller batches let the orchestrator deliver cache/failure/progress signals to you between actions. Tool calls beyond the cap are dropped.${todoBatchGuidance}`,
580332
+ medium: `
580333
+
580334
+ ## Response batching
580335
+
580336
+ Emit AT MOST 4 tool calls per response. After observing their results, plan the next batch in your following response. Smaller batches let the orchestrator deliver cache/failure/progress signals to you between actions. Tool calls beyond the cap are dropped.${todoBatchGuidance}`,
580337
+ large: `
580338
+
580339
+ ## Response batching
580340
+
580341
+ Emit AT MOST 6 tool calls per response. Smaller batches receive better feedback (cache/failure/progress signals between actions). Tool calls beyond the cap are dropped.${todoBatchGuidance}`
580147
580342
  };
580148
580343
  const batchGuidance = _BATCH_GUIDANCE[this.options.modelTier ?? "large"] ?? _BATCH_GUIDANCE.large;
580149
580344
  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.";
@@ -581380,6 +581575,8 @@ ${result.output ?? ""}`;
581380
581575
  getOpenTodoItems() {
581381
581576
  if (this.options.disableTodoCompletionGuard)
581382
581577
  return [];
581578
+ if (!this._todoWriteAvailable())
581579
+ return [];
581383
581580
  const todos = this.readSessionTodos();
581384
581581
  if (!todos || todos.length === 0)
581385
581582
  return [];
@@ -582370,6 +582567,8 @@ ${_checks}`
582370
582567
  * Mutates `_lastTodoReminderTurn` when a reminder is produced.
582371
582568
  */
582372
582569
  getTodoReminderContent(currentTurn) {
582570
+ if (!this._todoWriteAvailable())
582571
+ return null;
582373
582572
  const todos = this.readSessionTodos();
582374
582573
  const result = computeTodoReminder({
582375
582574
  currentTurn,
@@ -582730,6 +582929,8 @@ ${contentPreview}
582730
582929
  * Recommends marking progress before further work. Returns null otherwise.
582731
582930
  */
582732
582931
  _renderProgressNudgeBlock(turn) {
582932
+ if (!this._todoWriteAvailable())
582933
+ return null;
582733
582934
  const n2 = this._writesSinceLastTodoWrite;
582734
582935
  if (n2 < 4)
582735
582936
  return null;
@@ -583343,11 +583544,14 @@ ${contentPreview}
583343
583544
  ].join("\n");
583344
583545
  }
583345
583546
  if (latest.tool === "todo_write") {
583547
+ const raw2 = `${latest.error || ""}
583548
+ ${latest.output || ""}`;
583549
+ const unavailable = /Unknown tool/i.test(raw2);
583346
583550
  return [
583347
583551
  `[PLANNING TOOL RECOVERY]`,
583348
583552
  `Last failing tool: todo_write (turn ${latest.turn})`,
583349
- `A checklist write failure is bookkeeping/argument transport, not evidence that the target codebase is broken.`,
583350
- `Repair the todo_write payload if you still need the visible plan, or continue with the intended first substantive task using the plan content you already attempted to record.`,
583553
+ unavailable ? `todo_write is not registered in this tool surface. Do not retry it.` : `A checklist write failure is bookkeeping/argument transport, not evidence that the target codebase is broken.`,
583554
+ unavailable ? `Use the attempted checklist only as private state; continue with available substantive tools or task_complete with evidence.` : `Repair the todo_write payload if you still need the visible plan, or continue with the intended first substantive task using the plan content you already attempted to record.`,
583351
583555
  `Do not inspect or edit project files solely because todo_write rejected its arguments.`
583352
583556
  ].join("\n");
583353
583557
  }
@@ -583445,7 +583649,7 @@ ${latest.output || ""}`.trim();
583445
583649
  lines.push(`... +${ordered.length - visible.length} more todo node(s)`);
583446
583650
  }
583447
583651
  lines.push("decomposition_contract=Use stable ids plus parentId for subtasks. If the active item has multiple remaining actions, rewrite it as a parent with concrete child leaf todos; work leaf-first and do not complete a parent before all children are complete with evidence.");
583448
- lines.push(`(turn ${turn} — call todo_write ONLY to update state, never to re-read this plan)`);
583652
+ lines.push(this._todoWriteAvailable() ? `(turn ${turn} — call todo_write ONLY to update state, never to re-read this plan)` : `(turn ${turn} — todo_write is unavailable in this tool surface; use this plan as read-only state)`);
583449
583653
  return lines.join("\n");
583450
583654
  } catch {
583451
583655
  return null;
@@ -583699,12 +583903,13 @@ ${sections.join("\n")}` : sections.join("\n");
583699
583903
  */
583700
583904
  _buildRepeatGateBlock(toolName, args, hits) {
583701
583905
  const argPreview = JSON.stringify(args ?? {}).slice(0, 200);
583906
+ const progressStep = this._todoWriteAvailable() ? ` 1. Call todo_write — decompose the remaining work into concrete, verifiable steps, mark the current step in_progress, and mark anything already done as completed.` : ` 1. Re-anchor on the active evidence and name the concrete next action; do not call unavailable planning tools.`;
583702
583907
  return [
583703
583908
  `[REPEAT GATE] Exact repeat #${hits} of ${toolName}(${argPreview}).`,
583704
583909
  `You ALREADY have this result — re-reading identical arguments cannot produce new information, so this call was NOT executed.`,
583705
583910
  ``,
583706
583911
  `You are looping because progress tracking has drifted. Before any further redundant calls you MUST:`,
583707
- ` 1. Call todo_write — decompose the remaining work into concrete, verifiable steps, mark the current step in_progress, and mark anything already done as completed.`,
583912
+ progressStep,
583708
583913
  ` 2. Then take a CONCRETE action on the evidence you already have: edit/write the implicated file, run a verification command, or call task_complete with evidence.`,
583709
583914
  ``,
583710
583915
  `Do NOT repeat this exact call. If you believe state changed, perform the concrete change first, then retry with distinct evidence.`
@@ -583721,7 +583926,7 @@ ${sections.join("\n")}` : sections.join("\n");
583721
583926
  "This call was not executed again, and the cached payload was not re-emitted because repeating it bloats the model context.",
583722
583927
  summary,
583723
583928
  "",
583724
- "Required next action: use the active evidence frame/tool cache and take a concrete next step: edit/write, run verification, update todos with changed state, or task_complete with evidence.",
583929
+ this._todoWriteAvailable() ? "Required next action: use the active evidence frame/tool cache and take a concrete next step: edit/write, run verification, update todos with changed state, or task_complete with evidence." : "Required next action: use the active evidence frame/tool cache and take a concrete next step: edit/write, run verification, or task_complete with evidence.",
583725
583930
  "If you need different information, use a distinct offset/limit/query. For broad multi-file discovery, use file_explore, grep_search, fanout_explore, or full_sub_agent instead of repeating this call."
583726
583931
  ].join("\n");
583727
583932
  }
@@ -584840,7 +585045,7 @@ ${blob}
584840
585045
  if (seen.has(value2))
584841
585046
  return "#cycle";
584842
585047
  seen.add(value2);
584843
- const entries = Object.entries(value2).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${this._canonicalArgValue(v, seen)}`);
585048
+ 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)}`);
584844
585049
  return `#object:{${entries.join(",")}}`;
584845
585050
  }
584846
585051
  return String(value2);
@@ -587310,7 +587515,7 @@ ${_staleSamples.join("\n")}` : ``,
587310
587515
  ``,
587311
587516
  ` (c) REPLACE_ALL: If you want a global rename, set replace_all=true and the uniqueness check is bypassed.`,
587312
587517
  ``,
587313
- ` (d) FILE_WRITE: If the changes are extensive, rewrite the whole file with file_write instead of patching it. Faster than 6 failed edits.`,
587518
+ ` (d) CONSTRAINED PATCH: If the exact replacement is too brittle, use file_patch with expected_old_content or batch_edit/file_edit against fresh current text. Use file_write only for new files, dry-run proposals, or deliberate hash-guarded full_file_rewrite actions.`,
587314
587519
  ``,
587315
587520
  `Do NOT in your next response: call file_edit or batch_edit on ${_efWorstPath} again with another guess at old_string.`
587316
587521
  ].join("\n")
@@ -587557,7 +587762,7 @@ ${_staleSamples.join("\n")}` : ``,
587557
587762
  }
587558
587763
  }
587559
587764
  const turnTier = this.options.modelTier ?? "large";
587560
- if (turn === 0 && !this.options.disableTodoPlanningNudges && (turnTier === "small" || turnTier === "medium")) {
587765
+ if (turn === 0 && !this.options.disableTodoPlanningNudges && this._todoWriteAvailable() && (turnTier === "small" || turnTier === "medium")) {
587561
587766
  const goal = this._taskState.goal || "";
587562
587767
  const substantiveGoal = goal.replace(/\b(?:then\s+)?call\s+task_complete\b[^.?!;]*/gi, "").replace(/\b(?:observe|report|summarize|finish|complete)\b[^.?!;]*/gi, "");
587563
587768
  const wordCount2 = substantiveGoal.split(/\s+/).filter(Boolean).length;
@@ -587670,7 +587875,7 @@ ${top.map((t2) => `- ${t2.name}: ${t2.desc}`).join("\n")}`);
587670
587875
  const isReadTask = /\bread\b|\bshow\b|\btell me\b|\bwhat is\b/i.test(taskGoal);
587671
587876
  const hints = [];
587672
587877
  if (isSimpleTask) {
587673
- hints.push("This is a simple task — if it needs only ONE substantive tool call, skip todo_write and call the tool directly, then task_complete. Do not count reporting, observing output, or task_complete as planning steps. If it needs 2+ substantive work steps, use todo_write to plan.");
587878
+ hints.push(this._todoWriteAvailable() ? "This is a simple task — if it needs only ONE substantive tool call, skip todo_write and call the tool directly, then task_complete. Do not count reporting, observing output, or task_complete as planning steps. If it needs 2+ substantive work steps, use todo_write to plan." : "This is a simple task — if it needs only ONE substantive tool call, call the tool directly, then task_complete. Do not count reporting, observing output, or task_complete as planning steps.");
587674
587879
  }
587675
587880
  if (isSearchTask) {
587676
587881
  hints.push("SEARCH STRATEGY: Use grep_search to find what you need FIRST, THEN file_read only the specific file and lines. Do NOT read entire files hoping to find something.");
@@ -588406,7 +588611,7 @@ ${memoryLines.join("\n")}`
588406
588611
  `Best practice for this model tier: emit at most ${_responseCap} tool calls per turn.`,
588407
588612
  `Observe the results of the first ${_responseCap}, then plan the next batch in the NEXT turn.`,
588408
588613
  `Smaller batches let cache/dedup/progress-gate/failure signals reach you BEFORE you commit to more work.`,
588409
- `Use todo_write between batches to track which items are done.`
588614
+ this._todoWriteAvailable() ? `Use todo_write between batches to track which items are done.` : `Track progress internally between batches; do not call unavailable planning tools.`
588410
588615
  ].join("\n")
588411
588616
  });
588412
588617
  }
@@ -588414,6 +588619,7 @@ ${memoryLines.join("\n")}`
588414
588619
  const executeSingle = async (tc) => {
588415
588620
  if (this.aborted)
588416
588621
  return null;
588622
+ const actionReasonForAdvisory = this._extractToolActionReason(tc.arguments ?? {});
588417
588623
  const cohortKey = this.buildArgCohortKey(tc.name, tc.arguments);
588418
588624
  const cohort = this._argCohorts.get(cohortKey);
588419
588625
  if (cohort && cohort.failure >= 3 && cohort.success === 0) {
@@ -588746,7 +588952,7 @@ Corrective action: try a different approach first: read relevant files, adjust a
588746
588952
  turn,
588747
588953
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
588748
588954
  });
588749
- const budgetMsg = `[BUDGET EXHAUSTED] You have used all ${toolBudgets[tc.name]} allowed ${tc.name} calls for the current phase. You ALREADY have enough information from previous calls. DO NOT try to call ${tc.name} again — it will be blocked. If your todo list shows more phases pending: mark the current phase completed via todo_write so a new budget allowance kicks in. If all phases are done: call task_complete with your final summary.`;
588955
+ const budgetMsg = `[BUDGET EXHAUSTED] You have used all ${toolBudgets[tc.name]} allowed ${tc.name} calls for the current phase. You ALREADY have enough information from previous calls. DO NOT try to call ${tc.name} again — it will be blocked. ` + (this._todoWriteAvailable() ? `If your todo list shows more phases pending: mark the current phase completed via todo_write so a new budget allowance kicks in. ` : `If more phases remain, continue with a different available tool instead of repeating the exhausted one. `) + `If all phases are done: call task_complete with your final summary.`;
588750
588956
  this.emit({
588751
588957
  type: "tool_result",
588752
588958
  toolName: tc.name,
@@ -588805,6 +589011,7 @@ Read the current file if needed, make a different concrete edit, or move to veri
588805
589011
  turn,
588806
589012
  toolName: tc.name,
588807
589013
  args: tc.arguments ?? {},
589014
+ actionReason: actionReasonForAdvisory,
588808
589015
  fingerprint: toolFingerprint,
588809
589016
  isReadLike: false,
588810
589017
  stalePreflightMessage: staleEditBlock,
@@ -588870,6 +589077,7 @@ Read the current file if needed, make a different concrete edit, or move to veri
588870
589077
  turn,
588871
589078
  toolName: tc.name,
588872
589079
  args: tc.arguments ?? {},
589080
+ actionReason: actionReasonForAdvisory,
588873
589081
  fingerprint: toolFingerprint,
588874
589082
  isReadLike: false,
588875
589083
  stalePreflightMessage: staleRewriteBlock,
@@ -588935,6 +589143,7 @@ Read the current file if needed, make a different concrete edit, or move to veri
588935
589143
  turn,
588936
589144
  toolName: tc.name,
588937
589145
  args: tc.arguments ?? {},
589146
+ actionReason: actionReasonForAdvisory,
588938
589147
  fingerprint: toolFingerprint,
588939
589148
  isReadLike: false,
588940
589149
  stalePreflightMessage: editReversalBlock,
@@ -589066,7 +589275,11 @@ Read the current file if needed, make a different concrete edit, or move to veri
589066
589275
  decision: "pass",
589067
589276
  reason: "adversary critic disabled for isolated evaluation"
589068
589277
  } : evaluate2({
589069
- proposedCall: { tool: tc.name, args: tc.arguments ?? {} },
589278
+ proposedCall: {
589279
+ tool: tc.name,
589280
+ args: tc.arguments ?? {},
589281
+ actionReason: actionReasonForAdvisory ?? void 0
589282
+ },
589070
589283
  fingerprint: toolFingerprint,
589071
589284
  isReadLike,
589072
589285
  recentToolResults,
@@ -589115,6 +589328,7 @@ Read the current file if needed, make a different concrete edit, or move to veri
589115
589328
  tool: tc.name,
589116
589329
  target: _target,
589117
589330
  count: criticDecision.hitNumber,
589331
+ actionReason: actionReasonForAdvisory ?? void 0,
589118
589332
  alreadyHave: _existingFp?.result
589119
589333
  }
589120
589334
  });
@@ -589196,10 +589410,12 @@ ${cachedResult}`,
589196
589410
  }
589197
589411
  const focusCachedEntry = recentToolResults.get(toolFingerprint);
589198
589412
  const focusDuplicateHits = criticDecision.decision === "guidance" ? criticDecision.hitNumber : dedupHitCount.get(toolFingerprint) ?? 0;
589413
+ const usesAuthoritativeTargetEvidence = this._toolCallUsesFreshFileReadEvidence(tc.name, tc.arguments ?? {});
589199
589414
  const focusDecision = this._focusSupervisor?.evaluateProposedCall({
589200
589415
  turn,
589201
589416
  toolName: tc.name,
589202
589417
  args: tc.arguments ?? {},
589418
+ actionReason: actionReasonForAdvisory,
589203
589419
  completionStatus: completionStatusFromTaskCompleteArgs(tc.arguments),
589204
589420
  fingerprint: toolFingerprint,
589205
589421
  isReadLike,
@@ -589207,7 +589423,8 @@ ${cachedResult}`,
589207
589423
  cachedResult: focusCachedEntry?.result,
589208
589424
  cachedResultFailed: tc.name === "shell" && typeof focusCachedEntry?.result === "string" && focusCachedEntry.result.includes("status: failure"),
589209
589425
  duplicateHitCount: focusDuplicateHits,
589210
- context: this._buildFocusContextSnapshot()
589426
+ context: this._buildFocusContextSnapshot(),
589427
+ usesAuthoritativeTargetEvidence
589211
589428
  });
589212
589429
  if (focusDecision && focusDecision.kind !== "pass") {
589213
589430
  this._emitFocusSupervisorEvent({
@@ -589247,6 +589464,7 @@ ${cachedResult}`,
589247
589464
  const tool = resolvedTool?.tool;
589248
589465
  let result;
589249
589466
  let runtimeSystemGuidance = null;
589467
+ let toolActionReason = actionReasonForAdvisory;
589250
589468
  if (repeatShortCircuit) {
589251
589469
  result = repeatShortCircuit;
589252
589470
  } else if (tc.arguments && "_raw" in tc.arguments) {
@@ -589262,7 +589480,14 @@ ${cachedResult}`,
589262
589480
  error: this.unknownToolError(tc.name)
589263
589481
  };
589264
589482
  } else {
589265
- let validationError = this._validateFreshExpectedHashesForEdit(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
589483
+ let validationError = this._validateToolActionReason(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
589484
+ if (!validationError) {
589485
+ tc.arguments = this._stripToolActionReasonArgs(tc.arguments ?? {});
589486
+ validationError = this._validateFileWriteOverwriteContract(resolvedTool?.name ?? tc.name, tc.arguments ?? {}, toolActionReason);
589487
+ }
589488
+ if (!validationError) {
589489
+ validationError = this._validateFreshExpectedHashesForEdit(resolvedTool?.name ?? tc.name, tc.arguments ?? {});
589490
+ }
589266
589491
  if (!validationError && tool.inputSchema) {
589267
589492
  const parseResult = tool.inputSchema.safeParse(tc.arguments);
589268
589493
  if (!parseResult.success) {
@@ -590413,14 +590638,15 @@ Respond with EXACTLY this structure before your next tool call:
590413
590638
  }
590414
590639
  if (!_prior && !this._opaqueErrorHintInjected.has(_refStem) && _entry.subject == null && (categorizeError(_refErr) === "unknown" || _refErr.length > 100)) {
590415
590640
  this._opaqueErrorHintInjected.add(_refStem);
590641
+ const todoWriteUnavailable = tc.name === "todo_write" && /Unknown tool/i.test(_entry.wentWrong);
590416
590642
  const opaqueMessage = tc.name === "todo_write" ? [
590417
590643
  `[PLANNING TOOL RECOVERY — todo_write failed before updating the visible checklist.]`,
590418
590644
  ``,
590419
590645
  `Tool: ${tc.name}`,
590420
590646
  `Error: "${_entry.wentWrong}"`,
590421
590647
  ``,
590422
- `This is a recoverable planning-tool argument issue, not a local code or dependency failure.`,
590423
- `Correct the todo_write payload if a visible checklist is still needed; otherwise continue with the first substantive task from the attempted plan.`,
590648
+ todoWriteUnavailable ? `todo_write is not registered in this tool surface. Do not retry it; continue using the available tools and call task_complete with evidence when done.` : `This is a recoverable planning-tool argument issue, not a local code or dependency failure.`,
590649
+ todoWriteUnavailable ? `Use the attempted checklist only as private state; do not let missing bookkeeping block substantive work.` : `Correct the todo_write payload if a visible checklist is still needed; otherwise continue with the first substantive task from the attempted plan.`,
590424
590650
  `Do not pivot to broad shell discovery or code edits solely because todo_write failed.`,
590425
590651
  ``,
590426
590652
  this._renderLocalFailureNudge(turn)
@@ -590733,6 +590959,7 @@ Evidence: ${evidencePreview}`.slice(0, 500);
590733
590959
  turn,
590734
590960
  toolName: tc.name,
590735
590961
  args: tc.arguments ?? {},
590962
+ actionReason: toolActionReason,
590736
590963
  fingerprint: toolFingerprint,
590737
590964
  success: result.success,
590738
590965
  output: result.output ?? result.llmContent ?? "",
@@ -590741,7 +590968,8 @@ Evidence: ${evidencePreview}`.slice(0, 500);
590741
590968
  isReadLike,
590742
590969
  noop: tc.name === "todo_write" ? this._isTodoWriteNoopResult(result) : result.noop,
590743
590970
  alreadyApplied: result.alreadyApplied === true,
590744
- runtimeAuthored: result.runtimeAuthored === true
590971
+ runtimeAuthored: result.runtimeAuthored === true,
590972
+ usedAuthoritativeTargetEvidence: usesAuthoritativeTargetEvidence
590745
590973
  });
590746
590974
  const focusSnapshotAfter = this._focusSupervisor?.snapshot();
590747
590975
  focusAfterToolResult = focusSnapshotAfter ?? void 0;
@@ -590784,6 +591012,7 @@ Evidence: ${evidencePreview}`.slice(0, 500);
590784
591012
  reason: focusDecision.directive.reason,
590785
591013
  requiredNextAction: focusDecision.directive.requiredNextAction
590786
591014
  },
591015
+ actionReason: toolActionReason ?? void 0,
590787
591016
  repeatShortCircuit: Boolean(repeatShortCircuit),
590788
591017
  runtimeAuthored: result.runtimeAuthored === true,
590789
591018
  runtimeGuidance: runtimeSystemGuidance
@@ -593490,6 +593719,16 @@ ${marker}` : marker);
593490
593719
  return { args, patchedCount: 0 };
593491
593720
  return { args: { ...args, expected_hash: fresh.hash }, patchedCount: 1 };
593492
593721
  }
593722
+ if (toolName === "file_write" || toolName === "file_patch") {
593723
+ if (this._normalizeExpectedHashValue(this._rawExpectedHashValue(args))) {
593724
+ return { args, patchedCount: 0 };
593725
+ }
593726
+ const path12 = this.extractPrimaryToolPath(args);
593727
+ const fresh = path12 ? this._freshReadHashForEditPath(path12) : null;
593728
+ if (!fresh)
593729
+ return { args, patchedCount: 0 };
593730
+ return { args: { ...args, expected_hash: fresh.hash }, patchedCount: 1 };
593731
+ }
593493
593732
  if (toolName !== "batch_edit" || !Array.isArray(args["edits"])) {
593494
593733
  return { args, patchedCount: 0 };
593495
593734
  }
@@ -593510,6 +593749,151 @@ ${marker}` : marker);
593510
593749
  });
593511
593750
  return patchedCount > 0 ? { args: { ...args, edits }, patchedCount } : { args, patchedCount: 0 };
593512
593751
  }
593752
+ _toolCallUsesFreshFileReadEvidence(toolName, args) {
593753
+ if (toolName !== "file_write" && toolName !== "file_edit" && toolName !== "file_patch" && toolName !== "batch_edit") {
593754
+ return false;
593755
+ }
593756
+ const matchesFresh = (rec) => {
593757
+ const path12 = typeof rec["path"] === "string" ? rec["path"] : typeof rec["file"] === "string" ? rec["file"] : "";
593758
+ if (!path12)
593759
+ return false;
593760
+ const expectedHash = this._normalizeExpectedHashValue(this._rawExpectedHashValue(rec));
593761
+ if (!expectedHash)
593762
+ return false;
593763
+ const fresh = this._freshReadHashForEditPath(path12);
593764
+ return fresh?.hash === expectedHash;
593765
+ };
593766
+ if (toolName === "batch_edit") {
593767
+ const edits = args["edits"];
593768
+ return Array.isArray(edits) && edits.length > 0 && edits.every((edit) => !!edit && typeof edit === "object" && !Array.isArray(edit) && matchesFresh(edit));
593769
+ }
593770
+ return matchesFresh(args);
593771
+ }
593772
+ _requiresToolActionReason(toolName) {
593773
+ if (process.env["OMNIUS_DISABLE_TOOL_ACTION_REASON"] === "1") {
593774
+ return false;
593775
+ }
593776
+ if ((process.env["VITEST"] || process.env["NODE_ENV"] === "test") && process.env["OMNIUS_REQUIRE_TOOL_ACTION_REASON"] !== "1") {
593777
+ return false;
593778
+ }
593779
+ if (this.options.artifactMode === "internal")
593780
+ return false;
593781
+ if (toolName.startsWith("__"))
593782
+ return false;
593783
+ return true;
593784
+ }
593785
+ _withToolActionReasonSchema(toolName, parameters) {
593786
+ if (!this._requiresToolActionReason(toolName)) {
593787
+ return parameters ?? { type: "object", properties: {} };
593788
+ }
593789
+ const base3 = parameters && typeof parameters === "object" && !Array.isArray(parameters) ? { ...parameters } : { type: "object" };
593790
+ const properties = base3["properties"] && typeof base3["properties"] === "object" && !Array.isArray(base3["properties"]) ? { ...base3["properties"] } : {};
593791
+ properties["action_reason"] = TOOL_ACTION_REASON_SCHEMA;
593792
+ const required = Array.isArray(base3["required"]) ? base3["required"].map(String) : [];
593793
+ return {
593794
+ ...base3,
593795
+ type: "object",
593796
+ properties,
593797
+ required: [.../* @__PURE__ */ new Set([...required, "action_reason"])]
593798
+ };
593799
+ }
593800
+ _extractToolActionReason(args) {
593801
+ const raw = args["action_reason"] ?? args["actionReason"] ?? args["justification"];
593802
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
593803
+ return null;
593804
+ const rec = raw;
593805
+ const reason = {
593806
+ task_anchor: String(rec["task_anchor"] ?? rec["taskAnchor"] ?? "").trim(),
593807
+ intent: String(rec["intent"] ?? "").trim(),
593808
+ evidence: String(rec["evidence"] ?? "").trim(),
593809
+ expected_result: String(rec["expected_result"] ?? rec["expectedResult"] ?? "").trim(),
593810
+ scope: String(rec["scope"] ?? "").trim()
593811
+ };
593812
+ return reason;
593813
+ }
593814
+ _validateToolActionReason(toolName, args) {
593815
+ if (!this._requiresToolActionReason(toolName))
593816
+ return null;
593817
+ const raw = args["action_reason"] ?? args["actionReason"] ?? args["justification"];
593818
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
593819
+ return [
593820
+ "[TOOL ACTION REASON CONTRACT]",
593821
+ `Every tool call must include action_reason: { task_anchor, intent, evidence, expected_result, scope }.`,
593822
+ `Use compact public facts only; do not include hidden chain-of-thought.`,
593823
+ `scope must be one of: ${Array.from(TOOL_ACTION_REASON_SCOPES).join(", ")}.`
593824
+ ].join("\n");
593825
+ }
593826
+ const reason = this._extractToolActionReason(args);
593827
+ if (!reason) {
593828
+ return "[TOOL ACTION REASON CONTRACT] action_reason must be an object with compact public task-state fields.";
593829
+ }
593830
+ const missing = [
593831
+ "task_anchor",
593832
+ "intent",
593833
+ "evidence",
593834
+ "expected_result",
593835
+ "scope"
593836
+ ].filter((key) => reason[key].length < 3);
593837
+ if (missing.length > 0) {
593838
+ return `[TOOL ACTION REASON CONTRACT] action_reason missing non-empty field(s): ${missing.join(", ")}.`;
593839
+ }
593840
+ if (!TOOL_ACTION_REASON_SCOPES.has(reason.scope)) {
593841
+ return `[TOOL ACTION REASON CONTRACT] action_reason.scope="${reason.scope}" is invalid. Use one of: ${Array.from(TOOL_ACTION_REASON_SCOPES).join(", ")}.`;
593842
+ }
593843
+ return null;
593844
+ }
593845
+ _stripToolActionReasonArgs(args) {
593846
+ let changed = false;
593847
+ const next = {};
593848
+ for (const [key, value2] of Object.entries(args)) {
593849
+ if (TOOL_ACTION_REASON_KEYS.has(key)) {
593850
+ changed = true;
593851
+ continue;
593852
+ }
593853
+ next[key] = value2;
593854
+ }
593855
+ return changed ? next : args;
593856
+ }
593857
+ _validateFileWriteOverwriteContract(toolName, args, actionReason) {
593858
+ if (toolName !== "file_write")
593859
+ return null;
593860
+ if (process.env["OMNIUS_ALLOW_UNVERIFIED_FILE_WRITE_OVERWRITE"] === "1") {
593861
+ return null;
593862
+ }
593863
+ if (args["dry_run"] === true || args["dryRun"] === true)
593864
+ return null;
593865
+ const path12 = this.extractPrimaryToolPath(args);
593866
+ if (!path12)
593867
+ return null;
593868
+ let exists2 = false;
593869
+ try {
593870
+ const st = _fsStatSync(this._absoluteToolPath(path12), {
593871
+ throwIfNoEntry: false
593872
+ });
593873
+ exists2 = !!st && st.isFile();
593874
+ } catch {
593875
+ exists2 = false;
593876
+ }
593877
+ if (!exists2)
593878
+ return null;
593879
+ if (actionReason && actionReason.scope !== "full_file_rewrite") {
593880
+ return [
593881
+ "[FULL FILE REWRITE CONTRACT]",
593882
+ `file_write targets an existing file: ${path12}.`,
593883
+ `For constrained repairs, use file_patch, batch_edit, or file_edit instead of rewriting the full file.`,
593884
+ `If a whole-file replacement is truly intended, set action_reason.scope="full_file_rewrite" and use expected_hash from a fresh file_read.`
593885
+ ].join("\n");
593886
+ }
593887
+ if (!this._toolCallUsesFreshFileReadEvidence(toolName, args)) {
593888
+ const fresh = this._freshReadHashForEditPath(path12);
593889
+ return [
593890
+ "[FULL FILE REWRITE CONTRACT]",
593891
+ `file_write would overwrite existing file ${path12}, but it is not anchored to fresh file_read evidence.`,
593892
+ fresh ? `Use expected_hash=${fresh.hash} from the current active evidence, or prefer file_patch/batch_edit for a constrained change.` : `First file_read ${path12}, then prefer file_patch/batch_edit; only retry file_write with expected_hash if a whole-file rewrite is necessary.`
593893
+ ].join("\n");
593894
+ }
593895
+ return null;
593896
+ }
593513
593897
  _validateFreshExpectedHashesForEdit(toolName, args) {
593514
593898
  if (process.env["OMNIUS_ALLOW_UNVERIFIED_OLD_STRING_EDIT"] === "1") {
593515
593899
  return null;
@@ -595478,6 +595862,7 @@ ${trimmedNew}`;
595478
595862
  args = JSON.parse(tc.function.arguments);
595479
595863
  } catch {
595480
595864
  }
595865
+ const actionReason = this._extractToolActionReason(args);
595481
595866
  const argsKey = this._buildExactArgsKey(args);
595482
595867
  const fingerprint = this._buildToolFingerprint(name10, args);
595483
595868
  const prior = this._adversaryToolOutcomes.find((o2) => o2.succeeded && o2.tool === name10 && o2.fingerprint === fingerprint && o2.stateVersion === this._adversaryStateVersion && o2.turn < turn);
@@ -595491,6 +595876,7 @@ ${trimmedNew}`;
595491
595876
  tool: name10,
595492
595877
  target: argsKey.slice(0, 180),
595493
595878
  count: repeatCount,
595879
+ actionReason: actionReason ?? void 0,
595494
595880
  alreadyHave: prior.preview
595495
595881
  }
595496
595882
  }, `possible repeated action ${name10} after prior same-state success`);
@@ -596724,7 +597110,7 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
596724
597110
  function: {
596725
597111
  name: tool.name,
596726
597112
  description: compressDesc ? (desc.split(/\.\s/)[0]?.slice(0, 120) ?? desc.slice(0, 120)) + "." : desc,
596727
- parameters: tool.parameters
597113
+ parameters: this._withToolActionReasonSchema(tool.name, tool.parameters)
596728
597114
  }
596729
597115
  };
596730
597116
  });
@@ -596746,7 +597132,7 @@ Example: ${tool.name}(${JSON.stringify(meta.examples[0].args ?? {})})` : "";
596746
597132
 
596747
597133
  Available tools (${deferred.length}):
596748
597134
  ${catalog}`,
596749
- parameters: {
597135
+ parameters: this._withToolActionReasonSchema("tool_search", {
596750
597136
  type: "object",
596751
597137
  properties: {
596752
597138
  query: {
@@ -596755,7 +597141,7 @@ ${catalog}`,
596755
597141
  }
596756
597142
  },
596757
597143
  required: ["query"]
596758
- }
597144
+ })
596759
597145
  }
596760
597146
  });
596761
597147
  const activatedToolsRef = this._activatedTools;
@@ -596797,7 +597183,7 @@ ${catalog}`,
596797
597183
  lines.push(`Aliases: ${tool.aliases.join(", ")}`);
596798
597184
  }
596799
597185
  lines.push(`${getDesc(tool)}${customToolDetails(tool)}`);
596800
- lines.push(`Parameters: ${JSON.stringify(tool.parameters)}`);
597186
+ lines.push(`Parameters: ${JSON.stringify(this._withToolActionReasonSchema(tool.name, tool.parameters))}`);
596801
597187
  }
596802
597188
  }
596803
597189
  if (alreadyAvailable.length > 0) {
@@ -596849,7 +597235,7 @@ ${catalog}`,
596849
597235
  for (const t2 of matches)
596850
597236
  activatedToolsRef.add(t2.name);
596851
597237
  const result = matches.map((t2) => {
596852
- const paramsStr = JSON.stringify(t2.parameters, null, 2);
597238
+ const paramsStr = JSON.stringify(this._withToolActionReasonSchema(t2.name, t2.parameters), null, 2);
596853
597239
  const aliases = t2.aliases?.length ? `
596854
597240
  Aliases: ${t2.aliases.join(", ")}` : "";
596855
597241
  return `## ${t2.name}${aliases}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.460",
3
+ "version": "1.0.462",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.460",
9
+ "version": "1.0.462",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
@@ -1662,9 +1662,9 @@
1662
1662
  }
1663
1663
  },
1664
1664
  "node_modules/@multiformats/murmur3": {
1665
- "version": "2.2.5",
1666
- "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.5.tgz",
1667
- "integrity": "sha512-M+VwV8hEx5qB8i7b8fljMwZETJsqyLo8RAXA+JAn+QF9NnH46ZWvGErNukJVlxXSR2KQpuOtHIYIzZlbhhdFvw==",
1665
+ "version": "2.2.6",
1666
+ "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-2.2.6.tgz",
1667
+ "integrity": "sha512-SPovNpXC6BQpYkCy2IpN58t4BFx2dnwmo5ppWk3z5/DjD2syV/Na1fAPkBKjk6iualm6qpoTp55cX1UE0jIRlQ==",
1668
1668
  "license": "Apache-2.0 OR MIT",
1669
1669
  "dependencies": {
1670
1670
  "multiformats": "^14.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.460",
3
+ "version": "1.0.462",
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",