@lazyingart/agintiflow 0.20.216 → 0.20.218

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,98 @@ 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.
270
+
271
+ ### Live inbox requirement coalescing
272
+
273
+ The next installed-release run tested actual mid-turn input rather than a
274
+ sequence of explicit resume commands. A DeepSeek task started from a normal
275
+ repository-writing request. While its first model call was active, two
276
+ independent corrections were appended through `aginti queue`. Both received
277
+ distinct durable inbox IDs and were applied exactly once at the first safe tool
278
+ boundary, before the source note was read.
279
+
280
+ AgInTiFlow `0.20.217` retained both corrections through later context
281
+ compaction, produced one concise artifact, removed every superseded value,
282
+ preserved the source note, added only narrowly scoped runtime ignore rules,
283
+ committed the intentional files, and left the target repository clean. An
284
+ external semantic and repository-state contract passed at target commit
285
+ `f19ff4b`. No AgInTiFlow source change or task-specific prompt branch was
286
+ needed; the run validates the generic inbox, context, evidence, and completion
287
+ contracts in the published package.
288
+
289
+ ### LabCanvas least-privilege worker integration
290
+
291
+ `labcanvas-worker-permission-018` validated the installed AgInTiFlow `0.20.217`
292
+ through LabCanvas after a real WeCom research task paused at
293
+ `permission_required`. The defect was in the integration boundary: LabCanvas
294
+ unconditionally selected AgInTi's blocked package-install tier, which also
295
+ blocked ordinary workspace shell execution. LabCanvas now keeps response-only
296
+ roles blocked while granting genuine worker roles reversible package setup
297
+ inside the Docker workspace. Host access, credentials, payment, publication,
298
+ destructive operations, and other irreversible actions remain separately
299
+ guarded.
300
+
301
+ A direct DeepSeek-backed worker smoke created and validated a bounded workspace
302
+ artifact without a permission prompt. Focused backend regressions, all WeChat
303
+ self-checks, the complete 1,442-test LabCanvas suite, and GitHub Actions passed.
304
+ The originally blocked exact-chat task was then resumed without duplicate model
305
+ work, produced a visually inspected nine-page XeLaTeX research report, and
306
+ delivered one concise message plus the PDF through the verified WeCom transport.
307
+ The follow-up message was preserved in the same chat session. The reusable fix
308
+ is AgenticApp commit `e705bb0`; no AgInTiFlow release change was required.
309
+
310
+ ### Durable background-job recovery after foreground interruption
311
+
312
+ `long-job-recovery-019` exercised installed AgInTiFlow `0.20.217` with a normal
313
+ laboratory-export request whose terminal could disconnect. The DeepSeek-backed
314
+ agent inspected the checked-in resumable scripts, rejected duplicate execution,
315
+ and selected the generic `start_long_job` routine. The campaign interrupted only
316
+ the foreground AgInTi pane after durable admission; the independent job tmux
317
+ continued, completed eight delayed batches, ran deferred verification, and
318
+ cleaned up its own tmux session.
319
+
320
+ The same AgInTi session then resumed from retained state. It recovered the exact
321
+ job ID through `long_job_status`, independently reran the project verifier,
322
+ confirmed the artifact and checksum, and finished without calling
323
+ `start_long_job` again. Proactive context compaction reduced the resumed history
324
+ from 190,805 to 30,680 characters while preserving the job identity and original
325
+ task boundary.
326
+
327
+ The independent acceptance contract verified one process invocation, one durable
328
+ job directory, attempt `1`, command and verifier exit `0`, the required durable
329
+ event sequence, a project handoff with exact status and verification commands,
330
+ and no lingering job tmux session. No source patch or npm release was needed;
331
+ this run validates the published package's general long-job, resume, context,
332
+ deduplication, and cleanup contracts without task-specific routing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.216",
3
+ "version": "0.20.218",
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";
@@ -580,6 +583,58 @@ try {
580
583
  ...hostWorkspacePolicy,
581
584
  readOnlyRoots: [externalReadRoot],
582
585
  };
