@lazyingart/agintiflow 0.20.207 → 0.20.209

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 (69) hide show
  1. package/docs/supervision-campaign-ledger.md +27 -0
  2. package/package.json +31 -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 +137 -0
  7. package/scripts/smoke-context-budget-recovery.js +4 -0
  8. package/scripts/smoke-deep-research.js +7 -0
  9. package/scripts/smoke-dynamic-step-budget.js +392 -0
  10. package/scripts/smoke-integration-production-mount.js +3 -5
  11. package/scripts/smoke-integration-retained-durable-common.js +693 -0
  12. package/scripts/smoke-integration-retained-event-ledger-bundle.js +771 -0
  13. package/scripts/smoke-integration-retained-event-ledger.js +991 -0
  14. package/scripts/smoke-integration-retained-file-lock.js +707 -0
  15. package/scripts/smoke-integration-retained-native-execution-evidence.js +2948 -0
  16. package/scripts/smoke-integration-retained-native-session-repository-state.js +1761 -0
  17. package/scripts/smoke-integration-retained-repository-kernel.js +1345 -0
  18. package/scripts/smoke-integration-retained-runtime-repository-phase-b.js +1296 -0
  19. package/scripts/smoke-integration-retained-runtime-repository-surface.js +1230 -0
  20. package/scripts/smoke-integration-retained-session-state-store.js +2055 -0
  21. package/scripts/smoke-integration-runtime-authority.js +5040 -0
  22. package/scripts/smoke-integration-runtime-repository-contract.js +655 -0
  23. package/scripts/smoke-integration-storage-authority.js +884 -0
  24. package/scripts/smoke-local-failure-recovery.js +497 -0
  25. package/scripts/smoke-model-roles.js +70 -0
  26. package/scripts/smoke-perception-research.js +28 -4
  27. package/scripts/smoke-progressive-tool-selection.js +410 -5
  28. package/scripts/smoke-scs-evidence-visibility.js +22 -0
  29. package/scripts/smoke-session-runtime.js +5 -0
  30. package/scripts/smoke-skills.js +4 -0
  31. package/scripts/smoke-supervision-ledger.js +20 -1
  32. package/scripts/smoke-truthful-completion.js +434 -12
  33. package/scripts/smoke-web-autostart.js +15 -1
  34. package/scripts/supervision-ledger.js +45 -11
  35. package/src/agent-runner.js +2595 -176
  36. package/src/artifact-tunnel.js +48 -7
  37. package/src/config.js +7 -2
  38. package/src/deep-research.js +13 -1
  39. package/src/integration-authority-error.js +15 -0
  40. package/src/integration-durable-common.js +12 -18
  41. package/src/integration-event-ledger-store.js +1667 -0
  42. package/src/integration-native-executor.js +119 -114
  43. package/src/integration-native-runtime-roots.js +263 -0
  44. package/src/integration-retained-native-execution-evidence.js +869 -0
  45. package/src/integration-retained-native-session-repository-state.js +3203 -0
  46. package/src/integration-retained-runtime-repository-surface.js +3753 -0
  47. package/src/integration-retained-session-state-store.js +2141 -0
  48. package/src/integration-retained-text-workspace.js +899 -0
  49. package/src/integration-retained-vision-workspace.js +1676 -0
  50. package/src/integration-run-registry.js +81 -5
  51. package/src/integration-runtime-authority.js +917 -174
  52. package/src/integration-runtime-process-owner-bootstrap.js +63 -0
  53. package/src/integration-runtime-repository-contract.js +395 -0
  54. package/src/integration-runtime-repository.js +1536 -0
  55. package/src/integration-session-persistence.js +259 -8
  56. package/src/integration-session-service.js +4 -4
  57. package/src/integration-storage-authority.js +2870 -0
  58. package/src/model-client.js +109 -28
  59. package/src/perception-tools.js +30 -9
  60. package/src/progressive-tool-selection.js +136 -8
  61. package/src/scs-controller.js +23 -4
  62. package/src/scs-evidence.js +442 -17
  63. package/src/session-runtime.js +2 -1
  64. package/src/session-store.js +124 -2
  65. package/src/skill-library.js +8 -3
  66. package/src/step-budget-controller.js +43 -7
  67. package/src/tool-contract.js +59 -0
  68. package/src/workspace-tools.js +28 -6
  69. 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.207",
3
+ "version": "0.20.209",
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,10 +61,24 @@
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",
67
68
  "scripts/smoke-integration-production-mount.js",
