@lazyingart/agintiflow 0.20.208 → 0.20.210

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 (61) hide show
  1. package/docs/supervision-campaign-ledger.md +27 -0
  2. package/package.json +13 -3
  3. package/scripts/check-js-syntax.js +38 -0
  4. package/scripts/smoke-canvas-artifacts.js +19 -0
  5. package/scripts/smoke-cli-chat.js +10 -0
  6. package/scripts/smoke-coding-tools.js +59 -0
  7. package/scripts/smoke-deep-research.js +7 -0
  8. package/scripts/smoke-dynamic-step-budget.js +219 -0
  9. package/scripts/smoke-integration-production-mount.js +3 -5
  10. package/scripts/smoke-integration-retained-event-ledger-bundle.js +1 -1
  11. package/scripts/smoke-integration-retained-native-execution-evidence.js +2948 -0
  12. package/scripts/smoke-integration-retained-native-session-repository-state.js +40 -1
  13. package/scripts/smoke-integration-retained-repository-kernel.js +1 -0
  14. package/scripts/smoke-integration-retained-runtime-repository-phase-b.js +1296 -0
  15. package/scripts/smoke-integration-retained-runtime-repository-surface.js +1230 -0
  16. package/scripts/smoke-integration-retained-session-state-store.js +1 -0
  17. package/scripts/smoke-integration-runtime-authority.js +5040 -0
  18. package/scripts/smoke-integration-runtime-repository-contract.js +3 -1
  19. package/scripts/smoke-integration-storage-authority.js +64 -2
  20. package/scripts/smoke-local-failure-recovery.js +497 -0
  21. package/scripts/smoke-model-roles.js +70 -0
  22. package/scripts/smoke-perception-research.js +28 -4
  23. package/scripts/smoke-progressive-tool-selection.js +315 -3
  24. package/scripts/smoke-scs-evidence-visibility.js +22 -0
  25. package/scripts/smoke-session-runtime.js +5 -0
  26. package/scripts/smoke-skills.js +4 -0
  27. package/scripts/smoke-supervision-ledger.js +20 -1
  28. package/scripts/smoke-truthful-completion.js +212 -8
  29. package/scripts/smoke-web-api.js +4 -2
  30. package/scripts/smoke-web-autostart.js +15 -1
  31. package/scripts/supervision-ledger.js +45 -11
  32. package/src/agent-runner.js +2163 -178
  33. package/src/artifact-tunnel.js +48 -7
  34. package/src/config.js +7 -2
  35. package/src/deep-research.js +13 -1
  36. package/src/integration-durable-common.js +1 -0
  37. package/src/integration-native-executor.js +114 -12
  38. package/src/integration-retained-native-execution-evidence.js +869 -0
  39. package/src/integration-retained-native-session-repository-state.js +431 -20
  40. package/src/integration-retained-runtime-repository-surface.js +3753 -0
  41. package/src/integration-retained-session-state-store.js +350 -4
  42. package/src/integration-retained-text-workspace.js +899 -0
  43. package/src/integration-retained-vision-workspace.js +1676 -0
  44. package/src/integration-runtime-authority.js +384 -20
  45. package/src/integration-runtime-process-owner-bootstrap.js +63 -0
  46. package/src/integration-runtime-repository.js +34 -1
  47. package/src/integration-session-persistence.js +259 -8
  48. package/src/integration-session-service.js +4 -4
  49. package/src/integration-storage-authority.js +265 -10
  50. package/src/model-client.js +100 -21
  51. package/src/perception-tools.js +30 -9
  52. package/src/progressive-tool-selection.js +129 -8
  53. package/src/research-routing.js +10 -6
  54. package/src/scs-controller.js +17 -1
  55. package/src/scs-evidence.js +330 -13
  56. package/src/session-runtime.js +2 -1
  57. package/src/session-store.js +124 -2
  58. package/src/skill-library.js +8 -3
  59. package/src/step-budget-controller.js +39 -6
  60. package/src/workspace-tools.js +28 -6
  61. package/web.js +3 -1
@@ -38,6 +38,11 @@ Create, start, and finish a concrete run with `test`, `start`, `event`, and
38
38
  idempotent or append-only SQLite operation suitable for a persistent tmux
39
39
  campaign.
40
40
 
41
+ Test registration validates that any named capability and scenario belong to
42
+ the same campaign. Finishing a test updates the test, capability, and scenario
43
+ status in one SQLite transaction, so a typo cannot silently leave the campaign
44
+ matrix stale or split across contradictory states.
45
+
41
46
  Inspect current coverage:
