@lazyingart/agintiflow 0.20.255 → 0.20.256
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 +1 -1
- package/scripts/smoke-cli-chat.js +10 -2
- package/scripts/smoke-coding-tools.js +268 -5
- package/scripts/smoke-context-budget-recovery.js +17 -0
- package/scripts/smoke-dynamic-step-budget.js +2210 -21
- package/scripts/smoke-local-failure-recovery.js +1302 -76
- package/scripts/smoke-planning-timeout-recovery.js +120 -2
- package/scripts/smoke-progressive-tool-selection.js +1443 -69
- package/scripts/smoke-scs-evidence-visibility.js +219 -0
- package/src/agent-runner.js +5161 -299
- package/src/command-policy.js +103 -4
- package/src/local-failure-recovery.js +135 -16
- package/src/model-client.js +110 -4
- package/src/permission-advice.js +115 -4
- package/src/progressive-tool-selection.js +499 -28
- package/src/scs-controller.js +60 -3
- package/src/scs-evidence.js +495 -45
- package/src/tool-contract.js +114 -0
- package/src/workspace-tools.js +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.256",
|
|
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",
|
|
@@ -1076,7 +1076,13 @@ try {
|
|
|
1076
1076
|
}
|
|
1077
1077
|
const reviewResult = await runChat("/review changed files only\n/exit\n");
|
|
1078
1078
|
if (!reviewResult.stdout.includes("Review focus: changed files only") || !reviewResult.stdout.includes("Mock run complete")) {
|
|
1079
|
-
throw new Error(
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
[
|
|
1081
|
+
"interactive /review did not launch the bounded review workflow",
|
|
1082
|
+
`stdout tail:\n${reviewResult.stdout.slice(-6000)}`,
|
|
1083
|
+
`stderr tail:\n${reviewResult.stderr.slice(-3000)}`,
|
|
1084
|
+
].join("\n")
|
|
1085
|
+
);
|
|
1080
1086
|
}
|
|
1081
1087
|
const scsDefaultStatusResult = await runChat("/scs status\n");
|
|
1082
1088
|
if (!scsDefaultStatusResult.stdout.includes("SCS mode: auto")) {
|
|
@@ -1353,5 +1359,7 @@ try {
|
|
|
1353
1359
|
)
|
|
1354
1360
|
);
|
|
1355
1361
|
} finally {
|
|
1356
|
-
|
|
1362
|
+
if (process.env.AGINTIFLOW_SMOKE_KEEP_TEMP !== "1") {
|
|
1363
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
1364
|
+
}
|
|
1357
1365
|
}
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import {
|
|
7
7
|
buildModelTimeoutRetryMessages,
|
|
8
8
|
genericArtifactFilenameBlock,
|
|
9
|
+
modelTimeoutExhaustionRoute,
|
|
9
10
|
modelTimeoutRetryRoute,
|
|
10
11
|
applyModelTimeoutRetryRoute,
|
|
11
12
|
recoverFocusedTextRewriteWithWritingSpecialist,
|
|
@@ -734,6 +735,37 @@ try {
|
|
|
734
735
|
exactReadOnlyMultiPatternSummaryPolicy.writesWorkspace === false,
|
|
735
736
|
"a bounded grep/sort/uniq count summary incorrectly required destructive host permission"
|
|
736
737
|
);
|
|
738
|
+
const exactPdfHeaderAudit =
|
|
739
|
+
'cd /home/lachlan/ProjectsLFS/AgenticApp/output/wechat_worker/task-123 && file report.pdf && head -c 8 report.pdf | xxd | head -1';
|
|
740
|
+
const exactPdfHeaderAuditPolicy = evaluateCommandPolicy(
|
|
741
|
+
exactPdfHeaderAudit,
|
|
742
|
+
{
|
|
743
|
+
...hostWorkspacePolicy,
|
|
744
|
+
commandCwd: "/home/lachlan/ProjectsLFS/AgenticApp",
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
assert(
|
|
748
|
+
exactPdfHeaderAuditPolicy.allowed &&
|
|
749
|
+
exactPdfHeaderAuditPolicy.category === "read-only" &&
|
|
750
|
+
exactPdfHeaderAuditPolicy.needsNetwork === false &&
|
|
751
|
+
exactPdfHeaderAuditPolicy.writesWorkspace === false,
|
|
752
|
+
"a bounded workspace-local PDF header audit incorrectly required destructive host permission"
|
|
753
|
+
);
|
|
754
|
+
for (const unsafeXxdCommand of [
|
|
755
|
+
"xxd -r - output.bin",
|
|
756
|
+
"xxd report.pdf output.hex",
|
|
757
|
+
]) {
|
|
758
|
+
const unsafeXxdPolicy = evaluateCommandPolicy(
|
|
759
|
+
unsafeXxdCommand,
|
|
760
|
+
hostWorkspacePolicy
|
|
761
|
+
);
|
|
762
|
+
assert(
|
|
763
|
+
!unsafeXxdPolicy.allowed ||
|
|
764
|
+
unsafeXxdPolicy.category !== "read-only" ||
|
|
765
|
+
unsafeXxdPolicy.writesWorkspace === true,
|
|
766
|
+
`write-capable xxd command bypassed host policy: ${unsafeXxdCommand}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
737
769
|
const exactReadOnlyGitIdentityProbe =
|
|
738
770
|
'git status --short && echo "TOPLEVEL=$(git rev-parse --show-toplevel 2>&1)" && echo "BRANCH=$(git rev-parse --abbrev-ref HEAD 2>&1)"';
|
|
739
771
|
const exactReadOnlyGitIdentityProbePolicy = evaluateCommandPolicy(
|
|
@@ -971,6 +1003,12 @@ try {
|
|
|
971
1003
|
const pythonDemoPolicy = evaluateCommandPolicy("python3 demo.py 2>&1", dockerWorkspaceNoInstallsPolicy);
|
|
972
1004
|
assert(pythonDemoPolicy.allowed, "workspace-local python demo script should be allowed without package installs");
|
|
973
1005
|
assert(pythonDemoPolicy.category === "toolchain", "workspace-local python demo script should be classified as toolchain");
|
|
1006
|
+
const scopedPdfBuildPolicy = evaluateCommandPolicy(
|
|
1007
|
+
"cd output/task-1 && python3 build_report.py 2>&1 | tail -20 && ls -la *.pdf && sha256sum *.pdf",
|
|
1008
|
+
hostWorkspacePolicy
|
|
1009
|
+
);
|
|
1010
|
+
assert(scopedPdfBuildPolicy.allowed, "bounded PDF builder plus read-only verification was treated as broad host shell");
|
|
1011
|
+
assert(scopedPdfBuildPolicy.category === "toolchain", "bounded PDF builder pipeline should remain a toolchain");
|
|
974
1012
|
const broadLocalProbePolicy = evaluateCommandPolicy(
|
|
975
1013
|
'python3 --version; echo "---IMPORT TEST---"; python3 -c "import pathlib; print(pathlib.Path.cwd())" 2>&1; echo "---FILES---"; ls -la 2>&1 | head -20',
|
|
976
1014
|
dockerWorkspaceNoInstallsPolicy
|
|
@@ -1232,6 +1270,27 @@ try {
|
|
|
1232
1270
|
const localGitCommitPolicy = evaluateCommandPolicy('git commit -m "Initial local workflow commit"', dockerWorkspaceNoInstallsPolicy);
|
|
1233
1271
|
assert(localGitCommitPolicy.allowed, "local git commit should be allowed without package installs");
|
|
1234
1272
|
assert(localGitCommitPolicy.category === "git-workflow", "local git commit should be classified as git-workflow");
|
|
1273
|
+
const privateRuntimeStagePolicy = evaluateCommandPolicy("git add -- '.aginti/'", dockerWorkspaceNoInstallsPolicy);
|
|
1274
|
+
assert(!privateRuntimeStagePolicy.allowed, "private .aginti runtime state must never be stageable");
|
|
1275
|
+
assert(
|
|
1276
|
+
privateRuntimeStagePolicy.permissionAdvice?.autoRecover === true,
|
|
1277
|
+
"private runtime staging should recover automatically instead of pausing for user approval"
|
|
1278
|
+
);
|
|
1279
|
+
const chainedPrivateRuntimeStagePolicy = evaluateCommandPolicy(
|
|
1280
|
+
"git add -- 'service_ctl.py' && git add -- '.aginti/' && git commit -m 'Repair service'",
|
|
1281
|
+
dockerWorkspaceNoInstallsPolicy
|
|
1282
|
+
);
|
|
1283
|
+
assert(!chainedPrivateRuntimeStagePolicy.allowed, "a chained command must not stage private runtime state");
|
|
1284
|
+
const broadGitAddPolicy = evaluateCommandPolicy("git add -A", dockerWorkspaceNoInstallsPolicy);
|
|
1285
|
+
assert(!broadGitAddPolicy.allowed, "broad git staging must be rejected");
|
|
1286
|
+
assert(
|
|
1287
|
+
broadGitAddPolicy.permissionAdvice?.autoRecover === true,
|
|
1288
|
+
"broad staging should recover automatically to exact task-owned paths"
|
|
1289
|
+
);
|
|
1290
|
+
const commitAllPolicy = evaluateCommandPolicy("git commit -a -m 'Repair service'", dockerWorkspaceNoInstallsPolicy);
|
|
1291
|
+
assert(!commitAllPolicy.allowed, "git commit -a must not bypass bounded task-owned staging");
|
|
1292
|
+
const boundedGitAddPolicy = evaluateCommandPolicy("git add -- 'service_ctl.py'", dockerWorkspaceNoInstallsPolicy);
|
|
1293
|
+
assert(boundedGitAddPolicy.allowed, "bounded task-owned git staging should remain available");
|
|
1235
1294
|
const localGitSwitchPolicy = evaluateCommandPolicy("git switch -c feature-a", dockerWorkspaceNoInstallsPolicy);
|
|
1236
1295
|
assert(localGitSwitchPolicy.allowed, "local git switch -c should be allowed without package installs");
|
|
1237
1296
|
const localGitCheckoutExistingPolicy = evaluateCommandPolicy("git checkout main", dockerWorkspaceNoInstallsPolicy);
|
|
@@ -1299,6 +1358,40 @@ try {
|
|
|
1299
1358
|
}),
|
|
1300
1359
|
"recoverable workspace cd correction still paused the session"
|
|
1301
1360
|
);
|
|
1361
|
+
const outsideScratchAdvice = buildPermissionAdvice({
|
|
1362
|
+
toolName: "run_command",
|
|
1363
|
+
args: { command: "mkdir -p /tmp/test_service && cd /tmp/test_service" },
|
|
1364
|
+
guard: {
|
|
1365
|
+
category: "blocked",
|
|
1366
|
+
reason: "mkdir target must be a safe workspace-relative directory: /tmp/test_service",
|
|
1367
|
+
},
|
|
1368
|
+
config: {
|
|
1369
|
+
...dockerWorkspacePolicy,
|
|
1370
|
+
commandCwd: "/home/example/project",
|
|
1371
|
+
},
|
|
1372
|
+
state: { sessionId: "coding-outside-scratch-smoke" },
|
|
1373
|
+
});
|
|
1374
|
+
assert(
|
|
1375
|
+
outsideScratchAdvice.autoRecover === true,
|
|
1376
|
+
"an outside scratch-directory request should be corrected in-turn instead of pausing"
|
|
1377
|
+
);
|
|
1378
|
+
assert(
|
|
1379
|
+
/\.aginti\/verification/i.test(outsideScratchAdvice.instruction),
|
|
1380
|
+
"scratch-directory correction did not provide a workspace-relative verification path"
|
|
1381
|
+
);
|
|
1382
|
+
assert(
|
|
1383
|
+
!/package-install|approve-package/i.test(
|
|
1384
|
+
[outsideScratchAdvice.summary, outsideScratchAdvice.instruction].join("\n")
|
|
1385
|
+
),
|
|
1386
|
+
"scratch-directory correction incorrectly suggested package-install escalation"
|
|
1387
|
+
);
|
|
1388
|
+
assert(
|
|
1389
|
+
!shouldPauseForPermissionAdvice({
|
|
1390
|
+
blocked: true,
|
|
1391
|
+
permissionAdvice: outsideScratchAdvice,
|
|
1392
|
+
}),
|
|
1393
|
+
"recoverable scratch-directory correction still paused the session"
|
|
1394
|
+
);
|
|
1302
1395
|
const destructiveAdvice = buildPermissionAdvice({
|
|
1303
1396
|
toolName: "run_command",
|
|
1304
1397
|
args: { command: "rm -rf reports && git reset --hard" },
|
|
@@ -1448,6 +1541,95 @@ try {
|
|
|
1448
1541
|
!shouldPauseForPermissionAdvice({ blocked: true, permissionAdvice: dynamicEvidenceAdvice }),
|
|
1449
1542
|
"dynamic evidence filename recovery still produced a permission pause"
|
|
1450
1543
|
);
|
|
1544
|
+
const mixedToolchainAuditCommand = [
|
|
1545
|
+
"cd output/task-1 && python3 build_report.py",
|
|
1546
|
+
"python3 - <<'PY'",
|
|
1547
|
+
"from pathlib import Path",
|
|
1548
|
+
"print(Path('report.pdf').stat().st_size)",
|
|
1549
|
+
"PY",
|
|
1550
|
+
].join(" && ");
|
|
1551
|
+
const mixedToolchainAuditAdvice = buildPermissionAdvice({
|
|
1552
|
+
toolName: "run_command",
|
|
1553
|
+
args: { command: mixedToolchainAuditCommand },
|
|
1554
|
+
guard: {
|
|
1555
|
+
category: "general-shell",
|
|
1556
|
+
reason: "General shell commands on the host require Allow destructive actions.",
|
|
1557
|
+
},
|
|
1558
|
+
config: hostWorkspacePolicy,
|
|
1559
|
+
state: { sessionId: "coding-mixed-toolchain-audit-smoke" },
|
|
1560
|
+
});
|
|
1561
|
+
assert(
|
|
1562
|
+
mixedToolchainAuditAdvice.autoRecover === true &&
|
|
1563
|
+
mixedToolchainAuditAdvice.recoveryCommand ===
|
|
1564
|
+
"cd output/task-1 && python3 build_report.py",
|
|
1565
|
+
"mixed toolchain and inline audit did not recover to the safe build prefix"
|
|
1566
|
+
);
|
|
1567
|
+
assert(
|
|
1568
|
+
!shouldPauseForPermissionAdvice({
|
|
1569
|
+
blocked: true,
|
|
1570
|
+
permissionAdvice: mixedToolchainAuditAdvice,
|
|
1571
|
+
}),
|
|
1572
|
+
"mixed toolchain and inline audit recovery still paused the session"
|
|
1573
|
+
);
|
|
1574
|
+
const unsafeMixedAuditAdvice = buildPermissionAdvice({
|
|
1575
|
+
toolName: "run_command",
|
|
1576
|
+
args: {
|
|
1577
|
+
command:
|
|
1578
|
+
"rm -rf output/task-1 && python3 build_report.py && python3 - <<'PY'\nprint('x')\nPY",
|
|
1579
|
+
},
|
|
1580
|
+
guard: {
|
|
1581
|
+
category: "general-shell",
|
|
1582
|
+
reason: "General shell commands on the host require Allow destructive actions.",
|
|
1583
|
+
},
|
|
1584
|
+
config: hostWorkspacePolicy,
|
|
1585
|
+
state: { sessionId: "coding-unsafe-mixed-audit-smoke" },
|
|
1586
|
+
});
|
|
1587
|
+
assert(
|
|
1588
|
+
unsafeMixedAuditAdvice.autoRecover !== true,
|
|
1589
|
+
"destructive mixed audit received an unsafe automatic recovery"
|
|
1590
|
+
);
|
|
1591
|
+
const inlinePdfAuditAdvice = buildPermissionAdvice({
|
|
1592
|
+
toolName: "run_command",
|
|
1593
|
+
args: {
|
|
1594
|
+
command:
|
|
1595
|
+
'cd output/task-1 && python3 -c "import re;d=open(\'report.pdf\',\'rb\').read();print(len(re.findall(rb\'/Type\\\\s*/Page[^s]\',d)))"',
|
|
1596
|
+
},
|
|
1597
|
+
guard: {
|
|
1598
|
+
category: "general-shell",
|
|
1599
|
+
reason: "General shell commands on the host require Allow destructive actions.",
|
|
1600
|
+
},
|
|
1601
|
+
config: hostWorkspacePolicy,
|
|
1602
|
+
state: { sessionId: "coding-inline-pdf-audit-smoke" },
|
|
1603
|
+
});
|
|
1604
|
+
assert(
|
|
1605
|
+
inlinePdfAuditAdvice.autoRecover === true &&
|
|
1606
|
+
/deterministic artifact gate/i.test(inlinePdfAuditAdvice.instruction),
|
|
1607
|
+
"read-only inline PDF audit did not recover without stronger permission"
|
|
1608
|
+
);
|
|
1609
|
+
assert(
|
|
1610
|
+
!shouldPauseForPermissionAdvice({
|
|
1611
|
+
blocked: true,
|
|
1612
|
+
permissionAdvice: inlinePdfAuditAdvice,
|
|
1613
|
+
}),
|
|
1614
|
+
"read-only inline PDF audit recovery still paused the session"
|
|
1615
|
+
);
|
|
1616
|
+
const inlinePdfWriteAdvice = buildPermissionAdvice({
|
|
1617
|
+
toolName: "run_command",
|
|
1618
|
+
args: {
|
|
1619
|
+
command:
|
|
1620
|
+
'cd output/task-1 && python3 -c "open(\'report.pdf\',\'wb\').write(b\'replacement\')"',
|
|
1621
|
+
},
|
|
1622
|
+
guard: {
|
|
1623
|
+
category: "general-shell",
|
|
1624
|
+
reason: "General shell commands on the host require Allow destructive actions.",
|
|
1625
|
+
},
|
|
1626
|
+
config: hostWorkspacePolicy,
|
|
1627
|
+
state: { sessionId: "coding-inline-pdf-write-smoke" },
|
|
1628
|
+
});
|
|
1629
|
+
assert(
|
|
1630
|
+
inlinePdfWriteAdvice.autoRecover !== true,
|
|
1631
|
+
"write-capable inline Python received read-only automatic recovery"
|
|
1632
|
+
);
|
|
1451
1633
|
const malformedPatchGuard = {
|
|
1452
1634
|
allowed: false,
|
|
1453
1635
|
category: "workspace-patch",
|
|
@@ -1878,6 +2060,33 @@ try {
|
|
|
1878
2060
|
!boundedList.entries.some((item) => item.path.startsWith(".runtime-generated")),
|
|
1879
2061
|
"list_files traversed an unrecognized hidden runtime directory"
|
|
1880
2062
|
);
|
|
2063
|
+
await fs.mkdir(path.join(workspace, "output", "task-scope"), { recursive: true });
|
|
2064
|
+
await fs.writeFile(path.join(workspace, "output", "task-scope", "report.md"), "scoped\n", "utf8");
|
|
2065
|
+
await fs.writeFile(path.join(workspace, "unrelated.md"), "unrelated\n", "utf8");
|
|
2066
|
+
const scopedRead = await executeWorkspaceTool(
|
|
2067
|
+
"read_file",
|
|
2068
|
+
{ path: "output/task-scope/report.md" },
|
|
2069
|
+
{
|
|
2070
|
+
commandCwd: workspace,
|
|
2071
|
+
allowFileTools: true,
|
|
2072
|
+
workspacePathScopeRoots: ["output/task-scope"],
|
|
2073
|
+
}
|
|
2074
|
+
);
|
|
2075
|
+
assert(scopedRead.content === "scoped\n", "task-scoped artifact read failed inside its root");
|
|
2076
|
+
const outsideScopedRead = await executeWorkspaceTool(
|
|
2077
|
+
"read_file",
|
|
2078
|
+
{ path: "unrelated.md" },
|
|
2079
|
+
{
|
|
2080
|
+
commandCwd: workspace,
|
|
2081
|
+
allowFileTools: true,
|
|
2082
|
+
workspacePathScopeRoots: ["output/task-scope"],
|
|
2083
|
+
}
|
|
2084
|
+
);
|
|
2085
|
+
assert(
|
|
2086
|
+
outsideScopedRead.blocked === true &&
|
|
2087
|
+
/outside the active task artifact scope/.test(String(outsideScopedRead.reason || "")),
|
|
2088
|
+
"task-scoped artifact tools could read an unrelated repository file"
|
|
2089
|
+
);
|
|
1881
2090
|
const filenameSearch = await executeWorkspaceTool(
|
|
1882
2091
|
"search_files",
|
|
1883
2092
|
{ path: ".", query: "routine_entry.py", maxResults: 5 },
|
|
@@ -2143,8 +2352,43 @@ try {
|
|
|
2143
2352
|
model: "localllm-fast",
|
|
2144
2353
|
});
|
|
2145
2354
|
assert(
|
|
2146
|
-
defaultLocalTimeoutRoute.timeoutMs === 300000 && defaultLocalTimeoutRoute.retryTimeoutMs ===
|
|
2147
|
-
"LocalLLM
|
|
2355
|
+
defaultLocalTimeoutRoute.timeoutMs === 300000 && defaultLocalTimeoutRoute.retryTimeoutMs === 180000,
|
|
2356
|
+
"LocalLLM same-model timeout retry retained a doubled stall window after prompt compaction"
|
|
2357
|
+
);
|
|
2358
|
+
const exhaustedFastRoute = modelTimeoutExhaustionRoute(
|
|
2359
|
+
{
|
|
2360
|
+
provider: "localllm",
|
|
2361
|
+
model: "localllm-fast",
|
|
2362
|
+
mainProvider: "localllm",
|
|
2363
|
+
mainModel: "localllm-deep",
|
|
2364
|
+
spareProvider: "localllm",
|
|
2365
|
+
spareModel: "localllm-deep",
|
|
2366
|
+
localAvailableModels: ["localllm-fast", "localllm-deep"],
|
|
2367
|
+
},
|
|
2368
|
+
defaultLocalTimeoutRoute
|
|
2369
|
+
);
|
|
2370
|
+
assert(
|
|
2371
|
+
exhaustedFastRoute.switchedModel === true &&
|
|
2372
|
+
exhaustedFastRoute.model === "localllm-deep" &&
|
|
2373
|
+
exhaustedFastRoute.retryTimeoutMs === 240000,
|
|
2374
|
+
"two bounded fast-route timeouts did not select one stronger in-provider recovery hop"
|
|
2375
|
+
);
|
|
2376
|
+
const alreadySwitchedTimeoutRoute = modelTimeoutExhaustionRoute(
|
|
2377
|
+
{
|
|
2378
|
+
provider: "localllm",
|
|
2379
|
+
model: "localllm-deep",
|
|
2380
|
+
mainProvider: "localllm",
|
|
2381
|
+
mainModel: "localllm-deep",
|
|
2382
|
+
},
|
|
2383
|
+
modelTimeoutRetryRoute({
|
|
2384
|
+
provider: "localllm",
|
|
2385
|
+
model: "localllm-deep",
|
|
2386
|
+
modelTimeoutMs: 120000,
|
|
2387
|
+
})
|
|
2388
|
+
);
|
|
2389
|
+
assert(
|
|
2390
|
+
alreadySwitchedTimeoutRoute.switchedModel === false,
|
|
2391
|
+
"timeout exhaustion attempted an unbounded model bounce after an existing route switch"
|
|
2148
2392
|
);
|
|
2149
2393
|
const artifactTimeoutMessages = buildModelTimeoutRetryMessages(
|
|
2150
2394
|
{
|
|
@@ -2310,14 +2554,33 @@ try {
|
|
|
2310
2554
|
assert(patchRun.events.some((event) => event.type === "file.changed"), "patch run did not persist file.changed event");
|
|
2311
2555
|
|
|
2312
2556
|
await fs.writeFile(path.join(workspace, "patch-target.txt"), "old\n", "utf8");
|
|
2313
|
-
const multiPatchRun = await
|
|
2557
|
+
const multiPatchRun = await executeWorkspaceTool(
|
|
2558
|
+
"apply_patch",
|
|
2559
|
+
{
|
|
2560
|
+
patch: [
|
|
2561
|
+
"*** Begin Patch",
|
|
2562
|
+
"*** Update File: patch-target.txt",
|
|
2563
|
+
"@@",
|
|
2564
|
+
"-old",
|
|
2565
|
+
"+new",
|
|
2566
|
+
"*** Add File: notes/patch-note.md",
|
|
2567
|
+
"+Created by AgInTiFlow mock mode.",
|
|
2568
|
+
"+Goal: multi-file patch smoke.",
|
|
2569
|
+
"*** End Patch",
|
|
2570
|
+
].join("\n"),
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
commandCwd: workspace,
|
|
2574
|
+
allowFileTools: true,
|
|
2575
|
+
}
|
|
2576
|
+
);
|
|
2314
2577
|
const multiPatched = await fs.readFile(path.join(workspace, "patch-target.txt"), "utf8");
|
|
2315
2578
|
const patchNote = await fs.readFile(path.join(workspace, "notes/patch-note.md"), "utf8");
|
|
2316
2579
|
assert(multiPatched === "new\n", "mock multi-file patch did not update expected file");
|
|
2317
2580
|
assert(patchNote.includes("multi-file patch smoke"), "mock multi-file patch did not add expected file");
|
|
2318
2581
|
assert(
|
|
2319
|
-
multiPatchRun.
|
|
2320
|
-
"multi-file patch did not
|
|
2582
|
+
multiPatchRun.ok && multiPatchRun.changes?.length >= 2,
|
|
2583
|
+
"multi-file patch did not report each file change"
|
|
2321
2584
|
);
|
|
2322
2585
|
|
|
2323
2586
|
await fs.writeFile(path.join(workspace, "unified-target.txt"), "alpha\nold\nomega\n", "utf8");
|
|
@@ -208,6 +208,7 @@ const compactionState = {
|
|
|
208
208
|
content: JSON.stringify({
|
|
209
209
|
ok: true,
|
|
210
210
|
toolName: "read_file",
|
|
211
|
+
goalRevision: 11,
|
|
211
212
|
path: "reports/reliability.md",
|
|
212
213
|
startLine: 1,
|
|
213
214
|
lineLimit: 50,
|
|
@@ -253,6 +254,7 @@ const compactionState = {
|
|
|
253
254
|
content: JSON.stringify({
|
|
254
255
|
ok: true,
|
|
255
256
|
toolName: "read_file",
|
|
257
|
+
goalRevision: 11,
|
|
256
258
|
path: "/evidence/Musia/SKILL.md",
|
|
257
259
|
bytes: 2048,
|
|
258
260
|
sha256: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
@@ -307,6 +309,21 @@ assert.ok(runtimeText.includes("/evidence/Musia/SKILL.md"));
|
|
|
307
309
|
assert.ok(runtimeText.includes("Create and review songs through the established Musia production workflow"));
|
|
308
310
|
assert.ok(runtimeText.includes("# Musia Music Production"));
|
|
309
311
|
assert.ok(runtimeText.includes("sha256=1234567890abcdef"));
|
|
312
|
+
const retainedRevisionedReadIndex = runtimeMessages.findIndex((message) =>
|
|
313
|
+
Array.isArray(message.tool_calls) &&
|
|
314
|
+
message.tool_calls.some(
|
|
315
|
+
(call) =>
|
|
316
|
+
call?.function?.name === "read_file" &&
|
|
317
|
+
String(call?.function?.arguments || "").includes("reports/reliability.md") &&
|
|
318
|
+
String(call?.function?.arguments || "").includes('"startLine":1')
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
assert.ok(retainedRevisionedReadIndex >= 0, "native compaction lost the revisioned source read");
|
|
322
|
+
assert.equal(
|
|
323
|
+
JSON.parse(runtimeMessages[retainedRevisionedReadIndex + 1].content).goalRevision,
|
|
324
|
+
11,
|
|
325
|
+
"runtime compaction dropped the source read's goal revision"
|
|
326
|
+
);
|
|
310
327
|
assert.ok(runtimeText.includes("content=complete"));
|
|
311
328
|
assert.ok(runtimeText.includes("node bin/musia.js doctor --json"));
|
|
312
329
|
assert.ok(runtimeText.includes("scripts/xyq_cdp_browser.py"));
|