69
+ "scripts/smoke-integration-storage-authority.js",
70
+ "scripts/smoke-integration-retained-durable-common.js",
71
+ "scripts/smoke-integration-retained-file-lock.js",
72
+ "scripts/smoke-integration-retained-event-ledger.js",
73
+ "scripts/smoke-integration-retained-event-ledger-bundle.js",
74
+ "scripts/smoke-integration-retained-repository-kernel.js",
75
+ "scripts/smoke-integration-retained-session-state-store.js",
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",
80
+ "scripts/smoke-integration-runtime-repository-contract.js",
81
+ "scripts/smoke-integration-runtime-authority.js",
68
82
  "scripts/smoke-auxiliary-tools.js",
69
83
  "scripts/smoke-agentlink.js",
70
84
  "scripts/smoke-auth.js",
@@ -82,6 +96,7 @@
82
96
  "scripts/smoke-long-jobs.js",
83
97
  "scripts/smoke-localllm-auto-max.js",
84
98
  "scripts/smoke-localllm-code-routing.js",
99
+ "scripts/smoke-local-failure-recovery.js",
85
100
  "scripts/smoke-local-resource-policy.js",
86
101
  "scripts/smoke-localllm-model-tiers.js",
87
102
  "scripts/smoke-localllm-provider.js",
