@lazyingart/agintiflow 0.20.217 → 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.
|
@@ -267,3 +267,66 @@ platform-aware POSIX and Windows command quoting; and recognition of bounded
|
|
|
267
267
|
stdin `sed -n` pipelines as read-only inspection. Production code contains no
|
|
268
268
|
campaign scenario IDs, target paths, expected prose, commit IDs, or acceptance
|
|
269
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.
|
|
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",
|
|
@@ -583,6 +583,58 @@ try {
|
|
|
583
583
|
...hostWorkspacePolicy,
|
|
584
584
|
readOnlyRoots: [externalReadRoot],
|
|
585
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
|
+
}
|
|
586
638
|
const readRootDoctorPolicy = evaluateCommandPolicy(
|
|
587
639
|
`cd ${externalReadRoot} && node bin/musia.js doctor --json 2>&1 | head -80`,
|
|
588
640
|
hostReadRootPolicy
|
package/src/command-policy.js
CHANGED
|
@@ -1372,6 +1372,100 @@ function classifyShellSequence(normalized) {
|
|
|
1372
1372
|
};
|
|
1373
1373
|
}
|
|
1374
1374
|
|
|
1375
|
+
const READ_ONLY_FOR_LOOP_MAX_ITEMS = 64;
|
|
1376
|
+
const READ_ONLY_FOR_LOOP_MAX_COMMANDS = 16;
|
|
1377
|
+
const READ_ONLY_FOR_LOOP_LITERAL_PATTERN = /^[A-Za-z0-9_@%+=:,./~+-]+$/;
|
|
1378
|
+
|
|
1379
|
+
function shellIdentifierPattern(value = "") {
|
|
1380
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function broadForLoopClassification(normalized, reason) {
|
|
1384
|
+
return {
|
|
1385
|
+
category: "general-shell",
|
|
1386
|
+
needsNetwork: false,
|
|
1387
|
+
writesWorkspace: true,
|
|
1388
|
+
reason: `Shell for-loop is outside the bounded read-only form (${reason}): ${normalized}`,
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function classifyReadOnlyForLoop(normalized) {
|
|
1393
|
+
const text = String(normalized || "").trim();
|
|
1394
|
+
if (!/^for\s+/.test(text)) return null;
|
|
1395
|
+
if (text.length > 64 * 1024) {
|
|
1396
|
+
return broadForLoopClassification(text, "command is too large");
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const match = text.match(
|
|
1400
|
+
/^for[ \t]+([A-Za-z_][A-Za-z0-9_]*)[ \t]+in[ \t]+([\s\S]*?)(?:;|\n)[ \t]*do\s+([\s\S]*?)(?:;|\n)[ \t]*done[ \t]*$/
|
|
1401
|
+
);
|
|
1402
|
+
if (!match) return broadForLoopClassification(text, "unsupported loop syntax");
|
|
1403
|
+
|
|
1404
|
+
const [, variableName, itemSource, bodySource] = match;
|
|
1405
|
+
if (hasActiveShellExpansion(itemSource) || hasActiveShellCommandSubstitution(itemSource)) {
|
|
1406
|
+
return broadForLoopClassification(text, "the item list is dynamic");
|
|
1407
|
+
}
|
|
1408
|
+
const items = tokenizeShellWords(itemSource);
|
|
1409
|
+
if (!items.length || items.length > READ_ONLY_FOR_LOOP_MAX_ITEMS) {
|
|
1410
|
+
return broadForLoopClassification(text, "the item list is empty or unbounded");
|
|
1411
|
+
}
|
|
1412
|
+
if (items.some((item) => item.startsWith("-") || !READ_ONLY_FOR_LOOP_LITERAL_PATTERN.test(item))) {
|
|
1413
|
+
return broadForLoopClassification(
|
|
1414
|
+
text,
|
|
1415
|
+
"items must be finite literal words or paths without option prefixes or globs"
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const unquotedBody = stripQuotedSegments(bodySource);
|
|
1420
|
+
if (/[|&<>(){}]/.test(unquotedBody) || hasActiveShellCommandSubstitution(bodySource)) {
|
|
1421
|
+
return broadForLoopClassification(text, "the body uses control, redirection, or substitution syntax");
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const escapedVariable = shellIdentifierPattern(variableName);
|
|
1425
|
+
const variableReference = new RegExp(
|
|
1426
|
+
`\\$(?:\\{${escapedVariable}\\}|${escapedVariable}(?![A-Za-z0-9_]))`,
|
|
1427
|
+
"g"
|
|
1428
|
+
);
|
|
1429
|
+
const referenceCount = [...bodySource.matchAll(variableReference)].length;
|
|
1430
|
+
if (!referenceCount) {
|
|
1431
|
+
return broadForLoopClassification(text, "the body does not consume the loop item");
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
for (const item of items) {
|
|
1435
|
+
const expandedBody = bodySource.replace(variableReference, item);
|
|
1436
|
+
if (hasActiveShellExpansion(expandedBody) || hasActiveShellCommandSubstitution(expandedBody)) {
|
|
1437
|
+
return broadForLoopClassification(text, "the body contains expansion beyond the loop variable");
|
|
1438
|
+
}
|
|
1439
|
+
const sequence = splitTopLevelShellSequence(expandedBody);
|
|
1440
|
+
const commands = sequence?.parts || [expandedBody.trim()];
|
|
1441
|
+
if (
|
|
1442
|
+
!commands.length ||
|
|
1443
|
+
commands.length > READ_ONLY_FOR_LOOP_MAX_COMMANDS ||
|
|
1444
|
+
(sequence && sequence.separators.some((separator) => ![";", "newline"].includes(separator)))
|
|
1445
|
+
) {
|
|
1446
|
+
return broadForLoopClassification(text, "the body is not a bounded sequential command list");
|
|
1447
|
+
}
|
|
1448
|
+
for (const command of commands) {
|
|
1449
|
+
const classification = classifySimpleCommand(command);
|
|
1450
|
+
if (classification.category === "blocked" || classification.category === "destructive") {
|
|
1451
|
+
return { ...classification, gitOnly: false };
|
|
1452
|
+
}
|
|
1453
|
+
if (classification.category !== "read-only" || classification.writesWorkspace) {
|
|
1454
|
+
return broadForLoopClassification(text, `body command is ${classification.category}`);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
return {
|
|
1460
|
+
category: "read-only",
|
|
1461
|
+
needsNetwork: false,
|
|
1462
|
+
writesWorkspace: false,
|
|
1463
|
+
gitOnly: false,
|
|
1464
|
+
boundedForLoop: true,
|
|
1465
|
+
reason: `Finite read-only shell loop over ${items.length} literal item${items.length === 1 ? "" : "s"}.`,
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1375
1469
|
function splitTopLevelPipeline(command = "") {
|
|
1376
1470
|
const parts = [];
|
|
1377
1471
|
let current = "";
|
|
@@ -1536,7 +1630,7 @@ export function classifyCommand(command) {
|
|
|
1536
1630
|
const normalized = String(command || "").trim();
|
|
1537
1631
|
if (!normalized) return { category: "blocked", reason: "Command is empty." };
|
|
1538
1632
|
|
|
1539
|
-
return classifyBackgroundShell(normalized) || classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifyPipelineSequence(normalized) || classifySimpleCommand(normalized);
|
|
1633
|
+
return classifyBackgroundShell(normalized) || classifyReadOnlyForLoop(normalized) || classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifyPipelineSequence(normalized) || classifySimpleCommand(normalized);
|
|
1540
1634
|
}
|
|
1541
1635
|
|
|
1542
1636
|
export function evaluateCommandPolicy(command, config = {}) {
|