@lazyingart/agintiflow 0.20.216 → 0.20.217

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.
@@ -235,3 +235,35 @@ provider balance. A separate read-only `localllm-deep` review completed with no
235
235
  actionable findings. The full `npm test` suite, focused dynamic-step-budget,
236
236
  coding-tools, SCS-evidence, and syntax checks all pass before packaging
237
237
  AgInTiFlow `0.20.216`.
238
+
239
+ ### Same-session interruption convergence and bounded completion
240
+
241
+ The `context-interruption-016` campaign exercised a long retained DeepSeek
242
+ session with multiple concrete interruptions, source corrections, an exact
243
+ external acceptance command, and a final clean-repository requirement. The
244
+ agent owned every target edit and commit. Early recovery turns repeatedly read
245
+ the same already-correct diff, showing that token limits alone do not guarantee
246
+ convergence while the available action surface remains open ended.
247
+
248
+ The reusable repair is driven by runtime state and evidence rather than task
249
+ literals. Mutations invalidate stale tests and prior completion evidence;
250
+ same-task interruptions refresh per-turn acceptance while retaining durable
251
+ evidence; repository-state recovery derives task-owned paths since the latest
252
+ successful commit and offers a bounded `commit_project_changes` action; and
253
+ the runtime constructs the path-scoped Git command. A current-revision exact
254
+ validator creates a one-use completion candidate. The following turn compacts
255
+ the evidence and exposes only `finish`; rejection or mutation invalidates the
256
+ candidate.
257
+
258
+ The final live continuation converged in three turns: bounded commit, exact
259
+ validator, and truthful finish. The target ended clean at commit `bd74bbd`.
260
+ Repository-repair context compacted from 156,716 to 5,788 characters, and the
261
+ verified-completion context compacted from 23,658 to 5,157 characters.
262
+
263
+ An independent read-only DeepSeek review then drove three general fixes: a
264
+ dedicated task-owned commit-path validator that supports binary,
265
+ extensionless, and instruction files while rejecting protected paths;
266
+ platform-aware POSIX and Windows command quoting; and recognition of bounded
267
+ stdin `sed -n` pipelines as read-only inspection. Production code contains no
268
+ campaign scenario IDs, target paths, expected prose, commit IDs, or acceptance
269
+ literals.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.216",
3
+ "version": "0.20.217",
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",
@@ -7,6 +7,8 @@ import {
7
7
  buildModelTimeoutRetryMessages,
8
8
  genericArtifactFilenameBlock,
9
9
  modelTimeoutRetryRoute,
10
+ applyModelTimeoutRetryRoute,
11
+ recoverFocusedTextRewriteWithWritingSpecialist,
10
12
  repairModelMessageHistory,
11
13
  shouldResetStaticDiscoveryPhase,
12
14
  runAgent,
@@ -16,6 +18,7 @@ import {
16
18
  shellDiagnosticHint,
17
19
  skippedAfterBlockedToolResult,
18
20
  } from "../src/agent-runner.js";
21
+ import { createToolContract, resolveDispatchableToolCallBatch } from "../src/tool-contract.js";
19
22
  import { formatBehaviorContractForPrompt } from "../src/behavior-contract.js";
20
23
  import { resolveRuntimeConfig } from "../src/config.js";
21
24
  import { readCodebaseMap } from "../src/codebase-map.js";
@@ -648,6 +651,14 @@ try {
648
651
  );
649
652
  assert(readonlyVersionPipelinePolicy.allowed, "read-only version probe pipelines should not require package-install-policy=allow");
650
653
  assert(readonlyVersionPipelinePolicy.category === "read-only", "read-only version probe pipelines should be classified as read-only");
654
+ const readonlyDiffSlicePolicy = evaluateCommandPolicy(
655
+ "git diff -- src/agent-runner.js | sed -n '1,240p'",
656
+ dockerWorkspaceNoInstallsPolicy
657
+ );
658
+ assert(
659
+ readonlyDiffSlicePolicy.allowed && readonlyDiffSlicePolicy.category === "read-only",
660
+ "a bounded sed print filter made a read-only Git diff pipeline require broad shell access"
661
+ );
651
662
  const nodeNpmTestPolicy = evaluateCommandPolicy(
652
663
  'cd /workspace && node --version && npm test 2>&1; echo "EXIT:$?"',
653
664
  dockerWorkspaceNoInstallsPolicy
@@ -1308,6 +1319,128 @@ try {
1308
1319
  localTimeoutRoute.model === "localllm-fast" && localTimeoutRoute.retryTimeoutMs === 90000,
1309
1320
  "LocalLLM timeout retry did not switch to its same-boundary fast route"
1310
1321
  );
1322
+ const adoptedLocalTimeoutRoute = applyModelTimeoutRetryRoute(
1323
+ { provider: "localllm", model: "localllm-deep", routingMode: "manual" },
1324
+ localTimeoutRoute
1325
+ );
1326
+ assert(
1327
+ adoptedLocalTimeoutRoute.model === "localllm-fast" &&
1328
+ adoptedLocalTimeoutRoute.modelTimeoutRecoveryActive === true &&
1329
+ /continuing this run/.test(adoptedLocalTimeoutRoute.routeReason),
1330
+ "a successful in-provider timeout retry was not retained for later steps in the same run"
1331
+ );
1332
+ const focusedRewriteDescriptor = {
1333
+ type: "function",
1334
+ function: {
1335
+ name: "rewrite_text_excerpt",
1336
+ description: "Rewrite one evidence-selected excerpt.",
1337
+ parameters: {
1338
+ type: "object",
1339
+ properties: {
1340
+ revisedText: {
1341
+ type: "string",
1342
+ minLength: 1,
1343
+ maxLength: 4000,
1344
+ pattern: "^(?:(?!load)[\\s\\S])+$",
1345
+ description:
1346
+ "Return only the complete revised excerpt. Remove the premature first-match operand while preserving the technical meaning.",
1347
+ },
1348
+ },
1349
+ required: ["revisedText"],
1350
+ additionalProperties: false,
1351
+ },
1352
+ },
1353
+ };
1354
+ const focusedRewriteContract = createToolContract([focusedRewriteDescriptor]);
1355
+ const invalidFocusedRewriteCall = {
1356
+ id: "focused-rewrite-smoke",
1357
+ type: "function",
1358
+ function: {
1359
+ name: "rewrite_text_excerpt",
1360
+ arguments: JSON.stringify({ revisedText: "A carefully preloaded technical summary." }),
1361
+ },
1362
+ };
1363
+ const invalidFocusedRewrite = resolveDispatchableToolCallBatch(
1364
+ [invalidFocusedRewriteCall],
1365
+ focusedRewriteContract
1366
+ );
1367
+ assert(
1368
+ !invalidFocusedRewrite.ok &&
1369
+ invalidFocusedRewrite.errors.some((error) => error.code === "ARGUMENT_PATTERN_MISMATCH"),
1370
+ "focused rewrite smoke input did not exercise the semantic pattern failure"
1371
+ );
1372
+ const focusedWriterCalls = [];
1373
+ const focusedRewriteState = {
1374
+ meta: {
1375
+ failedTestDiagnostic: {
1376
+ mutationRevision: 7,
1377
+ failureSignature: "generic-first-occurrence-relation",
1378
+ },
1379
+ },
1380
+ };
1381
+ const focusedRewriteConfig = {
1382
+ provider: "localllm",
1383
+ model: "localllm-fast",
1384
+ baseURL: "http://127.0.0.1:8008/v1",
1385
+ testFailureSignature: "generic-first-occurrence-relation",
1386
+ testFailureRepairPatchTargets: [{
1387
+ path: "notes/handoff.md",
1388
+ search: "A carefully preloaded technical summary.",
1389
+ }],
1390
+ writingClientFactory: (writingConfig) => {
1391
+ focusedWriterCalls.push(writingConfig);
1392
+ return {
1393
+ chat: {
1394
+ completions: {
1395
+ create: async () => ({
1396
+ choices: [{
1397
+ message: {
1398
+ content: JSON.stringify({
1399
+ draft: "A carefully prepared technical summary.",
1400
+ revision_notes: [],
1401
+ continuity_notes: [],
1402
+ format_handoff: {},
1403
+ quality_checks: ["constraint satisfied"],
1404
+ questions: [],
1405
+ }),
1406
+ },
1407
+ }],
1408
+ }),
1409
+ },
1410
+ },
1411
+ };
1412
+ },
1413
+ };
1414
+ const recoveredFocusedRewrite = await recoverFocusedTextRewriteWithWritingSpecialist(
1415
+ focusedRewriteConfig,
1416
+ focusedRewriteState,
1417
+ [invalidFocusedRewriteCall],
1418
+ focusedRewriteContract,
1419
+ invalidFocusedRewrite
1420
+ );
1421
+ assert(recoveredFocusedRewrite?.ok, "writing specialist did not recover the bounded semantic rewrite");
1422
+ assert(
1423
+ recoveredFocusedRewrite.recoveredFocusedTextRewrite === true &&
1424
+ JSON.parse(recoveredFocusedRewrite.acceptedToolCalls[0].function.arguments).revisedText ===
1425
+ "A carefully prepared technical summary.",
1426
+ "writing specialist recovery did not return a schema-valid focused rewrite call"
1427
+ );
1428
+ assert(
1429
+ focusedWriterCalls.length === 1 &&
1430
+ focusedWriterCalls[0].provider === "localllm" &&
1431
+ focusedWriterCalls[0].model === "localllm-fast",
1432
+ "focused rewrite recovery crossed the active provider/model boundary"
1433
+ );
1434
+ assert(
1435
+ (await recoverFocusedTextRewriteWithWritingSpecialist(
1436
+ focusedRewriteConfig,
1437
+ focusedRewriteState,
1438
+ [invalidFocusedRewriteCall],
1439
+ focusedRewriteContract,
1440
+ invalidFocusedRewrite
1441
+ )) === null && focusedWriterCalls.length === 1,
1442
+ "focused rewrite recovery repeated for the same retained failure state"
1443
+ );
1311
1444
  const defaultLocalTimeoutRoute = modelTimeoutRetryRoute({
1312
1445
  provider: "localllm",
1313
1446
  model: "localllm-fast",
@@ -1512,6 +1645,25 @@ try {
1512
1645
  const unifiedText = await fs.readFile(path.join(workspace, "unified-target.txt"), "utf8");
1513
1646
  assert(unified.ok && unifiedText === "alpha\nnew\nomega\n", "unified apply_patch did not update expected file");
1514
1647
 
1648
+ await fs.writeFile(path.join(workspace, "no-op-patch-target.txt"), "already correct\n", "utf8");
1649
+ const noOpPatchError = await executeWorkspaceTool(
1650
+ "apply_patch",
1651
+ {
1652
+ path: "no-op-patch-target.txt",
1653
+ search: "already correct",
1654
+ replace: "already correct",
1655
+ expectedReplacements: 1,
1656
+ },
1657
+ { commandCwd: workspace, allowFileTools: true }
1658
+ )
1659
+ .then(() => "")
1660
+ .catch((error) => String(error?.message || error));
1661
+ assert(
1662
+ /patch made no changes/i.test(noOpPatchError) &&
1663
+ (await fs.readFile(path.join(workspace, "no-op-patch-target.txt"), "utf8")) === "already correct\n",
1664
+ "an exact byte-identical replacement was reported as a successful mutation"
1665
+ );
1666
+
1515
1667
  await fs.writeFile(path.join(workspace, "hybrid-patch-target.txt"), "alpha\nold\nomega\n", "utf8");
1516
1668
  const hybridPatch = await executeWorkspaceTool(
1517
1669
  "apply_patch",
@@ -1858,6 +2010,7 @@ try {
1858
2010
  "node_profile_cli_package_manifest",
1859
2011
  "python_profile_helper_test_report",
1860
2012
  "model_timeout_compact_retry_messages",
2013
+ "focused_text_rewrite_specialist_recovery",
1861
2014
  ],
1862
2015
  },
1863
2016
  null,