@@ -130,7 +145,7 @@
130
145
  "scripts": {
131
146
  "start": "node run.js",
132
147
  "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",
148
+ "check": "node scripts/check-js-syntax.js",
134
149
  "setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
135
150
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
136
151
  "smoke:dynamic-step-budget": "node scripts/smoke-dynamic-step-budget.js",
@@ -186,10 +201,23 @@
186
201
  "supervision:ledger": "node scripts/supervision-ledger.js",
187
202
  "smoke:supervision-ledger": "node scripts/smoke-supervision-ledger.js",
188
203
  "smoke:integration-production-mount": "node scripts/smoke-integration-production-mount.js",
204
+ "smoke:integration-storage-authority": "node scripts/smoke-integration-storage-authority.js",
205
+ "smoke:integration-retained-durable-common": "node scripts/smoke-integration-retained-durable-common.js",
206
+ "smoke:integration-retained-file-lock": "node scripts/smoke-integration-retained-file-lock.js",
207
+ "smoke:integration-retained-event-ledger": "node scripts/smoke-integration-retained-event-ledger.js",
208
+ "smoke:integration-retained-event-ledger-bundle": "node --experimental-test-module-mocks scripts/smoke-integration-retained-event-ledger-bundle.js",
209
+ "smoke:integration-retained-repository-kernel": "node --experimental-test-module-mocks scripts/smoke-integration-retained-repository-kernel.js",
210
+ "smoke:integration-retained-session-state-store": "node --experimental-test-module-mocks scripts/smoke-integration-retained-session-state-store.js",
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",
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",
189
217
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
190
218
  "publish:env": "node scripts/npm-publish-from-env.js publish --access public",
191
219
  "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",
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",
193
221
  "pack:dry-run": "npm pack --dry-run",
194
222
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
195
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,8 +5,10 @@ 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,
11
+ shouldResetStaticDiscoveryPhase,
10
12
  runAgent,
11
13
  sanitizeToolResult,
12
14
  toolResultForModel,
@@ -18,6 +20,7 @@ import { formatBehaviorContractForPrompt } from "../src/behavior-contract.js";
18
20
  import { resolveRuntimeConfig } from "../src/config.js";
19
21
  import { readCodebaseMap } from "../src/codebase-map.js";
20
22
  import { evaluateCommandPolicy } from "../src/command-policy.js";
23
+ import { shouldReviewToolResult } from "../src/scs-controller.js";
21
24
  import {
22
25
  engineeringGuidanceForTask,
23
26
  recommendedMaxStepsForTask,
@@ -81,6 +84,40 @@ async function runMock(goal, sessionId, { resume = false } = {}) {
81
84
  }
82
85
 
83
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
+
84
121
  const staleDeepSeekState = {
85
122
  messages: [
86
123
  { role: "system", content: "system" },
@@ -164,6 +201,74 @@ try {
164
201
  interruptedDeepSeekState.messages.at(-1)?.content === "Continue with this new request: /review",
165
202
  "interrupted repair dropped the new user request"
166
203
  );
204
+ const deepSeekCompactionState = {
205
+ goal: "Continue a data repair from retained evidence.",
206
+ plan: "Use the verified source evidence, repair once, then test.",
207
+ stepsCompleted: 4,
208
+ meta: {},
209
+ messages: [
210
+ { role: "system", content: "system" },
211
+ { role: "user", content: "inspect the exact source" },
212
+ {
213
+ role: "assistant",
214
+ content: "",
215
+ reasoning_content: "The source must be read before editing.",
216
+ tool_calls: [
217
+ { id: "deep-read", type: "function", function: { name: "read_file", arguments: '{"path":"analysis.py"}' } },
218
+ ],
219
+ },
220
+ { role: "tool", tool_call_id: "deep-read", content: '{"ok":true,"path":"analysis.py","content":"verified source"}' },
221
+ ],
222
+ };
223
+ const deepSeekCompacted = buildModelTimeoutRetryMessages(
224
+ deepSeekCompactionState,
225
+ { provider: "deepseek", model: "deepseek-v4-pro", contextWindowTokens: 32768 },
226
+ { url: "", title: "No browser page open" },
227
+ 5,
228
+ new Error("synthetic timeout")
229
+ );
230
+ assert(
231
+ !deepSeekCompacted.some((message) => message.role === "assistant" && Array.isArray(message.tool_calls)),
232
+ "DeepSeek compaction synthesized assistant tool calls without original reasoning_content"
233
+ );
234
+ assert(
235
+ deepSeekCompacted.some(
236
+ (message) => message.role === "user" && /Retained runtime tool evidence/.test(message.content) && /analysis\.py/.test(message.content) && /verified source/.test(message.content)
237
+ ),
238
+ "DeepSeek compaction dropped bounded source evidence while removing synthetic tool-call messages"
239
+ );
240
+ assert(
241
+ !shouldReviewToolResult(
242
+ { ok: true, toolName: "read_file", path: "analysis.py" },
243
+ { meta: { toolLoop: { warned: ["old-read"], recent: [{ toolName: "read_file", ok: false }] } } }
244
+ ),
245
+ "SCS scheduled a redundant review for a successful read because an older read failed"
246
+ );
247
+ assert(
248
+ shouldReviewToolResult(
249
+ { ok: false, blocked: true, toolName: "read_file", reason: "exact read blocked" },
250
+ { meta: { toolLoop: { warned: [], recent: [] } } }
251
+ ),
252
+ "SCS stopped reviewing an exact blocked tool result"
253
+ );
254
+ assert(
255
+ !shouldResetStaticDiscoveryPhase({
256
+ ok: true,
257
+ toolName: "run_command",
258
+ args: { command: 'echo "SOURCE"; cat analysis.py; echo "DIFF"; git diff -- analysis.py' },
259
+ commandPolicy: { writesWorkspace: false },
260
+ }),
261
+ "composite read-only shell discovery incorrectly reset the bounded discovery phase"
262
+ );
263
+ assert(
264
+ shouldResetStaticDiscoveryPhase({
265
+ ok: true,
266
+ toolName: "run_command",
267
+ args: { command: "python analysis.py" },
268
+ commandPolicy: { writesWorkspace: true },
269
+ }),
270
+ "a successful workspace-writing command did not reset the discovery phase"
271
+ );
167
272
  const workspaceToolConfig = {
168
273
  commandCwd: workspace,
169
274
  allowFileTools: true,
@@ -1111,6 +1216,14 @@ try {
1111
1216
  localTimeoutRoute.model === "localllm-fast" && localTimeoutRoute.retryTimeoutMs === 90000,
1112
1217
  "LocalLLM timeout retry did not switch to its same-boundary fast route"
1113
1218
  );
1219
+ const defaultLocalTimeoutRoute = modelTimeoutRetryRoute({
1220
+ provider: "localllm",
1221
+ model: "localllm-fast",
1222
+ });
1223
+ assert(
1224
+ defaultLocalTimeoutRoute.timeoutMs === 300000 && defaultLocalTimeoutRoute.retryTimeoutMs === 600000,
1225
+ "LocalLLM default timeout did not allow bounded slow local generation and one longer retry"
1226
+ );
1114
1227
  const artifactTimeoutMessages = buildModelTimeoutRetryMessages(
1115
1228
  {
1116
1229
  meta: {
@@ -1307,6 +1420,30 @@ try {
1307
1420
  const unifiedText = await fs.readFile(path.join(workspace, "unified-target.txt"), "utf8");
1308
1421
  assert(unified.ok && unifiedText === "alpha\nnew\nomega\n", "unified apply_patch did not update expected file");
1309
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
+
1310
1447
  await fs.writeFile(path.join(workspace, "repair-report.md"), "old report\n", "utf8");
1311
1448
  const ordinaryAddExistingError = await executeWorkspaceTool(
1312
1449
  "apply_patch",
@@ -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"));
@@ -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"