@lazyingart/agintiflow 0.20.207 → 0.20.208

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.
Files changed (34) hide show
  1. package/package.json +21 -3
  2. package/scripts/smoke-coding-tools.js +78 -0
  3. package/scripts/smoke-context-budget-recovery.js +4 -0
  4. package/scripts/smoke-dynamic-step-budget.js +173 -0
  5. package/scripts/smoke-integration-retained-durable-common.js +693 -0
  6. package/scripts/smoke-integration-retained-event-ledger-bundle.js +771 -0
  7. package/scripts/smoke-integration-retained-event-ledger.js +991 -0
  8. package/scripts/smoke-integration-retained-file-lock.js +707 -0
  9. package/scripts/smoke-integration-retained-native-session-repository-state.js +1722 -0
  10. package/scripts/smoke-integration-retained-repository-kernel.js +1344 -0
  11. package/scripts/smoke-integration-retained-session-state-store.js +2054 -0
  12. package/scripts/smoke-integration-runtime-repository-contract.js +653 -0
  13. package/scripts/smoke-integration-storage-authority.js +822 -0
  14. package/scripts/smoke-progressive-tool-selection.js +131 -3
  15. package/scripts/smoke-truthful-completion.js +222 -4
  16. package/src/agent-runner.js +450 -16
  17. package/src/integration-authority-error.js +15 -0
  18. package/src/integration-durable-common.js +11 -18
  19. package/src/integration-event-ledger-store.js +1667 -0
  20. package/src/integration-native-executor.js +5 -102
  21. package/src/integration-native-runtime-roots.js +263 -0
  22. package/src/integration-retained-native-session-repository-state.js +2792 -0
  23. package/src/integration-retained-session-state-store.js +1795 -0
  24. package/src/integration-run-registry.js +81 -5
  25. package/src/integration-runtime-authority.js +533 -154
  26. package/src/integration-runtime-repository-contract.js +395 -0
  27. package/src/integration-runtime-repository.js +1503 -0
  28. package/src/integration-storage-authority.js +2615 -0
  29. package/src/model-client.js +9 -7
  30. package/src/progressive-tool-selection.js +13 -3
  31. package/src/scs-controller.js +6 -3
  32. package/src/scs-evidence.js +117 -5
  33. package/src/step-budget-controller.js +4 -1
  34. package/src/tool-contract.js +59 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.207",
3
+ "version": "0.20.208",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -65,6 +65,15 @@
65
65
  "scripts/supervision-ledger.js",
66
66
  "scripts/smoke-supervision-ledger.js",
67
67
  "scripts/smoke-integration-production-mount.js",
68
+ "scripts/smoke-integration-storage-authority.js",
69
+ "scripts/smoke-integration-retained-durable-common.js",
70
+ "scripts/smoke-integration-retained-file-lock.js",
71
+ "scripts/smoke-integration-retained-event-ledger.js",
72
+ "scripts/smoke-integration-retained-event-ledger-bundle.js",
73
+ "scripts/smoke-integration-retained-repository-kernel.js",
74
+ "scripts/smoke-integration-retained-session-state-store.js",
75
+ "scripts/smoke-integration-retained-native-session-repository-state.js",
76
+ "scripts/smoke-integration-runtime-repository-contract.js",
68
77
  "scripts/smoke-auxiliary-tools.js",
69
78
  "scripts/smoke-agentlink.js",
70
79
  "scripts/smoke-auth.js",