42
47
 
43
48
  ```bash
@@ -48,3 +53,25 @@ node scripts/supervision-ledger.js status \
48
53
  Do not mark a test passed from the agent's prose. Verify session events, files,
49
54
  checks, external state, and side-effect boundaries first, then store those paths
50
55
  in `evidence_json` and `events`.
56
+
57
+ ## Current Campaign Findings
58
+
59
+ ### Data analysis retained-state recovery
60
+
61
+ `data-analysis-local-011` passed after reusable runtime fixes. AgInTi completed
62
+ the mixed sensor-export project, repaired its own unclosed CSV handles, created
63
+ commits `2f79553` and `3317a59`, and resumed the same durable session after a
64
+ control-loop pause. Independent validation ran both the unit suite and the
65
+ canonical generator with `ResourceWarning` promoted to an error; all tests,
66
+ audit counts, condition means, plot bytes, and clean-git checks passed.
67
+
68
+ The incident established two general contracts:
69
+
70
+ - A canonical command wrapped only by an explicit exit-status probe, such as
71
+ `python analysis.py; echo "EXIT=$?"`, is valid evidence when the captured
72
+ status is zero. Arbitrary semicolon chains remain invalid because a later
73
+ command can hide an earlier failure.
74
+ - User-facing artifacts keep canonical workspace paths but receive a readable
75
+ canvas/download filename derived from the task title and source purpose.
76
+ Internal collision identifiers are short suffixes, never the leading or only
77
+ visible filename information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.208",
3
+ "version": "0.20.210",
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",
@@ -61,6 +61,7 @@
61
61
  "scripts/setup-agent-toolchain-docker.sh",
62
62
  "scripts/real-deepseek-capabilities.js",
63
63
  "scripts/postinstall-webapp.js",
64
+ "scripts/check-js-syntax.js",
64
65
  "scripts/seed-supervised-homework.js",
65
66
  "scripts/supervision-ledger.js",
66
67
  "scripts/smoke-supervision-ledger.js",
@@ -73,7 +74,11 @@
73
74
  "scripts/smoke-integration-retained-repository-kernel.js",
74
75
  "scripts/smoke-integration-retained-session-state-store.js",
75
76
  "scripts/smoke-integration-retained-native-session-repository-state.js",
77
+ "scripts/smoke-integration-retained-native-execution-evidence.js",
78
+ "scripts/smoke-integration-retained-runtime-repository-surface.js",
79
+ "scripts/smoke-integration-retained-runtime-repository-phase-b.js",
76
80
  "scripts/smoke-integration-runtime-repository-contract.js",
81
+ "scripts/smoke-integration-runtime-authority.js",
77
82
  "scripts/smoke-auxiliary-tools.js",
78
83
  "scripts/smoke-agentlink.js",
79
84
  "scripts/smoke-auth.js",
@@ -91,6 +96,7 @@
91
96
  "scripts/smoke-long-jobs.js",
92
97
  "scripts/smoke-localllm-auto-max.js",
93
98
  "scripts/smoke-localllm-code-routing.js",
99
+ "scripts/smoke-local-failure-recovery.js",
94
100
  "scripts/smoke-local-resource-policy.js",
95
101
  "scripts/smoke-localllm-model-tiers.js",
96
102
  "scripts/smoke-localllm-provider.js",
@@ -139,7 +145,7 @@
139
145
  "scripts": {
140
146
  "start": "node run.js",
141
147
  "web": "node web.js",
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",
148
+ "check": "node scripts/check-js-syntax.js",
143
149
  "setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
144
150
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
145
151
  "smoke:dynamic-step-budget": "node scripts/smoke-dynamic-step-budget.js",
@@ -203,11 +209,15 @@
203
209
  "smoke:integration-retained-repository-kernel": "node --experimental-test-module-mocks scripts/smoke-integration-retained-repository-kernel.js",
204
210
  "smoke:integration-retained-session-state-store": "node --experimental-test-module-mocks scripts/smoke-integration-retained-session-state-store.js",
205
211
  "smoke:integration-retained-native-session-repository-state": "node --experimental-test-module-mocks scripts/smoke-integration-retained-native-session-repository-state.js",
212
+ "smoke:integration-retained-native-execution-evidence": "node scripts/smoke-integration-retained-native-execution-evidence.js",
213
+ "smoke:integration-retained-runtime-repository-surface": "node scripts/smoke-integration-retained-runtime-repository-surface.js",
214
+ "smoke:integration-retained-runtime-repository-phase-b": "node scripts/smoke-integration-retained-runtime-repository-phase-b.js",
206
215
  "smoke:integration-runtime-repository-contract": "node scripts/smoke-integration-runtime-repository-contract.js",
216
+ "smoke:integration-runtime-authority": "node scripts/smoke-integration-runtime-authority.js",
207
217
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
208
218
  "publish:env": "node scripts/npm-publish-from-env.js publish --access public",
209
219
  "publish:env:whoami": "node scripts/npm-publish-from-env.js whoami",
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",
220
+ "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-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && 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",
211
221
  "pack:dry-run": "npm pack --dry-run",
212
222
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
213
223
  },
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+ const roots = ["run.js", "web.js", "bin", "src", "public", "scripts"];
9
+ const extensions = new Set([".cjs", ".js", ".mjs"]);
10
+
11
+ async function collect(entryPath, files) {
12
+ const stat = await fs.lstat(entryPath);
13
+ if (stat.isSymbolicLink()) return;
14
+ if (stat.isDirectory()) {
15
+ const entries = await fs.readdir(entryPath, { withFileTypes: true });
16
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
17
+ await collect(path.join(entryPath, entry.name), files);
18
+ }
19
+ return;
20
+ }
21
+ if (stat.isFile() && extensions.has(path.extname(entryPath).toLowerCase())) files.push(entryPath);
22
+ }
23
+
24
+ const files = [];
25
+ for (const root of roots) await collect(path.join(projectRoot, root), files);
26
+
27
+ for (const filePath of files) {
28
+ const checked = spawnSync(process.execPath, ["--check", filePath], {
29
+ cwd: projectRoot,
30
+ encoding: "utf8",
31
+ });
32
+ if (checked.status !== 0) {
33
+ process.stderr.write(checked.stderr || checked.stdout || `Syntax check failed: ${filePath}\n`);
34
+ process.exit(checked.status || 1);
35
+ }
36
+ }
37
+
38
+ console.log(`JavaScript syntax check passed (${files.length} files).`);
@@ -40,6 +40,24 @@ async function main() {
40
40
  const persisted = await persistCanvasPayloadFile(normalized.payload, { config, store });
41
41
  if (!persisted.ok) throw new Error(persisted.reason || "canvas artifact persistence failed");
42
42
  if (!persisted.payload.artifactPersisted) throw new Error("canvas file was not persisted into session artifacts");
43
+ if (persisted.payload.downloadName !== "durable-report.md") {
44
+ throw new Error(`canvas artifact lost its meaningful download name: ${persisted.payload.downloadName}`);
45
+ }
46
+ if (!/^durable-report--[A-Za-z0-9_-]{8}\.md$/.test(path.basename(persisted.payload.sessionFilePath))) {
47
+ throw new Error(`canvas persistence put an opaque id before the filename: ${persisted.payload.sessionFilePath}`);
48
+ }
49
+
50
+ const genericNamed = normalizeCanvasPayload(
51
+ {
52
+ title: "Fluorescence Experiment Analysis",
53
+ kind: "pdf",
54
+ path: "report.pdf",
55
+ },
56
+ config
57
+ );
58
+ if (!genericNamed.ok || genericNamed.payload.downloadName !== "Fluorescence-Experiment-Analysis.pdf") {
59
+ throw new Error(`generic artifact did not receive a task-meaningful filename: ${genericNamed.payload?.downloadName}`);
60
+ }
43
61
 
44
62
  await fs.rm(sourcePath);
45
63
 
@@ -59,6 +77,7 @@ async function main() {
59
77
  const content = await readArtifactContent(items[0], { store, config });
60
78
  if (!content.ok) throw new Error(content.error || "persisted artifact could not be read");
61
79
  if (!String(content.text || "").includes("Durable report")) throw new Error("persisted artifact content mismatch");
80
+ if (content.filename !== "durable-report.md") throw new Error("artifact read metadata lost the download filename");
62
81
 
63
82
  const largeImagePath = path.join(workspace, "large-preview.png");
64
83
  const pngHeader = Buffer.from("89504e470d0a1a0a", "hex");
@@ -1190,6 +1190,7 @@ try {
1190
1190
 
1191
1191
  const resumeStatePath = path.join(agintiflowHome, "sessions", resumedSessionId, "state.json");
1192
1192
  const runtimeBeforeAmbientResume = JSON.parse(await fs.readFile(resumeStatePath, "utf8"));
1193
+ const completedArtifactBeforeAmbientResume = await fs.stat(path.join(tempRoot, "notes/interactive.md"));
1193
1194
  const ambientSecret = "must-not-persist-cli-resume-secret";
1194
1195
  const ordinaryResume = await runCli(
1195
1196
  ["resume", resumedSessionId, "--", "Continue this saved mock session without changing its runtime."],
@@ -1204,6 +1205,7 @@ try {
1204
1205
  }
1205
1206
  );
1206
1207
  const runtimeAfterAmbientResume = JSON.parse(await fs.readFile(resumeStatePath, "utf8"));
1208
+ const completedArtifactAfterAmbientResume = await fs.stat(path.join(tempRoot, "notes/interactive.md"));
1207
1209
  if (
1208
1210
  runtimeAfterAmbientResume.meta.runtimeConfig.provider !== "mock" ||
1209
1211
  runtimeAfterAmbientResume.meta.runtimeConfig.model !== "mock-agent" ||
@@ -1213,6 +1215,14 @@ try {
1213
1215
  ) {
1214
1216
  throw new Error("ordinary one-shot resume drifted to ambient provider/model/key settings");
1215
1217
  }
1218
+ if (
1219
+ completedArtifactAfterAmbientResume.mtimeMs !== completedArtifactBeforeAmbientResume.mtimeMs ||
1220
+ runtimeAfterAmbientResume.meta.goalContract.status !== "completed" ||
1221
+ runtimeAfterAmbientResume.meta.goalContract.lifecycle.at(-1)?.reason !== "completed_task_noop" ||
1222
+ !ordinaryResume.stdout.includes("did not repeat any tool or external side effect")
1223
+ ) {
1224
+ throw new Error("generic resume of a completed task repeated work instead of preserving the verified result");
1225
+ }
1216
1226
 
1217
1227
  await runCli(
1218
1228
  ["resume", resumedSessionId, "--no-web-search", "--no-auxiliary-tools", "--", "Continue with explicitly reduced tools."],
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import {
7
7
  buildModelTimeoutRetryMessages,
8
+ genericArtifactFilenameBlock,
8
9
  modelTimeoutRetryRoute,
9
10
  repairModelMessageHistory,
10
11
  shouldResetStaticDiscoveryPhase,
@@ -83,6 +84,40 @@ async function runMock(goal, sessionId, { resume = false } = {}) {
83
84
  }
84
85
 
85
86
  try {
87
+ const genericArtifactBlock = await genericArtifactFilenameBlock(
88
+ "write_file",
89
+ { path: "report.md", content: "summary" },
90
+ { commandCwd: workspace, taskProfile: "data", goal: "Analyze the fluorescence experiment exports." },
91
+ { goal: "Analyze the fluorescence experiment exports.", messages: [], meta: {} }
92
+ );
93
+ assert(
94
+ genericArtifactBlock?.category === "artifact-filename" &&
95
+ /fluorescence|experiment/i.test(genericArtifactBlock.permissionAdvice?.instruction || ""),
96
+ "new generic artifact filename was not redirected to a meaningful topic-derived name"
97
+ );
98
+ assert(
99
+ (await genericArtifactFilenameBlock(
100
+ "write_file",
101
+ { path: "outputs/summary.json", content: "{}" },
102
+ { commandCwd: workspace, taskProfile: "data", goal: "Analyze the experiment." },
103
+ {
104
+ goal: "Analyze the experiment.",
105
+ messages: [],
106
+ meta: { projectVerification: { requiredOutputs: ["outputs/summary.json"] } },
107
+ }
108
+ )) === null,
109
+ "an exact project-declared artifact filename was incorrectly rejected as generic"
110
+ );
111
+ assert(
112
+ (await genericArtifactFilenameBlock(
113
+ "write_file",
114
+ { path: "reports/fluorescence-dose-response-analysis.md", content: "summary" },
115
+ { commandCwd: workspace, taskProfile: "data" },
116
+ { goal: "Analyze the experiment.", messages: [], meta: {} }
117
+ )) === null,
118
+ "a descriptive artifact filename was incorrectly blocked"
119
+ );
120
+
86
121
  const staleDeepSeekState = {
87
122
  messages: [
88
123
  { role: "system", content: "system" },
@@ -1385,6 +1420,30 @@ try {
1385
1420
  const unifiedText = await fs.readFile(path.join(workspace, "unified-target.txt"), "utf8");
1386
1421
  assert(unified.ok && unifiedText === "alpha\nnew\nomega\n", "unified apply_patch did not update expected file");
1387
1422
 
1423
+ await fs.writeFile(path.join(workspace, "hybrid-patch-target.txt"), "alpha\nold\nomega\n", "utf8");
1424
+ const hybridPatch = await executeWorkspaceTool(
1425
+ "apply_patch",
1426
+ {
1427
+ patch: [
1428
+ "*** Begin Patch ***",
1429
+ "--- a/hybrid-patch-target.txt",
1430
+ "+++ b/hybrid-patch-target.txt",
1431
+ "@@ -1,3 +1,3 @@",
1432
+ " alpha",
1433
+ "-old",
1434
+ "+new",
1435
+ " omega",
1436
+ "*** End Patch ***",
1437
+ ].join("\n"),
1438
+ },
1439
+ { commandCwd: workspace, allowFileTools: true }
1440
+ );
1441
+ const hybridPatchText = await fs.readFile(path.join(workspace, "hybrid-patch-target.txt"), "utf8");
1442
+ assert(
1443
+ hybridPatch.ok && hybridPatchText === "alpha\nnew\nomega\n",
1444
+ "hybrid wrapped unified apply_patch did not update expected file"
1445
+ );
1446
+
1388
1447
  await fs.writeFile(path.join(workspace, "repair-report.md"), "old report\n", "utf8");
1389
1448
  const ordinaryAddExistingError = await executeWorkspaceTool(
1390
1449
  "apply_patch",
@@ -52,6 +52,13 @@ async function main() {
52
52
  toolChoiceForProvider({ provider: "deepseek" }, []) === "auto",
53
53
  "provider-neutral research routing added an unsupported named tool_choice"
54
54
  );
55
+ assert(
56
+ toolChoiceForProvider(
57
+ { provider: "deepseek" },
58
+ [{ role: "user", content: "Emit exactly one enabled tool call that performs the next concrete action." }]
59
+ ) === "auto",
60
+ "DeepSeek thinking mode received unsupported required tool selection during recovery"
61
+ );
55
62
  assert(
56
63
  JSON.stringify(providerStructuredOutputAttempts("deepseek")) === JSON.stringify(["json_object", "prompt"]),
57
64
  "DeepSeek structured extraction still probes an unsupported JSON Schema mode"
@@ -18,21 +18,34 @@ import {
18
18
  artifactValidationScopeBlock,
19
19
  canonicalizeVerifiedArtifactCompletion,
20
20
  completedDeepResearchReuse,
21
+ completionEvidenceNeedsCommand,
22
+ enqueueFailedTestRepairInstruction,
21
23
  nextStepRuntimeConfig,
24
+ projectAcceptanceFromMarkdown,
25
+ recordCanonicalGeneratedOutputProgress,
26
+ recordProjectVerificationOutcome,
22
27
  recordExactOutputProgress,
23
28
  recordStaticDiscoveryProgress,
29
+ resetStaticDiscoveryAfterContextLoss,
24
30
  rememberCompletedDeepResearch,
25
31
  repeatedNoProgressToolBlock,
26
32
  repeatedSuccessfulMutationBlock,
27
33
  repeatedStaticToolBlock,
34
+ reopenedArtifactRepairPending,
28
35
  shouldResetStaticDiscoveryPhase,
29
36
  } from "../src/agent-runner.js";
37
+ import {
38
+ augmentScsTaskContractWithProjectVerification,
39
+ buildScsEvidenceLedger,
40
+ evaluateScsEvidence,
41
+ } from "../src/scs-evidence.js";
30
42
  import {
31
43
  createStepBudgetState,
32
44
  decideStepBudgetExtension,
33
45
  isStaticDiscoveryToolCall,
34
46
  staticToolCallSignature,
35
47
  normalizeDynamicStepsMode,
48
+ shouldEvaluateResumeBoundary,
36
49
  summarizeRepeatedStaticDiscovery,
37
50
  } from "../src/step-budget-controller.js";
38
51
  import { SessionStore } from "../src/session-store.js";
@@ -59,6 +72,18 @@ function toolMessage(payload) {
59
72
 
60
73
  try {
61
74
  assert(normalizeDynamicStepsMode("off") === "off", "dynamic mode off did not normalize");
75
+ assert(
76
+ completionEvidenceNeedsCommand({ missingProjectCommands: ["python analysis.py"] }),
77
+ "a pending canonical command did not reopen command execution"
78
+ );
79
+ assert(
80
+ completionEvidenceNeedsCommand({ missingGitActions: ["commit"] }),
81
+ "a pending git action did not reopen command execution"
82
+ );
83
+ assert(
84
+ !completionEvidenceNeedsCommand({ missing: [], missingProjectCommands: [], missingGitActions: [] }),
85
+ "satisfied completion evidence kept command execution open"
86
+ );
62
87
  assert(normalizeDynamicStepsMode("always") === "on", "dynamic mode always did not normalize to on");
63
88
  assert(normalizeDynamicStepsMode("smart") === "auto", "dynamic mode smart did not normalize to auto");
64
89
  const inheritedBudget = createStepBudgetState(
@@ -111,6 +136,27 @@ try {
111
136
  assert(uniqueDiscovery.staticTotal === 2, "duplicate reads consumed the unique convergence budget");
112
137
  assert(uniqueDiscovery.staticCallTotal === 3, "raw static call telemetry did not retain duplicate calls");
113
138
  assert(uniqueDiscovery.staticCounts["read_file:/reference/A.md"] === 2, "per-signature loop accounting was lost");
139
+ const compactedDiscoveryState = {
140
+ meta: {
141
+ toolLoop: {
142
+ recent: [],
143
+ warned: ["file-read:/reference/A.md", "run_command:keep"],
144
+ staticCounts: { "file-read:/reference/A.md": 1 },
145
+ staticOrder: ["file-read:/reference/A.md"],
146
+ staticTotal: 1,
147
+ staticCallTotal: 1,
148
+ convergenceAnnounced: { staticTotal: 1 },
149
+ },
150
+ },
151
+ };
152
+ resetStaticDiscoveryAfterContextLoss(compactedDiscoveryState, "smoke-compaction");
153
+ assert(compactedDiscoveryState.meta.toolLoop.staticTotal === 0, "context recovery kept a stale static convergence total");
154
+ assert(compactedDiscoveryState.meta.toolLoop.staticOrder.length === 0, "context recovery kept stale read signatures active");
155
+ assert(compactedDiscoveryState.meta.toolLoop.staticHistory.length === 1, "context recovery did not archive discovery telemetry");
156
+ assert(
157
+ JSON.stringify(compactedDiscoveryState.meta.toolLoop.warned) === JSON.stringify(["run_command:keep"]),
158
+ "context recovery did not clear only stale static-read warnings"
159
+ );
114
160
  const exactReadSignature = staticToolCallSignature("read_file", { path: "/reference/A.md" }, {
115
161
  commandCwd: workspace,
116
162
  });
@@ -379,6 +425,35 @@ try {
379
425
  deleteOutputBlock?.category === "artifact-validation-delete-output",
380
426
  "artifact validation allowed delete-and-recreate repair of an exact output"
381
427
  );
428
+ const reopenedSourceReadState = {
429
+ commandCwd: workspace,
430
+ meta: {
431
+ artifactProgress: {
432
+ exactOutputPaths: ["outputs/report.md"],
433
+ needsRepair: true,
434
+ needsSourceRead: true,
435
+ reopenedSourcePaths: ["analysis.py"],
436
+ },
437
+ },
438
+ };
439
+ assert(
440
+ artifactValidationScopeBlock(
441
+ reopenedSourceReadState,
442
+ "read_file",
443
+ { path: "analysis.py" },
444
+ { commandCwd: workspace, artifactValidationPhase: true }
445
+ ) === null,
446
+ "a correction request could not inspect its exact named source file"
447
+ );
448
+ assert(
449
+ artifactValidationScopeBlock(
450
+ reopenedSourceReadState,
451
+ "read_file",
452
+ { path: "unrelated.py" },
453
+ { commandCwd: workspace, artifactValidationPhase: true }
454
+ )?.category === "artifact-validation-scope",
455
+ "the correction source allowance leaked to unrelated files"
456
+ );
382
457
  const artifactProgress = recordExactOutputProgress(
383
458
  artifactState,
384
459
  {
@@ -395,6 +470,28 @@ try {
395
470
  nextStepRuntimeConfig({ provider: "localllm" }, artifactState).artifactValidationPhase === true,
396
471
  "next step did not enter artifact validation mode"
397
472
  );
473
+ const reopenedRepairState = {
474
+ meta: {
475
+ goalContract: { revision: 9 },
476
+ artifactProgress: { reopenedGoalRevision: 9, reopenedMutationRevision: 4 },
477
+ projectVerification: { mutationRevision: 4 },
478
+ },
479
+ };
480
+ assert(
481
+ reopenedArtifactRepairPending(reopenedRepairState),
482
+ "a fresh same-task correction was cleared before any source mutation"
483
+ );
484
+ reopenedRepairState.meta.projectVerification.mutationRevision = 5;
485
+ assert(
486
+ !reopenedArtifactRepairPending(reopenedRepairState),
487
+ "a source mutation did not satisfy the revision-scoped repair obligation"
488
+ );
489
+ reopenedRepairState.meta.projectVerification.mutationRevision = 4;
490
+ reopenedRepairState.meta.goalContract.revision = 10;
491
+ assert(
492
+ !reopenedArtifactRepairPending(reopenedRepairState),
493
+ "an old correction obligation leaked into a different goal revision"
494
+ );
398
495
  artifactState.meta.artifactProgress.needsRepair = true;
399
496
  artifactState.meta.artifactProgress.outputEmbedded = true;
400
497
  artifactState.meta.artifactProgress.usedValidationTools = ["read_file"];
@@ -513,6 +610,27 @@ try {
513
610
  ) === "Completed and verified MEDIA_ROUTINE_READINESS.md.",
514
611
  "verified completion rewrote a result that already named the exact contract output"
515
612
  );
613
+ const portablePathState = {
614
+ commandCwd: workspace,
615
+ meta: {
616
+ artifactProgress: {
617
+ complete: true,
618
+ exactOutputPaths: [path.join(workspace, "reports", "fluorescence-dose-response-analysis.pdf")],
619
+ preflight: { defectCount: 0 },
620
+ preflightFingerprint: "passed",
621
+ defectCount: 0,
622
+ needsRepair: false,
623
+ needsCommand: false,
624
+ needsSourceRead: false,
625
+ },
626
+ },
627
+ };
628
+ const portableCompletion = canonicalizeVerifiedArtifactCompletion(portablePathState, "");
629
+ assert(
630
+ portableCompletion.includes("reports/fluorescence-dose-response-analysis.pdf") &&
631
+ !portableCompletion.includes(workspace),
632
+ "verified completion leaked an absolute private workspace path"
633
+ );
516
634
  artifactState.meta.artifactProgress.needsRepair = true;
517
635
  artifactState.meta.artifactProgress.defectCount = 2;
518
636
  assert(
@@ -656,6 +774,48 @@ try {
656
774
  },
657
775
  { meta: {}, stepsCompleted: 0 }
658
776
  );
777
+ const migratedDefaultBudget = createStepBudgetState(
778
+ {
779
+ provider: "localllm",
780
+ maxSteps: 30,
781
+ dynamicSteps: "auto",
782
+ dynamicStepExtensionLimit: 1,
783
+ dynamicStepExtensionLimitExplicit: false,
784
+ scsActive: false,
785
+ },
786
+ {
787
+ meta: {
788
+ stepBudget: {
789
+ initialMaxSteps: 30,
790
+ currentMaxSteps: 40,
791
+ hardCap: 60,
792
+ extensionLimit: 1,
793
+ extensionsUsed: 1,
794
+ },
795
+ },
796
+ stepsCompleted: 40,
797
+ }
798
+ );
799
+ assert(
800
+ migratedDefaultBudget.extensionLimit === 3 && migratedDefaultBudget.extensionsUsed === 1,
801
+ "resumed non-explicit step budget did not adopt the current bounded default"
802
+ );
803
+ assert(
804
+ shouldEvaluateResumeBoundary(
805
+ { resume: "existing-session" },
806
+ { stepsCompleted: 40 },
807
+ migratedDefaultBudget
808
+ ),
809
+ "a resumed session at its consumed boundary did not request bounded capacity before the loop"
810
+ );
811
+ assert(
812
+ !shouldEvaluateResumeBoundary(
813
+ { resume: "existing-session" },
814
+ { stepsCompleted: migratedDefaultBudget.hardCap },
815
+ { ...migratedDefaultBudget, currentMaxSteps: migratedDefaultBudget.hardCap }
816
+ ),
817
+ "a resumed session attempted to exceed its dynamic hard cap"
818
+ );
659
819
  const progressDecision = decideStepBudgetExtension({
660
820
  config: { scsActive: false },
661
821
  budget: normalBudget,
@@ -699,6 +859,65 @@ try {
699
859
  });
700
860
  assert(!blockedDecision.approved && /permission|approval|blocked/i.test(blockedDecision.reason), "budget gate did not deny blocker loops");
701
861
 
862
+ const repairBudget = createStepBudgetState(
863
+ {
864
+ provider: "localllm",
865
+ maxSteps: 30,
866
+ dynamicSteps: "on",
867
+ dynamicStepExtensionLimit: 2,
868
+ scsActive: false,
869
+ },
870
+ { meta: {}, stepsCompleted: 0 }
871
+ );
872
+ const repairDecision = decideStepBudgetExtension({
873
+ config: { scsActive: false, commandCwd: "/tmp/workspace" },
874
+ budget: repairBudget,
875
+ step: 29,
876
+ state: {
877
+ messages: [
878
+ toolMessage({
879
+ toolName: "run_command",
880
+ ok: false,
881
+ exitCode: 1,
882
+ args: { command: "python -m unittest discover -s tests" },
883
+ stderr: "AssertionError: expected calibrated values",
884
+ }),
885
+ toolMessage({
886
+ toolName: "apply_patch",
887
+ ok: false,
888
+ reason: "Patch search text was not found in analysis.py.",
889
+ }),
890
+ toolMessage({
891
+ toolName: "read_file",
892
+ ok: true,
893
+ args: { path: "analysis.py" },
894
+ path: "analysis.py",
895
+ }),
896
+ toolMessage({
897
+ toolName: "read_file",
898
+ ok: false,
899
+ blocked: true,
900
+ category: "repeated-read-only-call",
901
+ reason: "The same static discovery call already ran once.",
902
+ }),
903
+ ],
904
+ },
905
+ events: [
906
+ { type: "file.changed", data: { path: "analysis.py" } },
907
+ ...Array.from({ length: 30 }, (_, index) => ({
908
+ type: index % 2 === 0 ? "snapshot.captured" : "model.requested",
909
+ data: {},
910
+ })),
911
+ { type: "tool.completed", data: { toolName: "run_command", exitCode: 1 } },
912
+ { type: "tool.failed", data: { toolName: "apply_patch" } },
913
+ { type: "tool.blocked", data: { toolName: "read_file" } },
914
+ ],
915
+ });
916
+ assert(
917
+ repairDecision.approved && repairDecision.extraSteps > 0,
918
+ `budget gate denied an active repair after concrete file progress: ${repairDecision.reason}`
919
+ );
920
+
702
921
  const repeatedDiscoveryMessages = [
703
922
  "ls -la ../Musia",
704
923
  "ls -la ../LALACHAN",
@@ -543,6 +543,7 @@ async function verifyPackagedBinAndScriptClosure() {
543
543
  assert.equal(packageJson.bin?.["aginti-integration"], "bin/aginti-integration.js");
544
544
  assert.equal(packageJson.files.includes("bin/"), true);
545
545
  for (const requiredFile of [
546
+ "scripts/check-js-syntax.js",
546
547
  "scripts/eval-provider-attribution.js",
547
548
  "scripts/smoke-context-budget-recovery.js",
548
549
  "scripts/smoke-integration-production-mount.js",
@@ -553,11 +554,7 @@ async function verifyPackagedBinAndScriptClosure() {
553
554
  packageJson.scripts?.["smoke:integration-production-mount"],
554
555
  "node scripts/smoke-integration-production-mount.js"
555
556
  );
556
- assert.equal(packageJson.scripts.check.includes("node --check bin/aginti-integration.js"), true);
557
- assert.equal(
558
- packageJson.scripts.check.includes("node --check scripts/smoke-integration-production-mount.js"),
559
- true
560
- );
557
+ assert.equal(packageJson.scripts.check, "node scripts/check-js-syntax.js");
561
558
  for (const relativePath of referencedRunnablePaths(packageJson)) {
562
559
  const stat = await fs.stat(path.join(REPOSITORY_ROOT, relativePath));
563
560
  assert.equal(stat.isFile(), true, `packaged script reference is missing: ${relativePath}`);
@@ -1131,5 +1128,6 @@ async function main() {
1131
1128
 
1132
1129
  main().catch((error) => {
1133
1130
  process.stderr.write(`integration production mount smoke: failed (${String(error?.code || error?.name || "ERROR")})\n`);
1131
+ process.stderr.write(`${String(error?.stack || error?.message || error)}\n`);
1134
1132
  process.exitCode = 1;
1135
1133
  });
@@ -601,7 +601,7 @@ async function runCore() {
601
601
  cancellationAttestation: runtimeFixture.cancellationAttestation,
602
602
  hardenedSandboxAttestation: runtimeFixture.hardenedSandboxAttestation,
603
603
  });
604
- const realRuntimeProof = realRuntimeAuthority.getIntegrationRuntimeProof();
604
+ const realRuntimeProof = await realRuntimeAuthority.getIntegrationRuntimeProof();
605
605
  assert.equal(realRuntimeProof.repositoryProofDigest, runtimeFixture.repositoryAttestation.digest);
606
606
  assert.equal(
607
607
  realRuntimeProof.eventAppendProofDigest,