586
+ const exactReadOnlySkillLoop = [
587
+ "for f in",
588
+ "/home/lachlan/.agintiflow/skillmesh/skills/lazyedit-publish-workflow/SKILL.md",
589
+ "/home/lachlan/.codex/skills/musia-music-production/SKILL.md",
590
+ "/home/lachlan/.codex/skills/musia-lalachan-mv-workflow/SKILL.md",
591
+ "/home/lachlan/.codex/skills/lalachan-xyq-browser-video/SKILL.md",
592
+ "/home/lachlan/.nvm/versions/node/v22.21.0/lib/node_modules/@lazyingart/agintiflow/skills/browser-automation/SKILL.md",
593
+ "/home/lachlan/.codex/skills/musia-song-localization/SKILL.md;",
594
+ 'do echo "===== $f ====="; wc -l "$f"; done',
595
+ ].join(" ");
596
+ const exactReadOnlySkillLoopPolicy = evaluateCommandPolicy(exactReadOnlySkillLoop, hostWorkspacePolicy);
597
+ assert(
598
+ exactReadOnlySkillLoopPolicy.allowed &&
599
+ exactReadOnlySkillLoopPolicy.category === "read-only" &&
600
+ exactReadOnlySkillLoopPolicy.boundedForLoop === true &&
601
+ exactReadOnlySkillLoopPolicy.needsNetwork === false &&
602
+ exactReadOnlySkillLoopPolicy.writesWorkspace === false,
603
+ "a finite echo/wc skill audit loop incorrectly required destructive host permission"
604
+ );
605
+ const multilineReadOnlyLoopPolicy = evaluateCommandPolicy(
606
+ [
607
+ "for target in README.md package.json",
608
+ "do",
609
+ ' echo "== ${target} =="',
610
+ ' wc -l "$target"',
611
+ "done",
612
+ ].join("\n"),
613
+ hostWorkspacePolicy
614
+ );
615
+ assert(
616
+ multilineReadOnlyLoopPolicy.allowed && multilineReadOnlyLoopPolicy.category === "read-only",
617
+ "a multiline finite read-only loop was not recognized"
618
+ );
619
+ for (const unsafeLoop of [
620
+ 'for f in README.md; do rm -rf "$f"; done',
621
+ 'for f in README.md; do curl "https://example.com/$f"; done',
622
+ 'for f in README.md; do cp "$f" copied.md; done',
623
+ 'for f in README.md; do echo "$f" > report.md; done',
624
+ 'for f in $(find . -type f); do wc -l "$f"; done',
625
+ 'for f in *.md; do wc -l "$f"; done',
626
+ 'for f in --pre=unsafe-helper; do rg pattern "$f"; done',
627
+ 'for f in $FILES; do wc -l "$f"; done',
628
+ 'for f in README.md; do wc -l "${f:-package.json}"; done',
629
+ 'for f in README.md; do for g in package.json; do wc -l "$g"; done; done',
630
+ 'for f in README.md; do wc -l "$f" & done',
631
+ ]) {
632
+ const unsafeLoopPolicy = evaluateCommandPolicy(unsafeLoop, hostWorkspacePolicy);
633
+ assert(
634
+ !unsafeLoopPolicy.allowed && unsafeLoopPolicy.category !== "read-only",
635
+ `unsafe or dynamic shell loop bypassed the bounded read-only policy: ${unsafeLoop}`
636
+ );
637
+ }
583
638
  const readRootDoctorPolicy = evaluateCommandPolicy(
584
639
  `cd ${externalReadRoot} && node bin/musia.js doctor --json 2>&1 | head -80`,
585
640
  hostReadRootPolicy
@@ -648,6 +703,14 @@ try {
648
703
  );
649
704
  assert(readonlyVersionPipelinePolicy.allowed, "read-only version probe pipelines should not require package-install-policy=allow");
650
705
  assert(readonlyVersionPipelinePolicy.category === "read-only", "read-only version probe pipelines should be classified as read-only");
706
+ const readonlyDiffSlicePolicy = evaluateCommandPolicy(
707
+ "git diff -- src/agent-runner.js | sed -n '1,240p'",
708
+ dockerWorkspaceNoInstallsPolicy
709
+ );
710
+ assert(
711
+ readonlyDiffSlicePolicy.allowed && readonlyDiffSlicePolicy.category === "read-only",
712
+ "a bounded sed print filter made a read-only Git diff pipeline require broad shell access"
713
+ );
651
714
  const nodeNpmTestPolicy = evaluateCommandPolicy(
652
715
  'cd /workspace && node --version && npm test 2>&1; echo "EXIT:$?"',
653
716
  dockerWorkspaceNoInstallsPolicy
@@ -1308,6 +1371,128 @@ try {
1308
1371
  localTimeoutRoute.model === "localllm-fast" && localTimeoutRoute.retryTimeoutMs === 90000,
1309
1372
  "LocalLLM timeout retry did not switch to its same-boundary fast route"
1310
1373
  );
1374
+ const adoptedLocalTimeoutRoute = applyModelTimeoutRetryRoute(
1375
+ { provider: "localllm", model: "localllm-deep", routingMode: "manual" },
1376
+ localTimeoutRoute
1377
+ );
1378
+ assert(
1379
+ adoptedLocalTimeoutRoute.model === "localllm-fast" &&
1380
+ adoptedLocalTimeoutRoute.modelTimeoutRecoveryActive === true &&
1381
+ /continuing this run/.test(adoptedLocalTimeoutRoute.routeReason),
1382
+ "a successful in-provider timeout retry was not retained for later steps in the same run"
1383
+ );
1384
+ const focusedRewriteDescriptor = {
1385
+ type: "function",
1386
+ function: {
1387
+ name: "rewrite_text_excerpt",
1388
+ description: "Rewrite one evidence-selected excerpt.",
1389
+ parameters: {
1390
+ type: "object",
1391
+ properties: {
1392
+ revisedText: {
1393
+ type: "string",
1394
+ minLength: 1,
1395
+ maxLength: 4000,
1396
+ pattern: "^(?:(?!load)[\\s\\S])+$",
1397
+ description:
1398
+ "Return only the complete revised excerpt. Remove the premature first-match operand while preserving the technical meaning.",
1399
+ },
1400
+ },
1401
+ required: ["revisedText"],
1402
+ additionalProperties: false,
1403
+ },
1404
+ },
1405
+ };
1406
+ const focusedRewriteContract = createToolContract([focusedRewriteDescriptor]);
1407
+ const invalidFocusedRewriteCall = {
1408
+ id: "focused-rewrite-smoke",
1409
+ type: "function",
1410
+ function: {
1411
+ name: "rewrite_text_excerpt",
1412
+ arguments: JSON.stringify({ revisedText: "A carefully preloaded technical summary." }),
1413
+ },
1414
+ };
1415
+ const invalidFocusedRewrite = resolveDispatchableToolCallBatch(
1416
+ [invalidFocusedRewriteCall],
1417
+ focusedRewriteContract
1418
+ );
1419
+ assert(
1420
+ !invalidFocusedRewrite.ok &&
1421
+ invalidFocusedRewrite.errors.some((error) => error.code === "ARGUMENT_PATTERN_MISMATCH"),
1422
+ "focused rewrite smoke input did not exercise the semantic pattern failure"
1423
+ );
1424
+ const focusedWriterCalls = [];
1425
+ const focusedRewriteState = {
1426
+ meta: {
1427
+ failedTestDiagnostic: {
1428
+ mutationRevision: 7,
1429
+ failureSignature: "generic-first-occurrence-relation",
1430
+ },
1431
+ },
1432
+ };
1433
+ const focusedRewriteConfig = {
1434
+ provider: "localllm",
1435
+ model: "localllm-fast",
1436
+ baseURL: "http://127.0.0.1:8008/v1",
1437
+ testFailureSignature: "generic-first-occurrence-relation",
1438
+ testFailureRepairPatchTargets: [{
1439
+ path: "notes/handoff.md",
1440
+ search: "A carefully preloaded technical summary.",
1441
+ }],
1442
+ writingClientFactory: (writingConfig) => {
1443
+ focusedWriterCalls.push(writingConfig);
1444
+ return {
1445
+ chat: {
1446
+ completions: {
1447
+ create: async () => ({
1448
+ choices: [{
1449
+ message: {
1450
+ content: JSON.stringify({
1451
+ draft: "A carefully prepared technical summary.",
1452
+ revision_notes: [],
1453
+ continuity_notes: [],
1454
+ format_handoff: {},
1455
+ quality_checks: ["constraint satisfied"],
1456
+ questions: [],
1457
+ }),
1458
+ },
1459
+ }],
1460
+ }),
1461
+ },
1462
+ },
1463
+ };
1464
+ },
1465
+ };
1466
+ const recoveredFocusedRewrite = await recoverFocusedTextRewriteWithWritingSpecialist(
1467
+ focusedRewriteConfig,
1468
+ focusedRewriteState,
1469
+ [invalidFocusedRewriteCall],
1470
+ focusedRewriteContract,
1471
+ invalidFocusedRewrite
1472
+ );
1473
+ assert(recoveredFocusedRewrite?.ok, "writing specialist did not recover the bounded semantic rewrite");
1474
+ assert(
1475
+ recoveredFocusedRewrite.recoveredFocusedTextRewrite === true &&
1476
+ JSON.parse(recoveredFocusedRewrite.acceptedToolCalls[0].function.arguments).revisedText ===
1477
+ "A carefully prepared technical summary.",
1478
+ "writing specialist recovery did not return a schema-valid focused rewrite call"
1479
+ );
1480
+ assert(
1481
+ focusedWriterCalls.length === 1 &&
1482
+ focusedWriterCalls[0].provider === "localllm" &&
1483
+ focusedWriterCalls[0].model === "localllm-fast",
1484
+ "focused rewrite recovery crossed the active provider/model boundary"
1485
+ );
1486
+ assert(
1487
+ (await recoverFocusedTextRewriteWithWritingSpecialist(
1488
+ focusedRewriteConfig,
1489
+ focusedRewriteState,
1490
+ [invalidFocusedRewriteCall],
1491
+ focusedRewriteContract,
1492
+ invalidFocusedRewrite
1493
+ )) === null && focusedWriterCalls.length === 1,
1494
+ "focused rewrite recovery repeated for the same retained failure state"
1495
+ );
1311
1496
  const defaultLocalTimeoutRoute = modelTimeoutRetryRoute({
1312
1497
  provider: "localllm",
1313
1498
  model: "localllm-fast",
@@ -1512,6 +1697,25 @@ try {
1512
1697
  const unifiedText = await fs.readFile(path.join(workspace, "unified-target.txt"), "utf8");
1513
1698
  assert(unified.ok && unifiedText === "alpha\nnew\nomega\n", "unified apply_patch did not update expected file");
1514
1699
 
1700
+ await fs.writeFile(path.join(workspace, "no-op-patch-target.txt"), "already correct\n", "utf8");
1701
+ const noOpPatchError = await executeWorkspaceTool(
1702
+ "apply_patch",
1703
+ {
1704
+ path: "no-op-patch-target.txt",
1705
+ search: "already correct",
1706
+ replace: "already correct",
1707
+ expectedReplacements: 1,
1708
+ },
1709
+ { commandCwd: workspace, allowFileTools: true }
1710
+ )
1711
+ .then(() => "")
1712
+ .catch((error) => String(error?.message || error));
1713
+ assert(
1714
+ /patch made no changes/i.test(noOpPatchError) &&
1715
+ (await fs.readFile(path.join(workspace, "no-op-patch-target.txt"), "utf8")) === "already correct\n",
1716
+ "an exact byte-identical replacement was reported as a successful mutation"
1717
+ );
1718
+
1515
1719
  await fs.writeFile(path.join(workspace, "hybrid-patch-target.txt"), "alpha\nold\nomega\n", "utf8");
1516
1720
  const hybridPatch = await executeWorkspaceTool(
1517
1721
  "apply_patch",
@@ -1858,6 +2062,7 @@ try {
1858
2062
  "node_profile_cli_package_manifest",
1859
2063
  "python_profile_helper_test_report",
1860
2064
  "model_timeout_compact_retry_messages",
2065
+ "focused_text_rewrite_specialist_recovery",
1861
2066
  ],
1862
2067
  },
1863
2068
  null,