@@ -130,7 +139,7 @@
130
139
  "scripts": {
131
140
  "start": "node run.js",
132
141
  "web": "node web.js",
133
- "check": "node --check run.js && node --check web.js && node --check bin/aginti-cli.js && node --check bin/aginti-integration.js && node --check bin/aginti-public-research.js && node --check bin/aginti-safe-chat.js && node --check src/*.js && node --check src/mcp/*.js && node --check public/*.js && node --check scripts/postinstall-webapp.js && node --check scripts/seed-supervised-homework.js && node --check scripts/supervision-ledger.js && node --check scripts/smoke-supervision-ledger.js && node --check scripts/smoke-integration-production-mount.js && node --check scripts/eval-provider-attribution.js && node --check scripts/smoke-agentlink.js && node --check scripts/smoke-execution-policy.js && node --check scripts/smoke-math-rendering.js && node --check scripts/smoke-mcp.js && node --check scripts/smoke-public-research-wrapper.js && node --check scripts/smoke-runtime-core.js && node --check scripts/smoke-safe-chat.js && node --check scripts/smoke-web-ui.js && node --check scripts/smoke-scs-evidence-visibility.js && node --check scripts/fixtures/mcp-stdio-smoke-server.mjs",
142
+ "check": "node --check run.js && node --check web.js && node --check bin/aginti-cli.js && node --check bin/aginti-integration.js && node --check bin/aginti-public-research.js && node --check bin/aginti-safe-chat.js && node --check src/*.js && node --check src/mcp/*.js && node --check public/*.js && node --check scripts/postinstall-webapp.js && node --check scripts/seed-supervised-homework.js && node --check scripts/supervision-ledger.js && node --check scripts/smoke-supervision-ledger.js && node --check scripts/smoke-integration-production-mount.js && node --check scripts/smoke-integration-storage-authority.js && node --check scripts/smoke-integration-retained-durable-common.js && node --check scripts/smoke-integration-retained-file-lock.js && node --check scripts/smoke-integration-retained-event-ledger.js && node --check scripts/smoke-integration-retained-event-ledger-bundle.js && node --check scripts/smoke-integration-retained-repository-kernel.js && node --check scripts/smoke-integration-retained-session-state-store.js && node --check scripts/smoke-integration-retained-native-session-repository-state.js && node --check scripts/smoke-integration-runtime-repository-contract.js && node --check scripts/eval-provider-attribution.js && node --check scripts/smoke-agentlink.js && node --check scripts/smoke-execution-policy.js && node --check scripts/smoke-math-rendering.js && node --check scripts/smoke-mcp.js && node --check scripts/smoke-public-research-wrapper.js && node --check scripts/smoke-runtime-core.js && node --check scripts/smoke-safe-chat.js && node --check scripts/smoke-web-ui.js && node --check scripts/smoke-scs-evidence-visibility.js && node --check scripts/fixtures/mcp-stdio-smoke-server.mjs",
134
143
  "setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
135
144
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
136
145
  "smoke:dynamic-step-budget": "node scripts/smoke-dynamic-step-budget.js",
@@ -186,10 +195,19 @@
186
195
  "supervision:ledger": "node scripts/supervision-ledger.js",
187
196
  "smoke:supervision-ledger": "node scripts/smoke-supervision-ledger.js",
188
197
  "smoke:integration-production-mount": "node scripts/smoke-integration-production-mount.js",
198
+ "smoke:integration-storage-authority": "node scripts/smoke-integration-storage-authority.js",
199
+ "smoke:integration-retained-durable-common": "node scripts/smoke-integration-retained-durable-common.js",
200
+ "smoke:integration-retained-file-lock": "node scripts/smoke-integration-retained-file-lock.js",
201
+ "smoke:integration-retained-event-ledger": "node scripts/smoke-integration-retained-event-ledger.js",
202
+ "smoke:integration-retained-event-ledger-bundle": "node --experimental-test-module-mocks scripts/smoke-integration-retained-event-ledger-bundle.js",
203
+ "smoke:integration-retained-repository-kernel": "node --experimental-test-module-mocks scripts/smoke-integration-retained-repository-kernel.js",
204
+ "smoke:integration-retained-session-state-store": "node --experimental-test-module-mocks scripts/smoke-integration-retained-session-state-store.js",
205
+ "smoke:integration-retained-native-session-repository-state": "node --experimental-test-module-mocks scripts/smoke-integration-retained-native-session-repository-state.js",
206
+ "smoke:integration-runtime-repository-contract": "node scripts/smoke-integration-runtime-repository-contract.js",
189
207
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
190
208
  "publish:env": "node scripts/npm-publish-from-env.js publish --access public",
191
209
  "publish:env:whoami": "node scripts/npm-publish-from-env.js whoami",
192
- "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:writing-specialist-routing && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
210
+ "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-runtime-repository-contract && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:writing-specialist-routing && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
193
211
  "pack:dry-run": "npm pack --dry-run",
194
212
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
195
213
  },
