@lazyingart/agintiflow 0.20.246 → 0.20.248

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.246",
3
+ "version": "0.20.248",
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",
@@ -49,6 +49,20 @@ async function runCliIn(cwd, args, envOverrides = {}) {
49
49
  return result.stdout;
50
50
  }
51
51
 
52
+ async function runCliAllowStopped(args, envOverrides = {}) {
53
+ try {
54
+ const stdout = await runCli(args, envOverrides);
55
+ return { stdout, stderr: "", exitCode: 0 };
56
+ } catch (error) {
57
+ if (!Number.isInteger(error?.code)) throw error;
58
+ return {
59
+ stdout: String(error.stdout || ""),
60
+ stderr: String(error.stderr || ""),
61
+ exitCode: error.code,
62
+ };
63
+ }
64
+ }
65
+
52
66
  try {
53
67
  await runCli(["init"]);
54
68
  const agintiMd = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
@@ -188,7 +202,7 @@ try {
188
202
  const doctor = JSON.parse(await runCli(["doctor", "--capabilities", "--json"]));
189
203
  assert(doctor.project.root === tempRoot, "doctor --capabilities used the wrong project root");
190
204
  assert(doctor.project.instructionsPresent, "doctor --capabilities did not report AGINTI.md");
191
- const envSandboxRun = await runCli(
205
+ const envSandboxRun = await runCliAllowStopped(
192
206
  ["--provider", "mock", "--routing", "manual", "--model", "mock-agent", "--max-steps", "1", "env sandbox smoke"],
193
207
  {
194
208
  SANDBOX_MODE: "host",
@@ -196,8 +210,19 @@ try {
196
210
  USE_DOCKER_SANDBOX: "false",
197
211
  }
198
212
  );
199
- assert(envSandboxRun.includes("Shell: host policy=allow"), "one-shot CLI did not respect host sandbox env defaults");
200
- assert(!envSandboxRun.includes("Docker workspace:"), "one-shot CLI forced Docker despite host sandbox env defaults");
213
+ assert(envSandboxRun.exitCode === 1, "a step-budget-stopped one-shot CLI run did not report failure");
214
+ assert(
215
+ envSandboxRun.stderr.includes("Stopped after 1 steps without finish()."),
216
+ "a step-budget-stopped one-shot CLI run did not expose its terminal reason"
217
+ );
218
+ assert(
219
+ envSandboxRun.stdout.includes("Shell: host policy=allow"),
220
+ "one-shot CLI did not respect host sandbox env defaults"
221
+ );
222
+ assert(
223
+ !envSandboxRun.stdout.includes("Docker workspace:"),
224
+ "one-shot CLI forced Docker despite host sandbox env defaults"
225
+ );
201
226
 
202
227
  console.log(
203
228
  JSON.stringify(
@@ -51,6 +51,25 @@ const artifactContract = deriveScsTaskContract({
51
51
  taskProfile: "chatops",
52
52
  });
53
53
  assert.equal(artifactContract.requiresExternalEvidence, true, "real chat artifact work lost its evidence gate");
54
+ const retainedReportContract = deriveScsTaskContract({
55
+ goal: [
56
+ "The completed evidence is already saved in tmp/reliability-evidence-pass.md and must remain read-only.",
57
+ "Read only missing bounded ranges of tmp/reliability-evidence-pass.md.",
58
+ "Rewrite agent-reliability-evidence-review.md as a concise decision document.",
59
+ "Rebuild sources.json so it contains only cited sources.",
60
+ ].join("\n"),
61
+ taskProfile: "research",
62
+ });
63
+ assert.deepEqual(
64
+ retainedReportContract.exactOutputPaths,
65
+ ["agent-reliability-evidence-review.md", "sources.json"],
66
+ "report continuation confused an existing saved input with rewrite/rebuild outputs"
67
+ );
68
+ assert.deepEqual(
69
+ retainedReportContract.exactInputPaths,
70
+ ["tmp/reliability-evidence-pass.md"],
71
+ "report continuation lost the read-only evidence input"
72
+ );
54
73
  assert.ok(artifactContract.requiredEvidence.some((item) => item.category === "artifact"));
55
74
  const scopedArtifactRootContract = deriveScsTaskContract({
56
75
  goal:
@@ -355,11 +374,176 @@ function noisyFullReadPair(index, generation) {
355
374
  ];
356
375
  }
357
376
 
377
+ function boundedOutputReadPair(index, generation) {
378
+ const id = `output-${generation}-${index}`;
379
+ return [
380
+ {
381
+ role: "assistant",
382
+ content: "",
383
+ reasoning_content: "Inspect the existing mutable output.",
384
+ tool_calls: [
385
+ {
386
+ id,
387
+ type: "function",
388
+ function: {
389
+ name: "read_file",
390
+ arguments: JSON.stringify({
391
+ path: "agent-reliability-evidence-review.md",
392
+ startLine: 1 + (index - 1) * 40,
393
+ lineLimit: 40,
394
+ }),
395
+ },
396
+ },
397
+ ],
398
+ },
399
+ {
400
+ role: "tool",
401
+ tool_call_id: id,
402
+ content: JSON.stringify({
403
+ ok: true,
404
+ toolName: "read_file",
405
+ path: "agent-reliability-evidence-review.md",
406
+ startLine: 1 + (index - 1) * 40,
407
+ lineLimit: 40,
408
+ lineCount: 240,
409
+ bytes: 24000,
410
+ sha256: `${generation}${String(index).padStart(2, "0")}`.repeat(24).slice(0, 64),
411
+ contentTruncated: false,
412
+ content: `MUTABLE-OUTPUT-${generation}-${index}\n${"old output content ".repeat(170)}`,
413
+ }),
414
+ },
415
+ ];
416
+ }
417
+
418
+ function exactInputEvidencePair(index) {
419
+ const id = `exact-input-${index}`;
420
+ const startLine = 1 + (index - 1) * 45;
421
+ return [
422
+ {
423
+ role: "assistant",
424
+ content: "",
425
+ reasoning_content: "Read one bounded exact-input evidence range.",
426
+ tool_calls: [
427
+ {
428
+ id,
429
+ type: "function",
430
+ function: {
431
+ name: "read_file",
432
+ arguments: JSON.stringify({
433
+ path: "tmp/reliability-evidence-pass.md",
434
+ startLine,
435
+ lineLimit: 45,
436
+ }),
437
+ },
438
+ },
439
+ ],
440
+ },
441
+ {
442
+ role: "tool",
443
+ tool_call_id: id,
444
+ content: JSON.stringify({
445
+ ok: true,
446
+ toolName: "read_file",
447
+ path: "tmp/reliability-evidence-pass.md",
448
+ startLine,
449
+ lineLimit: 45,
450
+ lineCount: 180,
451
+ bytes: 24000,
452
+ sha256: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
453
+ contentTruncated: false,
454
+ content: `EXACT-INPUT-RANGE-${index}\n${`source evidence ${index} `.repeat(220)}`,
455
+ }),
456
+ },
457
+ ];
458
+ }
459
+
460
+ function readOnlyDiagnosticPair(index) {
461
+ const id = `diagnostic-${index}`;
462
+ const command = `python3 -c "print('diagnostic ${index}')"`;
463
+ return [
464
+ {
465
+ role: "assistant",
466
+ content: "",
467
+ reasoning_content: "Inspect one diagnostic without changing task outputs.",
468
+ tool_calls: [
469
+ {
470
+ id,
471
+ type: "function",
472
+ function: { name: "run_command", arguments: JSON.stringify({ command }) },
473
+ },
474
+ ],
475
+ },
476
+ {
477
+ role: "tool",
478
+ tool_call_id: id,
479
+ content: JSON.stringify({
480
+ ok: true,
481
+ toolName: "run_command",
482
+ args: { command },
483
+ exitCode: 0,
484
+ stdout: `DIAGNOSTIC-${index}\n${"read-only shell output ".repeat(80)}`,
485
+ }),
486
+ },
487
+ ];
488
+ }
489
+
490
+ const exactInputCoverageState = {
491
+ goal: [
492
+ "Read tmp/reliability-evidence-pass.md as the exact read-only input.",
493
+ "Rewrite agent-reliability-evidence-review.md.",
494
+ "Rebuild sources.json.",
495
+ ].join("\n"),
496
+ plan: "Use retained evidence and create the two outputs.",
497
+ meta: {
498
+ scs: {
499
+ taskContract: {
500
+ exactInputPaths: ["tmp/reliability-evidence-pass.md"],
501
+ exactOutputPaths: ["agent-reliability-evidence-review.md", "sources.json"],
502
+ },
503
+ },
504
+ },
505
+ messages: [
506
+ { role: "system", content: `SYSTEM-INPUT-COVERAGE\n${"policy ".repeat(5000)}` },
507
+ { role: "user", content: "Create a source-grounded reader-facing report." },
508
+ ...Array.from({ length: 4 }, (_, index) => exactInputEvidencePair(index + 1)).flat(),
509
+ ...Array.from({ length: 10 }, (_, index) => readOnlyDiagnosticPair(index + 1)).flat(),
510
+ ...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 9)).flat(),
511
+ ],
512
+ };
513
+ const exactInputCoverage = buildContextBudgetCompactionMessages(
514
+ exactInputCoverageState,
515
+ { ...config, provider: "deepseek", model: "deepseek-chat" },
516
+ { title: "", url: "" },
517
+ 20,
518
+ { reason: "preserve all exact-input source ranges over diagnostics" }
519
+ );
520
+ const exactInputCoverageText = exactInputCoverage.map((message) => message.content || "").join("\n");
521
+ for (let index = 1; index <= 4; index += 1) {
522
+ assert.ok(
523
+ exactInputCoverageText.includes(`EXACT-INPUT-RANGE-${index}`),
524
+ `compaction lost exact input evidence range ${index}`
525
+ );
526
+ }
527
+ assert.ok(
528
+ estimateMessageTokens(exactInputCoverage) <= 12288,
529
+ "exact-input evidence retention exceeded the bounded retry target"
530
+ );
531
+
358
532
  const twiceCompactedState = {
359
533
  ...compactionState,
534
+ meta: {
535
+ scs: {
536
+ taskContract: {
537
+ exactInputPaths: ["reports/reliability.md"],
538
+ exactOutputPaths: ["agent-reliability-evidence-review.md", "sources.json"],
539
+ },
540
+ },
541
+ },
360
542
  messages: [
361
543
  ...deepSeekRuntimeMessages,
362
544
  ...Array.from({ length: 14 }, (_, index) => noisyFullReadPair(index + 1, 2)).flat(),
545
+ ...Array.from({ length: 8 }, (_, index) => readOnlyDiagnosticPair(index + 1)).flat(),
546
+ ...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 2)).flat(),
363
547
  ],
364
548
  };
365
549
  const twiceCompacted = buildContextBudgetCompactionMessages(
@@ -381,9 +565,11 @@ assert.equal(
381
565
 
382
566
  const thriceCompactedState = {
383
567
  ...compactionState,
568
+ meta: twiceCompactedState.meta,
384
569
  messages: [
385
570
  ...twiceCompacted,
386
571
  ...Array.from({ length: 14 }, (_, index) => noisyFullReadPair(index + 1, 3)).flat(),
572
+ ...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 3)).flat(),
387
573
  ],
388
574
  };
389
575
  const thriceCompacted = buildContextBudgetCompactionMessages(
@@ -9,6 +9,7 @@ import { flushHousekeeping } from "../src/housekeeping.js";
9
9
  import { requestNextStep, toolChoiceForProvider } from "../src/model-client.js";
10
10
  import { providerStructuredOutputAttempts } from "../src/provider-contract.js";
11
11
  import {
12
+ hasExplicitDeepResearchSuppression,
12
13
  hasExplicitDeepResearchIntent,
13
14
  hasLocalResearchWorkspaceIntent,
14
15
  shouldStartWithDeepResearch,
@@ -107,6 +108,50 @@ async function main() {
107
108
  shouldStartWithDeepResearch("Write a deep web research report comparing three primary papers."),
108
109
  "standalone deep research no longer starts with the bounded research workflow"
109
110
  );
111
+ const retainedResearchGoal = [
112
+ "Continue the interrupted evidence-review task from its saved state.",
113
+ "Do not run deep_research again. Reuse the completed research artifact.",
114
+ "Rewrite agent-reliability-evidence-review.md from the retained evidence.",
115
+ ].join("\n");
116
+ assert(
117
+ hasExplicitDeepResearchSuppression(retainedResearchGoal),
118
+ "an explicit completed-research reuse instruction was not detected"
119
+ );
120
+ assert(
121
+ !shouldStartWithDeepResearch(retainedResearchGoal, inspectedLocalEvidenceMessages),
122
+ "an explicit prohibition still narrowed the next turn to deep_research"
123
+ );
124
+ const coordinatedSuppressionGoal = [
125
+ "Resume the saved evidence-review task.",
126
+ "Do not restart the task, run deep_research, or reopen broad discovery.",
127
+ "Use the retained completed evidence and rebuild sources.json.",
128
+ ].join("\n");
129
+ assert(
130
+ hasExplicitDeepResearchSuppression(coordinatedSuppressionGoal),
131
+ "a coordinated do-not clause did not suppress deep_research"
132
+ );
133
+ assert(
134
+ !shouldStartWithDeepResearch(localEvidenceGoal, [
135
+ { role: "user", content: coordinatedSuppressionGoal },
136
+ ...inspectedLocalEvidenceMessages.slice(1),
137
+ ]),
138
+ "a resumed retained-evidence repair was forced back into deep_research after inspection"
139
+ );
140
+ assert(
141
+ shouldStartWithDeepResearch(localEvidenceGoal, [
142
+ ...inspectedLocalEvidenceMessages,
143
+ { role: "user", content: "Run a fresh deep research pass now; the retained evidence is stale." },
144
+ {
145
+ role: "assistant",
146
+ tool_calls: [{ id: "fresh-report", function: { name: "read_file", arguments: '{"path":"agent-reliability-evidence-review.md"}' } }],
147
+ },
148
+ {
149
+ role: "assistant",
150
+ tool_calls: [{ id: "fresh-sources", function: { name: "read_file", arguments: '{"path":"sources.json"}' } }],
151
+ },
152
+ ]),
153
+ "an older completed-research context suppressed a newer explicit refresh request"
154
+ );
110
155
  assert(
111
156
  !hasExplicitDeepResearchIntent("Create a phone-friendly document from this folder.", [
112
157
  {
@@ -1364,6 +1364,33 @@ try {
1364
1364
  `a status wrapper changed the inner command's mutation identity: ${command}`
1365
1365
  );
1366
1366
  }
1367
+ const readOnlyValidatorState = { meta: { goalContract: { revision: 1 } } };
1368
+ const readOnlyValidatorResult = {
1369
+ toolName: "run_command",
1370
+ ok: true,
1371
+ exitCode: 0,
1372
+ args: {
1373
+ command:
1374
+ 'python3 tmp/external_agent_reliability_quality.py . ; echo "EXIT=$?"',
1375
+ },
1376
+ stdout: "agent reliability research contract passed\nEXIT=1\n",
1377
+ stderr: "agent reliability research quality failed\n",
1378
+ commandPolicy: {
1379
+ category: "toolchain",
1380
+ writesWorkspace: true,
1381
+ mayMutateProject: false,
1382
+ substantiveTest: false,
1383
+ },
1384
+ };
1385
+ recordProjectVerificationOutcome(readOnlyValidatorState, readOnlyValidatorResult, {
1386
+ commandCwd: workspace,
1387
+ taskProfile: "writing",
1388
+ });
1389
+ assert(
1390
+ readOnlyValidatorState.meta.projectVerification?.mutationRevision === 0 &&
1391
+ readOnlyValidatorResult.projectMutationRevision === 0,
1392
+ "a semantically read-only validator fabricated project mutation progress"
1393
+ );
1367
1394
  const shellMutationState = { meta: { goalContract: { revision: 1 } } };
1368
1395
  const shellMutationResult = {
1369
1396
  toolName: "run_command",
@@ -2479,6 +2506,13 @@ try {
2479
2506
  args: { command: "npm test && git pull --ff-only" },
2480
2507
  stdout: "1 test passed",
2481
2508
  stderr: "",
2509
+ commandPolicy: {
2510
+ category: "git-remote",
2511
+ writesWorkspace: true,
2512
+ mayMutateProject: false,
2513
+ substantiveTest: true,
2514
+ gitOnly: false,
2515
+ },
2482
2516
  };
2483
2517
  recordProjectVerificationOutcome(testBeforePullState, testBeforePullResult, {
2484
2518
  commandCwd: workspace,
@@ -3067,6 +3101,21 @@ try {
3067
3101
  shouldResetStaticDiscoveryPhase({ ok: true, toolName: "write_file", args: { path: "report.md" } }),
3068
3102
  "successful output creation should reset static discovery convergence"
3069
3103
  );
3104
+ assert(
3105
+ !shouldResetStaticDiscoveryPhase({
3106
+ ok: true,
3107
+ toolName: "run_command",
3108
+ args: { command: "python3 - <<'PY'\nprint('inspect only')\nPY" },
3109
+ commandPolicy: {
3110
+ writesWorkspace: true,
3111
+ mayMutateProject: false,
3112
+ substantiveTest: false,
3113
+ },
3114
+ exitCode: 0,
3115
+ stdout: "inspect only",
3116
+ }),
3117
+ "a read-only diagnostic shell probe reset static discovery because of a conservative write heuristic"
3118
+ );
3070
3119
  const uniqueDiscovery = {};
3071
3120
  recordStaticDiscoveryProgress(uniqueDiscovery, "read_file:/reference/A.md");
3072
3121
  recordStaticDiscoveryProgress(uniqueDiscovery, "read_file:/reference/A.md");
@@ -3095,6 +3144,32 @@ try {
3095
3144
  JSON.stringify(compactedDiscoveryState.meta.toolLoop.warned) === JSON.stringify(["run_command:keep"]),
3096
3145
  "context recovery did not clear only stale static-read warnings"
3097
3146
  );
3147
+ const retainedDiscoveryState = {
3148
+ meta: {
3149
+ toolLoop: {
3150
+ recent: [],
3151
+ warned: ["file-read:/reference/A.md"],
3152
+ staticCounts: { "file-read:/reference/A.md": 2 },
3153
+ staticOrder: ["file-read:/reference/A.md"],
3154
+ staticTotal: 1,
3155
+ staticCallTotal: 2,
3156
+ },
3157
+ },
3158
+ };
3159
+ resetStaticDiscoveryAfterContextLoss(
3160
+ retainedDiscoveryState,
3161
+ "proactive-context-compaction",
3162
+ { preserveStaticEvidence: true }
3163
+ );
3164
+ assert(
3165
+ retainedDiscoveryState.meta.toolLoop.staticTotal === 1 &&
3166
+ retainedDiscoveryState.meta.toolLoop.staticCounts["file-read:/reference/A.md"] === 2,
3167
+ "lossless context compaction reopened already completed discovery"
3168
+ );
3169
+ assert(
3170
+ retainedDiscoveryState.meta.toolLoop.lastContextRecovery?.preservedStaticEvidence === true,
3171
+ "lossless context compaction did not record preserved discovery evidence"
3172
+ );
3098
3173
  const exactReadSignature = staticToolCallSignature("read_file", { path: "/reference/A.md" }, {
3099
3174
  commandCwd: workspace,
3100
3175
  });
@@ -6065,6 +6140,35 @@ try {
6065
6140
  events: [],
6066
6141
  });
6067
6142
  assert(!staticOnlyDecision.approved, "budget gate treated static discovery alone as implementation progress");
6143
+ const readOnlyShellDecision = decideStepBudgetExtension({
6144
+ config: { scsActive: true, commandCwd: "/tmp/workspace" },
6145
+ budget: createStepBudgetState(
6146
+ { provider: "localllm", maxSteps: 12, dynamicSteps: "on", dynamicStepExtensionLimit: 2, scsActive: true },
6147
+ { meta: {}, stepsCompleted: 0 }
6148
+ ),
6149
+ step: 11,
6150
+ state: {
6151
+ messages: Array.from({ length: 5 }, (_, index) =>
6152
+ toolMessage({
6153
+ toolName: "run_command",
6154
+ ok: true,
6155
+ args: { command: `python3 -c "print('inspect ${index}')"` },
6156
+ commandPolicy: {
6157
+ writesWorkspace: true,
6158
+ mayMutateProject: false,
6159
+ substantiveTest: false,
6160
+ },
6161
+ exitCode: 0,
6162
+ stdout: `inspection ${index}`,
6163
+ })
6164
+ ),
6165
+ },
6166
+ events: [],
6167
+ });
6168
+ assert(
6169
+ !readOnlyShellDecision.approved,
6170
+ "budget gate extended a run containing only read-only diagnostic shell output"
6171
+ );
6068
6172
 
6069
6173
  const mockAutoBudget = createStepBudgetState({ provider: "mock", maxSteps: 4, dynamicSteps: "auto" }, { meta: {}, stepsCompleted: 0 });
6070
6174
  assert(!mockAutoBudget.enabled, "mock provider should not auto-extend unless explicitly enabled");
@@ -1515,6 +1515,43 @@ sameNames(
1515
1515
  ["deep_research", "finish"],
1516
1516
  "local evidence research did not enter the bounded research engine after inspection"
1517
1517
  );
1518
+ const retainedEvidenceManifestRepair = selectProgressiveTools(allTools, {
1519
+ config: { provider: "deepseek" },
1520
+ goal:
1521
+ "Investigate the reliability problem in this folder, write an evidence review and sources.json, then commit the intentional work.",
1522
+ profile: "research",
1523
+ messages: [
1524
+ {
1525
+ role: "user",
1526
+ content: [
1527
+ "Resume the saved evidence-review task.",
1528
+ "Do not restart the task, run deep_research, or reopen broad discovery.",
1529
+ "Use the retained completed evidence and rebuild sources.json.",
1530
+ ].join("\n"),
1531
+ },
1532
+ {
1533
+ role: "assistant",
1534
+ tool_calls: [{ id: "retained-sources", function: { name: "read_file", arguments: '{"path":"sources.json"}' } }],
1535
+ },
1536
+ {
1537
+ role: "assistant",
1538
+ tool_calls: [{ id: "retained-evidence", function: { name: "read_file", arguments: '{"path":"tmp/reliability-evidence-pass.md"}' } }],
1539
+ },
1540
+ ],
1541
+ });
1542
+ assert(
1543
+ names(retainedEvidenceManifestRepair).includes("write_file"),
1544
+ "retained-evidence manifest repair omitted write_file after inspection"
1545
+ );
1546
+ assert(
1547
+ names(retainedEvidenceManifestRepair).includes("read_file"),
1548
+ "retained-evidence manifest repair omitted bounded source reads"
1549
+ );
1550
+ assert(
1551
+ !(names(retainedEvidenceManifestRepair).length === 2 &&
1552
+ names(retainedEvidenceManifestRepair)[0] === "deep_research"),
1553
+ "explicit retained-evidence reuse was forced back into deep_research"
1554
+ );
1518
1555
  const localEvidenceAfterDeepResearchCompaction = selectProgressiveTools(allTools, {
1519
1556
  config: { provider: "deepseek" },
1520
1557
  goal:
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { machineRunPayload } from "../src/cli.js";
7
+ import { machineRunPayload, runResultExitCode } from "../src/cli.js";
8
8
  import { showProjectSession } from "../src/project.js";
9
9
 
10
10
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -36,6 +36,12 @@ const stoppedPayload = machineRunPayload({
36
36
  if (stoppedPayload.ok !== false || stoppedPayload.failed !== true || stoppedPayload.stopped !== true) {
37
37
  throw new Error(`stopped machine run was reported as success: ${JSON.stringify(stoppedPayload)}`);
38
38
  }
39
+ if (runResultExitCode({ stopped: true, reason: "tool_contract_violation" }) !== 1) {
40
+ throw new Error("a stopped non-JSON agent run still reports shell success");
41
+ }
42
+ if (runResultExitCode({ stopped: false, result: "done" }) !== 0) {
43
+ throw new Error("a successful agent run was assigned a failing shell exit status");
44
+ }
39
45
 
40
46
  async function runMachine(label, commandArgs, stdin = "") {
41
47
  const command = [cliPath, ...commandArgs];
@@ -264,6 +264,27 @@ assert(
264
264
  "excluding output filenames also removed a real required text term"
265
265
  );
266
266
 
267
+ const wrappedManifestRepairContract = deriveScsTaskContract({
268
+ goal: [
269
+ "The only unresolved content work",
270
+ "is rebuilding the stale sources.json for the current report.",
271
+ "The verified claims are retained in",
272
+ "tmp/reliability-evidence-pass.md. Then write",
273
+ "sources.json immediately. Do not read any other file.",
274
+ ].join("\n"),
275
+ taskProfile: "research",
276
+ });
277
+ assert.deepEqual(
278
+ wrappedManifestRepairContract.exactOutputPaths,
279
+ ["sources.json"],
280
+ "an inflected, soft-wrapped output instruction did not classify its manifest as an exact output"
281
+ );
282
+ assert.deepEqual(
283
+ wrappedManifestRepairContract.exactInputPaths,
284
+ ["tmp/reliability-evidence-pass.md"],
285
+ "a mutable exact output leaked into exact inputs through a later negated read clause"
286
+ );
287
+
267
288
  const wordDocumentContract = deriveScsTaskContract({
268
289
  goal: "Create an editable DOCX and a phone-friendly PDF, then verify both outputs.",
269
290
  taskProfile: "word",
@@ -995,7 +995,7 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
995
995
  result.contentTruncatedByLines = payload.contentTruncatedByLines === true;
996
996
  if (payload.sha256) result.sha256 = compactSingleLine(payload.sha256, 96);
997
997
  const content = String(payload.content || payload.contentPreview || "");
998
- if (content) result.content = compactMultiline(content, 4200);
998
+ if (content) result.content = compactMultiline(content, 3000);
999
999
  if (Array.isArray(payload.pathEvidence)) {
1000
1000
  result.pathEvidence = compactPathItems(payload.pathEvidence, 12, ["path", "source"]);
1001
1001
  }
@@ -1018,7 +1018,22 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
1018
1018
  return redactValue(result);
1019
1019
  }
1020
1020
 
1021
- function retainedToolRecordPriority(record = {}) {
1021
+ function retainedPathMatchesAny(sourcePath = "", candidatePaths = []) {
1022
+ const normalize = (value = "") =>
1023
+ String(value || "")
1024
+ .replace(/\\/g, "/")
1025
+ .replace(/^\.\//, "")
1026
+ .replace(/\/{2,}/g, "/")
1027
+ .replace(/\/$/, "");
1028
+ const source = normalize(sourcePath);
1029
+ if (!source) return false;
1030
+ return candidatePaths.some((candidate) => {
1031
+ const output = normalize(candidate);
1032
+ return output && (source === output || source.endsWith(`/${output}`) || output.endsWith(`/${source}`));
1033
+ });
1034
+ }
1035
+
1036
+ function retainedToolRecordPriority(record = {}, outputPaths = [], inputPaths = []) {
1022
1037
  const name = String(record.name || "");
1023
1038
  const args = record.args || {};
1024
1039
  const payload = record.payload || {};
@@ -1032,11 +1047,14 @@ function retainedToolRecordPriority(record = {}) {
1032
1047
  } else if (name === "read_file") {
1033
1048
  const range = retainedReadRange(payload, args);
1034
1049
  priority = range.lineLimit > 0 ? 750 : 600;
1050
+ const sourcePath = String(payload.path || args.path || "").trim();
1051
+ if (retainedPathMatchesAny(sourcePath, inputPaths)) priority += 260;
1052
+ if (retainedPathMatchesAny(sourcePath, outputPaths)) priority -= 220;
1035
1053
  } else if (["search_files", "list_files"].includes(name)) priority = 400;
1036
1054
  return priority + Math.min(0.999, Math.max(0, Number(record.ordinal) || 0) / 100000);
1037
1055
  }
1038
1056
 
1039
- function retainedToolStateMessages(messages = [], limit = 12) {
1057
+ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [], inputPaths = []) {
1040
1058
  const callsById = new Map();
1041
1059
  const recordsByKey = new Map();
1042
1060
  let ordinal = 0;
@@ -1086,7 +1104,8 @@ function retainedToolStateMessages(messages = [], limit = 12) {
1086
1104
  const selected = [...records]
1087
1105
  .sort(
1088
1106
  (left, right) =>
1089
- retainedToolRecordPriority(right) - retainedToolRecordPriority(left) ||
1107
+ retainedToolRecordPriority(right, outputPaths, inputPaths) -
1108
+ retainedToolRecordPriority(left, outputPaths, inputPaths) ||
1090
1109
  right.ordinal - left.ordinal
1091
1110
  )
1092
1111
  .slice(0, Math.max(1, Number(limit) || 12))
@@ -1118,8 +1137,8 @@ function retainedToolStateMessages(messages = [], limit = 12) {
1118
1137
  });
1119
1138
  }
1120
1139
 
1121
- function retainedToolStateTextMessages(messages = [], limit = 12) {
1122
- const nativeMessages = retainedToolStateMessages(messages, limit);
1140
+ function retainedToolStateTextMessages(messages = [], limit = 12, outputPaths = [], inputPaths = []) {
1141
+ const nativeMessages = retainedToolStateMessages(messages, limit, outputPaths, inputPaths);
1123
1142
  const retained = [];
1124
1143
  for (let index = 0; index < nativeMessages.length; index += 2) {
1125
1144
  const assistantMessage = nativeMessages[index];
@@ -1139,13 +1158,17 @@ function retainedToolStateTextMessages(messages = [], limit = 12) {
1139
1158
  return retained;
1140
1159
  }
1141
1160
 
1142
- function retainedToolPairPriority(pair = [], order = 0) {
1161
+ function retainedToolPairPriority(pair = [], order = 0, outputPaths = [], inputPaths = []) {
1143
1162
  const assistantCall = pair[0]?.tool_calls?.[0];
1144
1163
  const retained = pair.length === 1 ? parseRetainedToolEvidenceMessage(pair[0]) : null;
1145
1164
  const name = String(retained?.name || assistantCall?.function?.name || "");
1146
1165
  const args = retained?.args || safeParseToolContent(assistantCall?.function?.arguments) || {};
1147
1166
  const payload = retained?.payload || safeParseToolContent(pair[1]?.content) || {};
1148
- return retainedToolRecordPriority({ name, args, payload, ordinal: order });
1167
+ return retainedToolRecordPriority(
1168
+ { name, args, payload, ordinal: order },
1169
+ outputPaths,
1170
+ inputPaths
1171
+ );
1149
1172
  }
1150
1173
 
1151
1174
  function isRuntimeCompactionRequest(content = "") {
@@ -1327,9 +1350,11 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1327
1350
  // synthetic pairs, so preserve their bounded evidence as explicit runtime
1328
1351
  // context instead of fabricating assistant reasoning.
1329
1352
  const deepSeekCompaction = normalizeProviderId(config.provider, "") === "deepseek";
1353
+ const exactOutputPaths = exactOutputPathsForState(state);
1354
+ const exactInputPaths = exactInputPathsForState(state);
1330
1355
  const retainedToolMessages = deepSeekCompaction
1331
- ? retainedToolStateTextMessages(messages)
1332
- : retainedToolStateMessages(messages);
1356
+ ? retainedToolStateTextMessages(messages, 12, exactOutputPaths, exactInputPaths)
1357
+ : retainedToolStateMessages(messages, 12, exactOutputPaths, exactInputPaths);
1333
1358
  const snapshotSummary = {
1334
1359
  step,
1335
1360
  maxSteps: config.maxSteps,
@@ -1407,7 +1432,7 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1407
1432
  }));
1408
1433
  const boundedContent = compactTextForTokenBudget(
1409
1434
  compactedContent,
1410
- Math.max(1024, Math.floor(targetTokens * 0.52)),
1435
+ Math.max(1024, Math.floor(targetTokens * (retainedToolMessages.length ? 0.28 : 0.52))),
1411
1436
  { headFraction: 0.58 }
1412
1437
  );
1413
1438
  const baseMessages = [
@@ -1423,7 +1448,12 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
1423
1448
  retainedPairs.push({
1424
1449
  pair,
1425
1450
  order: retainedPairs.length,
1426
- priority: retainedToolPairPriority(pair, retainedPairs.length),
1451
+ priority: retainedToolPairPriority(
1452
+ pair,
1453
+ retainedPairs.length,
1454
+ exactOutputPaths,
1455
+ exactInputPaths
1456
+ ),
1427
1457
  });
1428
1458
  }
1429
1459
  const selectedPairs = [];
@@ -4338,7 +4368,12 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
4338
4368
  // authorize a new invocation of the same command. A recognized trailing
4339
4369
  // status probe reports evidence but does not change the inner command's
4340
4370
  // mutation capability.
4341
- const commandPolicy = classifyCommand(mutationCommand);
4371
+ const commandPolicy = {
4372
+ ...classifyCommand(mutationCommand),
4373
+ ...(toolResult.commandPolicy && typeof toolResult.commandPolicy === "object"
4374
+ ? toolResult.commandPolicy
4375
+ : {}),
4376
+ };
4342
4377
  const requiredCommands = effectiveRequiredProjectCommands(state, verification, config);
4343
4378
  const requiredCommand = requiredCommands.find(
4344
4379
  (candidate) => projectCommandsEquivalent(candidate, exitProbe.command || command, config)
@@ -5317,16 +5352,31 @@ function expectedRepeatedObservationCommand(command = "") {
5317
5352
  );
5318
5353
  }
5319
5354
 
5355
+ function runCommandResultHasDurableProgress(toolResult = {}) {
5356
+ const policy = toolResult.commandPolicy || {};
5357
+ const policyAllowsMutation =
5358
+ policy.mayMutateProject === true ||
5359
+ (policy.mayMutateProject === undefined && policy.writesWorkspace === true);
5360
+ return Boolean(
5361
+ policyAllowsMutation ||
5362
+ policy.substantiveTest === true ||
5363
+ (Array.isArray(toolResult.verifiedGeneratedOutputPaths) &&
5364
+ toolResult.verifiedGeneratedOutputPaths.length > 0)
5365
+ );
5366
+ }
5367
+
5320
5368
  function isStaticDiscoveryToolResult(toolResult = {}) {
5321
5369
  if (isStaticDiscoveryToolCall(toolResult.toolName, toolResult.args || {})) return true;
5322
5370
  if (toolResult.toolName !== "run_command") return false;
5323
- if (toolResult.commandPolicy?.writesWorkspace !== false) return false;
5371
+ if (runCommandResultHasDurableProgress(toolResult)) return false;
5324
5372
  return !expectedRepeatedObservationCommand(toolResult.args?.command);
5325
5373
  }
5326
5374
 
5327
5375
  function successfulToolStateProgress(toolResult = {}) {
5328
5376
  if (!toolResult || toolResult.done || toolResult.ok === false || toolResult.blocked || toolResult.skipped) return false;
5329
- if (toolResult.toolName === "run_command") return toolResult.commandPolicy?.writesWorkspace === true;
5377
+ if (toolResult.toolName === "run_command") {
5378
+ return runCommandResultHasDurableProgress(toolResult);
5379
+ }
5330
5380
  if (["write_file", "apply_patch"].includes(String(toolResult.toolName || ""))) {
5331
5381
  return successfulProjectMutationPaths(toolResult).length > 0;
5332
5382
  }
@@ -5347,7 +5397,7 @@ function noProgressOutcomeFingerprint(toolResult = {}) {
5347
5397
  toolResult?.toolName !== "run_command" ||
5348
5398
  toolResult?.ok === false ||
5349
5399
  toolResult?.blocked ||
5350
- toolResult?.commandPolicy?.writesWorkspace === true ||
5400
+ successfulToolStateProgress(toolResult) ||
5351
5401
  expectedRepeatedObservationCommand(toolResult?.args?.command)
5352
5402
  ) {
5353
5403
  return "";
@@ -6161,6 +6211,16 @@ function commandWritesOnlyPrivateVerificationEvidence(command = "") {
6161
6211
  }
6162
6212
 
6163
6213
  function commandCanMutateProjectContent(command = "", commandPolicy = {}) {
6214
+ // The classifier may conservatively mark an interpreter or compound shell
6215
+ // command as workspace-writing while still proving that this exact command
6216
+ // cannot mutate project content. Preserve that stronger semantic result so
6217
+ // validators and inspection probes do not fabricate mutation progress. Git
6218
+ // sequences remain structurally inspected because an aggregate Git policy
6219
+ // can be conservative even when one segment changes the worktree.
6220
+ const category = String(commandPolicy.category || "");
6221
+ const requiresGitMutationInspection =
6222
+ ["git-workflow", "git-remote"].includes(category);
6223
+ if (commandPolicy.mayMutateProject === false && !requiresGitMutationInspection) return false;
6164
6224
  if (commandPolicy.writesWorkspace !== true && commandPolicy.mayMutateProject !== true) return false;
6165
6225
  const sequence = parseTopLevelShellSequence(String(command || ""));
6166
6226
  if (
@@ -6174,7 +6234,6 @@ function commandCanMutateProjectContent(command = "", commandPolicy = {}) {
6174
6234
  );
6175
6235
  }
6176
6236
  if (commandWritesOnlyPrivateVerificationEvidence(command)) return false;
6177
- const category = String(commandPolicy.category || "");
6178
6237
  if (!["git-workflow", "git-remote"].includes(category)) return true;
6179
6238
  if (/\bgit\s+clone\b/i.test(String(command || ""))) return true;
6180
6239
  // An aggregate Git category can still contain a non-Git build/generator
@@ -6682,6 +6741,18 @@ function exactOutputPathsForState(state = {}) {
6682
6741
  ])].slice(0, 32);
6683
6742
  }
6684
6743
 
6744
+ function exactInputPathsForState(state = {}) {
6745
+ const scsInputPaths = Array.isArray(state.meta?.scs?.taskContract?.exactInputPaths)
6746
+ ? state.meta.scs.taskContract.exactInputPaths.filter(Boolean)
6747
+ : [];
6748
+ const outputPaths = new Set(
6749
+ exactOutputPathsForState(state).map((item) => String(item).replace(/\\/g, "/").replace(/^\.\//, ""))
6750
+ );
6751
+ return [...new Set(scsInputPaths)]
6752
+ .filter((item) => !outputPaths.has(String(item).replace(/\\/g, "/").replace(/^\.\//, "")))
6753
+ .slice(0, 32);
6754
+ }
6755
+
6685
6756
  async function hashExactOutputFile(absolutePath) {
6686
6757
  return await new Promise((resolve, reject) => {
6687
6758
  const digest = crypto.createHash("sha256");
@@ -7296,7 +7367,11 @@ export function recordStaticDiscoveryProgress(toolLoop = {}, signature = "") {
7296
7367
  };
7297
7368
  }
7298
7369
 
7299
- export function resetStaticDiscoveryAfterContextLoss(state = {}, reason = "context-compaction") {
7370
+ export function resetStaticDiscoveryAfterContextLoss(
7371
+ state = {},
7372
+ reason = "context-compaction",
7373
+ options = {}
7374
+ ) {
7300
7375
  state.meta = state.meta || {};
7301
7376
  const toolLoop = state.meta.toolLoop && typeof state.meta.toolLoop === "object"
7302
7377
  ? state.meta.toolLoop
@@ -7305,6 +7380,16 @@ export function resetStaticDiscoveryAfterContextLoss(state = {}, reason = "conte
7305
7380
  const priorCounts = toolLoop.staticCounts && typeof toolLoop.staticCounts === "object"
7306
7381
  ? toolLoop.staticCounts
7307
7382
  : {};
7383
+ if (options.preserveStaticEvidence === true) {
7384
+ toolLoop.lastContextRecovery = {
7385
+ reason: String(reason || "context-compaction"),
7386
+ at: new Date().toISOString(),
7387
+ priorStaticTotal: Number(priorOrder.length),
7388
+ preservedStaticEvidence: true,
7389
+ };
7390
+ state.meta.toolLoop = toolLoop;
7391
+ return toolLoop.lastContextRecovery;
7392
+ }
7308
7393
  if (priorOrder.length || Object.keys(priorCounts).length) {
7309
7394
  const history = Array.isArray(toolLoop.staticHistory) ? toolLoop.staticHistory : [];
7310
7395
  history.push({
@@ -11025,7 +11110,9 @@ export async function runAgent(config) {
11025
11110
  const tokensAfter = estimateMessageTokens(compactMessages);
11026
11111
  if (charsAfter < contextDecision.charsBefore) {
11027
11112
  state.messages = compactMessages;
11028
- resetStaticDiscoveryAfterContextLoss(state, "proactive-context-compaction");
11113
+ resetStaticDiscoveryAfterContextLoss(state, "proactive-context-compaction", {
11114
+ preserveStaticEvidence: true,
11115
+ });
11029
11116
  state.meta.contextBudget = recordContextCompaction(contextBudget, {
11030
11117
  step,
11031
11118
  charsBefore: contextDecision.charsBefore,
@@ -11141,7 +11228,9 @@ export async function runAgent(config) {
11141
11228
  };
11142
11229
  state.messages = compactMessages;
11143
11230
  requestMessages = compactMessages;
11144
- resetStaticDiscoveryAfterContextLoss(state, "local-context-budget-retry");
11231
+ resetStaticDiscoveryAfterContextLoss(state, "local-context-budget-retry", {
11232
+ preserveStaticEvidence: true,
11233
+ });
11145
11234
  state.meta.localContextBudgetRetries = {
11146
11235
  ...contextRetriedSteps,
11147
11236
  [retryKey]: true,
@@ -11189,7 +11278,9 @@ export async function runAgent(config) {
11189
11278
  };
11190
11279
  state.messages = compactMessages;
11191
11280
  requestMessages = compactMessages;
11192
- resetStaticDiscoveryAfterContextLoss(state, "model-timeout-retry");
11281
+ resetStaticDiscoveryAfterContextLoss(state, "model-timeout-retry", {
11282
+ preserveStaticEvidence: true,
11283
+ });
11193
11284
  state.meta.modelTimeoutRetries = {
11194
11285
  ...retriedSteps,
11195
11286
  [retryKey]: true,
package/src/cli.js CHANGED
@@ -383,6 +383,10 @@ export function machineRunPayload(run, fallbackSessionId = "", metadata = {}) {
383
383
  };
384
384
  }
385
385
 
386
+ export function runResultExitCode(run = {}) {
387
+ return run?.stopped === true || run?.failed === true ? 1 : 0;
388
+ }
389
+
386
390
  function printMachineRunResult(run, fallbackSessionId = "", metadata = {}) {
387
391
  const payload = machineRunPayload(run, fallbackSessionId, metadata);
388
392
  console.log(JSON.stringify(payload));
@@ -2507,6 +2511,7 @@ export async function main(argv = process.argv.slice(2)) {
2507
2511
  ...(jsonFlag ? { onConsole: () => {} } : {}),
2508
2512
  });
2509
2513
  const run = await runAgent(config);
2514
+ if (runResultExitCode(run) !== 0) process.exitCode = 1;
2510
2515
  if (jsonFlag) {
2511
2516
  printMachineRunResult(run, runArgs.sessionId, {
2512
2517
  provider: config.provider,
@@ -2604,6 +2609,7 @@ export async function main(argv = process.argv.slice(2)) {
2604
2609
  config.expectedRuntimeRevision = preparedRuntime.expectedRuntimeRevision;
2605
2610
  if (preparedRuntime.runtimePatch) config.runtimePatch = preparedRuntime.runtimePatch;
2606
2611
  const run = await runAgent(config);
2612
+ if (runResultExitCode(run) !== 0) process.exitCode = 1;
2607
2613
  if (jsonFlag) {
2608
2614
  printMachineRunResult(run, sessionId, {
2609
2615
  provider: config.provider,
@@ -2765,5 +2771,6 @@ export async function main(argv = process.argv.slice(2)) {
2765
2771
  config.expectedRuntimeRevision = preparedRuntime.expectedRuntimeRevision;
2766
2772
  if (preparedRuntime.runtimePatch) config.runtimePatch = preparedRuntime.runtimePatch;
2767
2773
  }
2768
- await runAgent(config);
2774
+ const run = await runAgent(config);
2775
+ if (runResultExitCode(run) !== 0) process.exitCode = 1;
2769
2776
  }
@@ -95,12 +95,30 @@ export function hasLocalResearchWorkspaceIntent(goal = "", messages = []) {
95
95
  );
96
96
  }
97
97
 
98
+ export function hasExplicitDeepResearchSuppression(goal = "", messages = []) {
99
+ const current = currentIntentMessages(messages);
100
+ const latestUserIntent = [...current]
101
+ .reverse()
102
+ .find((message) => message?.role === "user" && !isRuntimeUserMessage(messageText(message.content)));
103
+ const text = `${scopedChatopsEvidenceGoal(goal)}\n${scopedChatopsEvidenceGoal(
104
+ messageText(latestUserIntent?.content)
105
+ )}`;
106
+ return (
107
+ /\b(?:do not|don't|must not|never)\s+(?:run|rerun|re-run|repeat|restart|invoke|call|start)\s+(?:the\s+)?(?:deep[_ -]?research|research workflow)\b/i.test(text) ||
108
+ /\b(?:do not|don't|must not|never)\b[^.!?\n]{0,140}\b(?:run|rerun|re-run|repeat|restart|invoke|call|start)\s+(?:the\s+)?(?:deep[_ -]?research|research workflow)\b/i.test(text) ||
109
+ /\b(?:reuse|use|continue from|recover from)\b.{0,140}\b(?:completed|existing|retained|saved)\b.{0,180}\b(?:deep[_ -]?research|research (?:result|artifact|evidence|pass))\b/i.test(text) ||
110
+ /(?:不要|无需|不必|禁止).{0,20}(?:重新|再次|重复)?(?:运行|调用|启动)?(?:深度研究|深入研究|deep[_ -]?research)/iu.test(text) ||
111
+ /(?:ディープリサーチ|深い調査).{0,20}(?:再実行しない|繰り返さない|呼び出さない)/u.test(text)
112
+ );
113
+ }
114
+
98
115
  export function toolWasRequested(messages = [], toolName = "") {
99
116
  return requestedToolCount(messages, toolName) > 0;
100
117
  }
101
118
 
102
119
  export function shouldStartWithDeepResearch(goal = "", messages = []) {
103
120
  if (!hasExplicitDeepResearchIntent(goal, messages)) return false;
121
+ if (hasExplicitDeepResearchSuppression(goal, messages)) return false;
104
122
  const current = currentIntentMessages(messages);
105
123
  if (toolWasRequested(current, "deep_research")) return false;
106
124
  if (hasLocalResearchWorkspaceIntent(goal, messages) && !localWorkspaceInspectionReady(current)) return false;
@@ -373,9 +373,9 @@ function inferExactOutputPaths(goal = "") {
373
373
  "gi"
374
374
  );
375
375
  const directOutputAction =
376
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/i;
376
+ /\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
377
377
  const directOutputActionGlobal =
378
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/gi;
378
+ /\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/gi;
379
379
  const outputListHeader =
380
380
  /^(?:#+\s*)?(?:(?:required|final|expected|declared|target|pilot|deliverable)\s+)*(?:create|created files?|files? to create|outputs?|output structure|required outputs?|artifacts?|deliverables?|generated files?|writer requirements|renderer requirements|生成文件|输出结构|輸出結構|输出文件|輸出文件|创建文件|建立文件)(?:\s+(?:outputs?|artifacts?|deliverables?))?\s*[::]?\s*$/i;
381
381
  const nonOutputToolLine =
@@ -410,8 +410,15 @@ function inferExactOutputPaths(goal = "") {
410
410
  }
411
411
  return matches[0]?.index ?? -1;
412
412
  };
413
- for (const rawLine of lines) {
414
- const line = String(rawLine || "").trim();
413
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
414
+ const rawLine = lines[lineIndex];
415
+ const currentLine = String(rawLine || "").trim();
416
+ const previousLine = String(lines[lineIndex - 1] || "").trim();
417
+ const wrappedOutputInstruction =
418
+ previousLine &&
419
+ !/[.!?。!?;;]$/.test(previousLine) &&
420
+ directOutputAction.test(previousLine);
421
+ const line = wrappedOutputInstruction ? `${previousLine} ${currentLine}`.trim() : currentLine;
415
422
  if (!line) {
416
423
  if (inOutputList) inOutputList = false;
417
424
  continue;
@@ -468,16 +475,23 @@ function inferExactInputPaths(goal = "") {
468
475
  "gi"
469
476
  );
470
477
  const inputAction =
471
- /\b(use|using|read|load|fill|upload|attach|import|select|choose|reference|input|from|fix|repair|patch|correct)\b|使用|读取|讀取|加载|載入|填写|填入|上传|上傳|附加|导入|導入|选择|選擇|选取|選取|参考|參考|素材|图片|圖片|照片|提示词|提示詞|修复|修正|更正|从|從/i;
478
+ /\b(?:use|using|read|load|fill|upload|attach|import|select|choose|reference|input|from|retain(?:ed|ing)?|fix|repair|patch|correct)\b|使用|读取|讀取|加载|載入|填写|填入|上传|上傳|附加|导入|導入|选择|選擇|选取|選取|参考|參考|素材|图片|圖片|照片|提示词|提示詞|保留|修复|修正|更正|从|從/i;
472
479
  const directOutputAction =
473
- /\b(save|saved|write|written|output|create|store|update|modify|edit)\b|保存|写入|寫入|输出|輸出|创建|建立|更新|修改|编辑|編輯/i;
480
+ /\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
474
481
  const pushPath = (raw = "") => {
475
482
  const cleaned = String(raw || "").trim();
476
483
  if (!cleaned || /[{}]/.test(cleaned)) return;
477
484
  paths.push(cleaned);
478
485
  };
479
- for (const rawLine of lines) {
480
- const fullLine = String(rawLine || "").trim();
486
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
487
+ const rawLine = lines[lineIndex];
488
+ const currentLine = String(rawLine || "").trim();
489
+ const previousLine = String(lines[lineIndex - 1] || "").trim();
490
+ const wrappedInputInstruction =
491
+ previousLine &&
492
+ !/[.!?。!?;;]$/.test(previousLine) &&
493
+ inputAction.test(previousLine);
494
+ const fullLine = wrappedInputInstruction ? `${previousLine} ${currentLine}`.trim() : currentLine;
481
495
  const outputIndex = fullLine.search(directOutputAction);
482
496
  const line = outputIndex > 0 ? fullLine.slice(0, outputIndex).trim() : fullLine;
483
497
  if (!line || !inputAction.test(line)) continue;
@@ -208,7 +208,25 @@ export function staticToolCallSignature(toolName, args = {}, context = {}) {
208
208
 
209
209
  function isStaticDiscoveryResult(result = {}) {
210
210
  if (!result || result.ok === false || result.blocked || result.done) return false;
211
- return isStaticDiscoveryToolCall(result.toolName, result.args || {});
211
+ if (isStaticDiscoveryToolCall(result.toolName, result.args || {})) return true;
212
+ if (result.toolName !== "run_command") return false;
213
+ if (/\b(?:watch|poll|status|queue|sleep)\b|tail\s+-f|tmux\s+capture-pane/i.test(String(result.args?.command || ""))) {
214
+ return false;
215
+ }
216
+ return !runCommandHasConcreteProgress(result);
217
+ }
218
+
219
+ function runCommandHasConcreteProgress(result = {}) {
220
+ const policy = result.commandPolicy || {};
221
+ const policyAllowsMutation =
222
+ policy.mayMutateProject === true ||
223
+ (policy.mayMutateProject === undefined && policy.writesWorkspace === true);
224
+ return Boolean(
225
+ policyAllowsMutation ||
226
+ policy.substantiveTest === true ||
227
+ (Array.isArray(result.verifiedGeneratedOutputPaths) &&
228
+ result.verifiedGeneratedOutputPaths.length > 0)
229
+ );
212
230
  }
213
231
 
214
232
  export function summarizeRepeatedStaticDiscovery(recentToolResults = [], context = {}) {
@@ -251,7 +269,7 @@ function hasConcreteProgress(recentToolResults = [], events = []) {
251
269
  if (result.ok === false || result.blocked || result.done) return false;
252
270
  if (isStaticDiscoveryToolCall(result.toolName, result.args || {})) return false;
253
271
  if (!PROGRESS_TOOL_NAMES.has(result.toolName)) return false;
254
- if (result.toolName === "run_command") return Boolean(result.stdout || result.stderr || result.exitCode === 0);
272
+ if (result.toolName === "run_command") return runCommandHasConcreteProgress(result);
255
273
  return Boolean(
256
274
  result.path ||
257
275
  result.artifactPath ||