@@ -7,6 +7,7 @@ import {
7
7
  buildModelTimeoutRetryMessages,
8
8
  modelTimeoutRetryRoute,
9
9
  repairModelMessageHistory,
10
+ shouldResetStaticDiscoveryPhase,
10
11
  runAgent,
11
12
  sanitizeToolResult,
12
13
  toolResultForModel,
@@ -18,6 +19,7 @@ import { formatBehaviorContractForPrompt } from "../src/behavior-contract.js";
18
19
  import { resolveRuntimeConfig } from "../src/config.js";
19
20
  import { readCodebaseMap } from "../src/codebase-map.js";
20
21
  import { evaluateCommandPolicy } from "../src/command-policy.js";
22
+ import { shouldReviewToolResult } from "../src/scs-controller.js";
21
23
  import {
22
24
  engineeringGuidanceForTask,
23
25
  recommendedMaxStepsForTask,
@@ -164,6 +166,74 @@ try {
164
166
  interruptedDeepSeekState.messages.at(-1)?.content === "Continue with this new request: /review",
165
167
  "interrupted repair dropped the new user request"
166
168
  );
169
+ const deepSeekCompactionState = {
170
+ goal: "Continue a data repair from retained evidence.",
171
+ plan: "Use the verified source evidence, repair once, then test.",
172
+ stepsCompleted: 4,
173
+ meta: {},
174
+ messages: [
175
+ { role: "system", content: "system" },
176
+ { role: "user", content: "inspect the exact source" },
177
+ {
178
+ role: "assistant",
179
+ content: "",
180
+ reasoning_content: "The source must be read before editing.",
181
+ tool_calls: [
182
+ { id: "deep-read", type: "function", function: { name: "read_file", arguments: '{"path":"analysis.py"}' } },
183
+ ],
184
+ },
185
+ { role: "tool", tool_call_id: "deep-read", content: '{"ok":true,"path":"analysis.py","content":"verified source"}' },
186
+ ],
187
+ };
188
+ const deepSeekCompacted = buildModelTimeoutRetryMessages(
189
+ deepSeekCompactionState,
190
+ { provider: "deepseek", model: "deepseek-v4-pro", contextWindowTokens: 32768 },
191
+ { url: "", title: "No browser page open" },
192
+ 5,
193
+ new Error("synthetic timeout")
194
+ );
195
+ assert(
196
+ !deepSeekCompacted.some((message) => message.role === "assistant" && Array.isArray(message.tool_calls)),
197
+ "DeepSeek compaction synthesized assistant tool calls without original reasoning_content"
198
+ );
199
+ assert(
200
+ deepSeekCompacted.some(
201
+ (message) => message.role === "user" && /Retained runtime tool evidence/.test(message.content) && /analysis\.py/.test(message.content) && /verified source/.test(message.content)
202
+ ),
203
+ "DeepSeek compaction dropped bounded source evidence while removing synthetic tool-call messages"
204
+ );
205
+ assert(
206
+ !shouldReviewToolResult(
207
+ { ok: true, toolName: "read_file", path: "analysis.py" },
208
+ { meta: { toolLoop: { warned: ["old-read"], recent: [{ toolName: "read_file", ok: false }] } } }
209
+ ),
210
+ "SCS scheduled a redundant review for a successful read because an older read failed"
211
+ );
212
+ assert(
213
+ shouldReviewToolResult(
214
+ { ok: false, blocked: true, toolName: "read_file", reason: "exact read blocked" },
215
+ { meta: { toolLoop: { warned: [], recent: [] } } }
216
+ ),
217
+ "SCS stopped reviewing an exact blocked tool result"
218
+ );
219
+ assert(
220
+ !shouldResetStaticDiscoveryPhase({
221
+ ok: true,
222
+ toolName: "run_command",
223
+ args: { command: 'echo "SOURCE"; cat analysis.py; echo "DIFF"; git diff -- analysis.py' },
224
+ commandPolicy: { writesWorkspace: false },
225
+ }),
226
+ "composite read-only shell discovery incorrectly reset the bounded discovery phase"
227
+ );
228
+ assert(
229
+ shouldResetStaticDiscoveryPhase({
230
+ ok: true,
231
+ toolName: "run_command",
232
+ args: { command: "python analysis.py" },
233
+ commandPolicy: { writesWorkspace: true },
234
+ }),
235
+ "a successful workspace-writing command did not reset the discovery phase"
236
+ );
167
237
  const workspaceToolConfig = {
168
238
  commandCwd: workspace,
169
239
  allowFileTools: true,
@@ -1111,6 +1181,14 @@ try {
1111
1181
  localTimeoutRoute.model === "localllm-fast" && localTimeoutRoute.retryTimeoutMs === 90000,
1112
1182
  "LocalLLM timeout retry did not switch to its same-boundary fast route"
1113
1183
  );
1184
+ const defaultLocalTimeoutRoute = modelTimeoutRetryRoute({
1185
+ provider: "localllm",
1186
+ model: "localllm-fast",
1187
+ });
1188
+ assert(
1189
+ defaultLocalTimeoutRoute.timeoutMs === 300000 && defaultLocalTimeoutRoute.retryTimeoutMs === 600000,
1190
+ "LocalLLM default timeout did not allow bounded slow local generation and one longer retry"
1191
+ );
1114
1192
  const artifactTimeoutMessages = buildModelTimeoutRetryMessages(
1115
1193
  {
1116
1194
  meta: {
@@ -163,6 +163,10 @@ const runtimeMessages = buildContextBudgetCompactionMessages(
163
163
  { reason: "test context recovery" }
164
164
  );
165
165
  const runtimeText = runtimeMessages.map((message) => message.content || "").join("\n");
166
+ assert.ok(
167
+ estimateMessageTokens(runtimeMessages) <= 12288,
168
+ "runtime compaction exceeded the bounded LocalLLM retry target"
169
+ );
166
170
  assert.ok(runtimeText.includes(HEAD));
167
171
  assert.ok(runtimeText.includes(TAIL));
168
172
  assert.ok(runtimeText.includes("latest interruption"));
@@ -22,6 +22,8 @@ import {
22
22
  recordExactOutputProgress,
23
23
  recordStaticDiscoveryProgress,
24
24
  rememberCompletedDeepResearch,
25
+ repeatedNoProgressToolBlock,
26
+ repeatedSuccessfulMutationBlock,
25
27
  repeatedStaticToolBlock,
26
28
  shouldResetStaticDiscoveryPhase,
27
29
  } from "../src/agent-runner.js";
@@ -59,6 +61,39 @@ try {
59
61
  assert(normalizeDynamicStepsMode("off") === "off", "dynamic mode off did not normalize");
60
62
  assert(normalizeDynamicStepsMode("always") === "on", "dynamic mode always did not normalize to on");
61
63
  assert(normalizeDynamicStepsMode("smart") === "auto", "dynamic mode smart did not normalize to auto");
64
+ const inheritedBudget = createStepBudgetState(
65
+ { maxSteps: 16, dynamicSteps: "off" },
66
+ {
67
+ stepsCompleted: 0,
68
+ meta: {
69
+ stepBudget: {
70
+ initialMaxSteps: 30,
71
+ currentMaxSteps: 40,
72
+ extensionsUsed: 1,
73
+ lastExtensionStep: 28,
74
+ },
75
+ },
76
+ }
77
+ );
78
+ assert(inheritedBudget.currentMaxSteps === 40, "ordinary resume did not retain its prior expanded budget");
79
+ const explicitBudget = createStepBudgetState(
80
+ { maxSteps: 16, dynamicSteps: "off", resetStepBudget: true },
81
+ {
82
+ stepsCompleted: 0,
83
+ meta: {
84
+ stepBudget: {
85
+ initialMaxSteps: 30,
86
+ currentMaxSteps: 40,
87
+ extensionsUsed: 1,
88
+ lastExtensionStep: 28,
89
+ },
90
+ },
91
+ }
92
+ );
93
+ assert(explicitBudget.initialMaxSteps === 16, "explicit resumed max-steps did not reset the initial budget");
94
+ assert(explicitBudget.currentMaxSteps === 16, "a prior extension overrode explicit resumed max-steps");
95
+ assert(explicitBudget.extensionsUsed === 0, "explicit resumed max-steps retained stale extension usage");
96
+ assert(explicitBudget.resetFromExplicitOverride, "explicit resumed max-steps reset was not recorded");
62
97
  assert(isStaticDiscoveryToolCall("run_command", { command: "ls -la ../Musia" }), "static ls discovery was not classified");
63
98
  assert(isStaticDiscoveryToolCall("read_image", { path: "snapshot.png" }), "image perception was not classified as static discovery");
64
99
  assert(
@@ -97,6 +132,126 @@ try {
97
132
  ) === null,
98
133
  "a bounded continuation read was mistaken for an exact reread"
99
134
  );
135
+ const repeatedProbeArgs = { command: "python -c \"print(100.0)\"" };
136
+ const repeatedProbeSignature = staticToolCallSignature("run_command", repeatedProbeArgs, {
137
+ commandCwd: workspace,
138
+ });
139
+ const repeatedProbeState = {
140
+ meta: {
141
+ toolLoop: {
142
+ stagnationEpoch: 4,
143
+ recent: [
144
+ {
145
+ signature: repeatedProbeSignature,
146
+ toolName: "run_command",
147
+ ok: true,
148
+ blocked: false,
149
+ noProgressProbe: true,
150
+ outcomeFingerprint: "same-output",
151
+ stagnationEpoch: 4,
152
+ },
153
+ {
154
+ signature: repeatedProbeSignature,
155
+ toolName: "run_command",
156
+ ok: true,
157
+ blocked: false,
158
+ noProgressProbe: true,
159
+ outcomeFingerprint: "same-output",
160
+ stagnationEpoch: 4,
161
+ },
162
+ ],
163
+ },
164
+ },
165
+ };
166
+ assert(
167
+ repeatedNoProgressToolBlock(repeatedProbeState, "run_command", repeatedProbeArgs, {
168
+ commandCwd: workspace,
169
+ })?.category === "repeated-no-progress-call",
170
+ "a third identical unchanged shell probe was not blocked"
171
+ );
172
+ assert(
173
+ repeatedNoProgressToolBlock(
174
+ {
175
+ meta: {
176
+ toolLoop: {
177
+ stagnationEpoch: 5,
178
+ recent: repeatedProbeState.meta.toolLoop.recent,
179
+ },
180
+ },
181
+ },
182
+ "run_command",
183
+ repeatedProbeArgs,
184
+ { commandCwd: workspace }
185
+ ) === null,
186
+ "a new mutation epoch did not reopen bounded validation"
187
+ );
188
+ assert(
189
+ repeatedNoProgressToolBlock(
190
+ repeatedProbeState,
191
+ "run_command",
192
+ { command: "python monitor.py --status" },
193
+ { commandCwd: workspace }
194
+ ) === null,
195
+ "an explicit status polling command was incorrectly blocked"
196
+ );
197
+ const repeatedPatchArgs = {
198
+ path: "analysis.py",
199
+ search: "signal = raw_signal",
200
+ replace: "signal = raw_signal - offset",
201
+ searchHash: "search-hash",
202
+ replaceHash: "replace-hash",
203
+ };
204
+ const repeatedPatchSignature = staticToolCallSignature("apply_patch", repeatedPatchArgs, {
205
+ commandCwd: workspace,
206
+ });
207
+ const repeatedPatchState = {
208
+ meta: {
209
+ toolLoop: {
210
+ stagnationEpoch: 7,
211
+ recent: [
212
+ {
213
+ signature: repeatedPatchSignature,
214
+ toolName: "apply_patch",
215
+ ok: true,
216
+ blocked: false,
217
+ successfulMutation: true,
218
+ stagnationEpoch: 7,
219
+ },
220
+ ],
221
+ },
222
+ },
223
+ };
224
+ assert(
225
+ repeatedSuccessfulMutationBlock(repeatedPatchState, "apply_patch", repeatedPatchArgs, {
226
+ commandCwd: workspace,
227
+ })?.category === "repeated-successful-mutation",
228
+ "an exact already-successful patch was not blocked in the same mutation epoch"
229
+ );
230
+ assert(
231
+ repeatedSuccessfulMutationBlock(
232
+ {
233
+ meta: {
234
+ toolLoop: {
235
+ stagnationEpoch: 8,
236
+ recent: repeatedPatchState.meta.toolLoop.recent,
237
+ },
238
+ },
239
+ },
240
+ "apply_patch",
241
+ repeatedPatchArgs,
242
+ { commandCwd: workspace }
243
+ ) === null,
244
+ "a user continuation did not reopen an intentional exact patch"
245
+ );
246
+ assert(
247
+ repeatedSuccessfulMutationBlock(
248
+ repeatedPatchState,
249
+ "apply_patch",
250
+ { ...repeatedPatchArgs, replaceHash: "different-replacement-hash" },
251
+ { commandCwd: workspace }
252
+ ) === null,
253
+ "a materially different patch was mistaken for an exact replay"
254
+ );
100
255
  const completedResearchState = {
101
256
  goal: "Create one cited report",
102
257
  meta: { goalContract: { revision: 1, currentHash: "goal-one" } },
@@ -640,6 +795,24 @@ try {
640
795
  const state = await store.loadState();
641
796
  assert(state.meta?.stepBudget?.extensionsUsed === 1, "dynamic budget state did not persist extension count");
642
797
 
798
+ const resumed = await runAgent({
799
+ ...config,
800
+ goal: "Create notes/explicit-resume-budget.md with one concise line.",
801
+ resume: run.sessionId,
802
+ sessionId: run.sessionId,
803
+ maxSteps: 2,
804
+ dynamicSteps: "off",
805
+ runtimePatch: { maxSteps: 2, dynamicSteps: "off" },
806
+ expectedRuntimeRevision: state.meta?.runtimeConfig?.revision,
807
+ });
808
+ assert(!resumed.stopped, "explicit bounded resume stopped before completing its simple task");
809
+ const resumedEvents = await store.loadEvents();
810
+ const resumedBudget = resumedEvents.filter((event) => event.type === "budget.initialized").at(-1)?.data;
811
+ assert(resumedBudget?.initialMaxSteps === 2, "runAgent did not apply explicit resumed max-steps as the initial budget");
812
+ assert(resumedBudget?.currentMaxSteps === 2, "runAgent inherited an older expanded budget over explicit resumed max-steps");
813
+ assert(resumedBudget?.extensionsUsed === 0, "runAgent retained stale extension usage after an explicit resumed max-steps patch");
814
+ assert(resumedBudget?.resetFromExplicitOverride === true, "runAgent did not record the explicit budget reset boundary");
815
+
643
816
  await fs.rm(tempRoot, { recursive: true, force: true });
644
817
  console.log("smoke-dynamic-step-budget ok");
645
818
  } catch (error) {