@lazyingart/agintiflow 0.20.324 → 0.20.326
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-document-artifact-quality.js +18 -1
- package/scripts/smoke-dynamic-step-budget.js +94 -1
- package/scripts/smoke-scs-evidence-visibility.js +45 -0
- package/src/agent-runner.js +133 -29
- package/src/command-policy.js +48 -0
- package/src/document-artifact-quality.js +26 -20
- package/src/scs-evidence.js +41 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.326",
|
|
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",
|
|
@@ -359,6 +359,17 @@ assert(
|
|
|
359
359
|
|
|
360
360
|
const exactSourceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "aginti-document-source-"));
|
|
361
361
|
try {
|
|
362
|
+
await fs.writeFile(
|
|
363
|
+
path.join(exactSourceRoot, "AGENTS.md"),
|
|
364
|
+
"WeChat PYTHONPATH LazyEdit STEP WeCom CAD PCB OpenHI\n",
|
|
365
|
+
"utf8"
|
|
366
|
+
);
|
|
367
|
+
await fs.mkdir(path.join(exactSourceRoot, "references"));
|
|
368
|
+
await fs.writeFile(
|
|
369
|
+
path.join(exactSourceRoot, "references", "unrelated.md"),
|
|
370
|
+
"Repository-wide material that does not belong to this exact report.\n",
|
|
371
|
+
"utf8"
|
|
372
|
+
);
|
|
362
373
|
await fs.writeFile(
|
|
363
374
|
path.join(exactSourceRoot, "chat_history.md"),
|
|
364
375
|
"Later correction: keep the publication blocked until login is restored.\n",
|
|
@@ -371,7 +382,13 @@ try {
|
|
|
371
382
|
assert.deepEqual(
|
|
372
383
|
exactSources.map((item) => item.path),
|
|
373
384
|
["chat_history.md"],
|
|
374
|
-
"
|
|
385
|
+
"repository-wide context contaminated an exact document source contract"
|
|
386
|
+
);
|
|
387
|
+
const discoveredSources = await collectDocumentSourceDocuments(exactSourceRoot);
|
|
388
|
+
assert.deepEqual(
|
|
389
|
+
discoveredSources.map((item) => item.path).sort(),
|
|
390
|
+
["AGENTS.md", "references/unrelated.md"],
|
|
391
|
+
"repository source discovery stopped working when no exact input was declared"
|
|
375
392
|
);
|
|
376
393
|
} finally {
|
|
377
394
|
await fs.rm(exactSourceRoot, { recursive: true, force: true });
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
recordAlreadyCommittedRepositoryRepair,
|
|
81
81
|
recordDurableEvidenceCategories,
|
|
82
82
|
recordProjectVerificationOutcome,
|
|
83
|
+
recordFailedCommandAttempt,
|
|
83
84
|
repositoryStateInspectionCommand,
|
|
84
85
|
recordExactOutputProgress,
|
|
85
86
|
recordStaticDiscoveryProgress,
|
|
@@ -94,6 +95,7 @@ import {
|
|
|
94
95
|
forbiddenCurrentTestRerunBlock,
|
|
95
96
|
unchangedFailedTestRerunBlock,
|
|
96
97
|
repeatedNoProgressToolBlock,
|
|
98
|
+
runCommandResultHasDurableProgress,
|
|
97
99
|
redundantCadValidationAliasPatchBlock,
|
|
98
100
|
regressiveInversePatchBlock,
|
|
99
101
|
repeatedSuccessfulMutationBlock,
|
|
@@ -8544,6 +8546,29 @@ try {
|
|
|
8544
8546
|
const semanticGateDocumentState = structuredClone(
|
|
8545
8547
|
currentGoalGeneratedDocumentState
|
|
8546
8548
|
);
|
|
8549
|
+
semanticGateDocumentState.messages = [
|
|
8550
|
+
{
|
|
8551
|
+
role: "assistant",
|
|
8552
|
+
tool_calls: [{
|
|
8553
|
+
id: "read-unrelated-root-policy",
|
|
8554
|
+
type: "function",
|
|
8555
|
+
function: {
|
|
8556
|
+
name: "read_file",
|
|
8557
|
+
arguments: JSON.stringify({ path: "AGENTS.md" }),
|
|
8558
|
+
},
|
|
8559
|
+
}],
|
|
8560
|
+
},
|
|
8561
|
+
{
|
|
8562
|
+
role: "tool",
|
|
8563
|
+
tool_call_id: "read-unrelated-root-policy",
|
|
8564
|
+
content: JSON.stringify({
|
|
8565
|
+
ok: true,
|
|
8566
|
+
path: "AGENTS.md",
|
|
8567
|
+
content: "WeChat PYTHONPATH LazyEdit STEP WeCom CAD PCB OpenHI",
|
|
8568
|
+
contentTruncated: false,
|
|
8569
|
+
}),
|
|
8570
|
+
},
|
|
8571
|
+
];
|
|
8547
8572
|
semanticGateDocumentState.meta.scs = {
|
|
8548
8573
|
taskContract: { exactInputPaths: ["TASK.md"] },
|
|
8549
8574
|
};
|
|
@@ -8581,7 +8606,8 @@ try {
|
|
|
8581
8606
|
semanticGateValidatorInput?.exactOutputPaths?.includes(
|
|
8582
8607
|
"daily_memo.pdf"
|
|
8583
8608
|
) &&
|
|
8584
|
-
semanticGateValidatorInput?.exactInputPaths
|
|
8609
|
+
JSON.stringify(semanticGateValidatorInput?.exactInputPaths) ===
|
|
8610
|
+
JSON.stringify(["TASK.md"]),
|
|
8585
8611
|
`current-goal artifact discovery bypassed source/status quality gates or reopened the missing-artifact loop: ${JSON.stringify({
|
|
8586
8612
|
assessment: semanticGateAssessment,
|
|
8587
8613
|
input: semanticGateValidatorInput,
|
|
@@ -11159,6 +11185,73 @@ try {
|
|
|
11159
11185
|
)?.category === "repeated-no-progress-call",
|
|
11160
11186
|
"dynamic failure output allowed an unchanged failing command to loop"
|
|
11161
11187
|
);
|
|
11188
|
+
assertStrict.equal(
|
|
11189
|
+
runCommandResultHasDurableProgress({
|
|
11190
|
+
toolName: "run_command",
|
|
11191
|
+
ok: true,
|
|
11192
|
+
commandPolicy: classifyCommand(
|
|
11193
|
+
'find /aginti-env -type f -name "pdflatex" | xargs ls -la | grep -v "not found"'
|
|
11194
|
+
),
|
|
11195
|
+
}),
|
|
11196
|
+
false,
|
|
11197
|
+
"a bounded find/xargs/grep probe fabricated durable workspace progress"
|
|
11198
|
+
);
|
|
11199
|
+
assertStrict.equal(
|
|
11200
|
+
runCommandResultHasDurableProgress({
|
|
11201
|
+
toolName: "run_command",
|
|
11202
|
+
ok: true,
|
|
11203
|
+
commandPolicy: {
|
|
11204
|
+
category: "general-shell",
|
|
11205
|
+
writesWorkspace: true,
|
|
11206
|
+
},
|
|
11207
|
+
projectMutationPaths: ["src/example.js"],
|
|
11208
|
+
}),
|
|
11209
|
+
true,
|
|
11210
|
+
"an observed shell mutation did not count as durable workspace progress"
|
|
11211
|
+
);
|
|
11212
|
+
const compactedFailureLoop = {
|
|
11213
|
+
stagnationEpoch: 4,
|
|
11214
|
+
recent: [],
|
|
11215
|
+
};
|
|
11216
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11217
|
+
recordFailedCommandAttempt(compactedFailureLoop, {
|
|
11218
|
+
signature: repeatedProbeState.meta.toolLoop.recent[0].signature,
|
|
11219
|
+
toolName: "run_command",
|
|
11220
|
+
ok: false,
|
|
11221
|
+
blocked: false,
|
|
11222
|
+
noProgressProbe: true,
|
|
11223
|
+
outcomeFingerprint: `failure-${attempt}`,
|
|
11224
|
+
stagnationEpoch: 4,
|
|
11225
|
+
goalRevision: 1,
|
|
11226
|
+
mutationRevision: 0,
|
|
11227
|
+
});
|
|
11228
|
+
}
|
|
11229
|
+
const compactedRepeatedFailureState = {
|
|
11230
|
+
meta: {
|
|
11231
|
+
toolLoop: compactedFailureLoop,
|
|
11232
|
+
},
|
|
11233
|
+
};
|
|
11234
|
+
assert(
|
|
11235
|
+
repeatedNoProgressToolBlock(
|
|
11236
|
+
compactedRepeatedFailureState,
|
|
11237
|
+
"run_command",
|
|
11238
|
+
repeatedProbeArgs,
|
|
11239
|
+
{ commandCwd: workspace }
|
|
11240
|
+
)?.category === "repeated-no-progress-call",
|
|
11241
|
+
"bounded failed-command history was lost after recent tool history rolled off"
|
|
11242
|
+
);
|
|
11243
|
+
const compactedFailureAfterMutation = structuredClone(compactedRepeatedFailureState);
|
|
11244
|
+
compactedFailureAfterMutation.meta.toolLoop.stagnationEpoch = 5;
|
|
11245
|
+
assertStrict.equal(
|
|
11246
|
+
repeatedNoProgressToolBlock(
|
|
11247
|
+
compactedFailureAfterMutation,
|
|
11248
|
+
"run_command",
|
|
11249
|
+
repeatedProbeArgs,
|
|
11250
|
+
{ commandCwd: workspace }
|
|
11251
|
+
),
|
|
11252
|
+
null,
|
|
11253
|
+
"a verified later state change did not release retained failed-command history"
|
|
11254
|
+
);
|
|
11162
11255
|
const newlyAuthoritativeVerificationState = structuredClone(repeatedFailureState);
|
|
11163
11256
|
newlyAuthoritativeVerificationState.meta.projectVerification = {
|
|
11164
11257
|
mutationRevision: 6,
|
|
@@ -240,6 +240,28 @@ const pageSafeReportContract = deriveScsTaskContract({
|
|
|
240
240
|
taskProfile: "auto",
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
+
const hostCompiledReportContract = deriveScsTaskContract({
|
|
244
|
+
goal: [
|
|
245
|
+
"Create report.md and report.tex for the requested evidence-grounded report.",
|
|
246
|
+
"Do not invoke LaTeX, latexmk, pdflatex, make, package managers, or document compiler commands; the LabCanvas host compiler owns PDF compilation and validation.",
|
|
247
|
+
"The host will create output/report.pdf after the agent turn.",
|
|
248
|
+
].join(" "),
|
|
249
|
+
taskProfile: "research",
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const agentCompiledReportContract = deriveScsTaskContract({
|
|
253
|
+
goal: "Create report.tex, compile output/report.pdf, inspect it, and return the PDF.",
|
|
254
|
+
taskProfile: "research",
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const fallbackHostCompilationContract = deriveScsTaskContract({
|
|
258
|
+
goal: [
|
|
259
|
+
"Create report.tex and compile output/report.pdf.",
|
|
260
|
+
"If compilation is unavailable in the sandbox, the LabCanvas host recovery stage owns PDF compilation.",
|
|
261
|
+
].join(" "),
|
|
262
|
+
taskProfile: "research",
|
|
263
|
+
});
|
|
264
|
+
|
|
243
265
|
const forbiddenOutputContract = deriveScsTaskContract({
|
|
244
266
|
goal: "Continue the task. verification_suite.py does not exist and must not be rerun or created. Preserve smoke_test.py and finish from current evidence.",
|
|
245
267
|
taskProfile: "devops",
|
|
@@ -543,6 +565,29 @@ assert(
|
|
|
543
565
|
!pageSafeReportContract.requiredEvidence.some((item) => item.category === "browser"),
|
|
544
566
|
"the editorial phrase page-safe incorrectly required browser evidence"
|
|
545
567
|
);
|
|
568
|
+
assert.equal(
|
|
569
|
+
hostCompiledReportContract.hostManagedDocumentCompilation,
|
|
570
|
+
true,
|
|
571
|
+
"an explicit host-only compilation contract was not retained"
|
|
572
|
+
);
|
|
573
|
+
assert(
|
|
574
|
+
hostCompiledReportContract.requiredArtifactKinds.some((item) => item.id === "format:.tex") &&
|
|
575
|
+
!hostCompiledReportContract.requiredArtifactKinds.some((item) => item.id === "format:.pdf"),
|
|
576
|
+
"host-managed PDF compilation did not preserve editable source while deferring PDF production"
|
|
577
|
+
);
|
|
578
|
+
assert(
|
|
579
|
+
!hostCompiledReportContract.exactOutputPaths.some((item) => item.endsWith(".pdf")),
|
|
580
|
+
"a host-managed PDF path remained an agent-owned exact output"
|
|
581
|
+
);
|
|
582
|
+
assert(
|
|
583
|
+
agentCompiledReportContract.requiredArtifactKinds.some((item) => item.id === "format:.pdf") &&
|
|
584
|
+
agentCompiledReportContract.exactOutputPaths.some((item) => item.endsWith("output/report.pdf")),
|
|
585
|
+
"ordinary agent-owned PDF compilation was weakened by the host-managed exception"
|
|
586
|
+
);
|
|
587
|
+
assert(
|
|
588
|
+
fallbackHostCompilationContract.hostManagedDocumentCompilation === false,
|
|
589
|
+
"a conditional host recovery fallback was mistaken for an explicit host-only compilation contract"
|
|
590
|
+
);
|
|
546
591
|
|
|
547
592
|
const noEvidenceProgress = {
|
|
548
593
|
role: "student",
|
package/src/agent-runner.js
CHANGED
|
@@ -147,6 +147,7 @@ import {
|
|
|
147
147
|
finishResultClaimsBlocker,
|
|
148
148
|
finishResultClaimsIncompleteWork,
|
|
149
149
|
hasScsBlockerEvidence,
|
|
150
|
+
hostManagedDocumentCompilationRequested,
|
|
150
151
|
buildTmuxGitIntent,
|
|
151
152
|
inferAuthoritativeReadOnlyRoutine,
|
|
152
153
|
inferGitActionsFromCommand,
|
|
@@ -1414,6 +1415,22 @@ export function successfulReadFileEvidencePaths(messages = []) {
|
|
|
1414
1415
|
.map(({ path: sourcePath, complete }) => ({ path: sourcePath, complete }));
|
|
1415
1416
|
}
|
|
1416
1417
|
|
|
1418
|
+
export function authoritativeDocumentInputPaths(state = {}, contract = {}) {
|
|
1419
|
+
const normalize = (value = "") =>
|
|
1420
|
+
String(value || "").replace(/\\/gu, "/").replace(/^\.\//u, "").trim();
|
|
1421
|
+
const declared = [
|
|
1422
|
+
...(Array.isArray(contract?.exactInputPaths) ? contract.exactInputPaths : []),
|
|
1423
|
+
...exactInputPathsForState(state),
|
|
1424
|
+
]
|
|
1425
|
+
.map(normalize)
|
|
1426
|
+
.filter(Boolean);
|
|
1427
|
+
if (declared.length > 0) return [...new Set(declared)].slice(0, 32);
|
|
1428
|
+
return successfulReadFileEvidencePaths(state.messages || [])
|
|
1429
|
+
.map((item) => normalize(item.path))
|
|
1430
|
+
.filter(Boolean)
|
|
1431
|
+
.slice(-32);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1417
1434
|
function writingRequestNeedsSourceGrounding(args = {}) {
|
|
1418
1435
|
const context = [args.writingBrief, args.canon, args.constraints, args.priorDraft]
|
|
1419
1436
|
.filter((value) => typeof value === "string")
|
|
@@ -3238,7 +3255,9 @@ async function createInitialState(config, sessionId) {
|
|
|
3238
3255
|
isRetainedWorkspaceProfile(config)
|
|
3239
3256
|
? "For website/app/code/LaTeX/Python/C/shell tasks, create or edit real workspace files and run bounded checks when useful."
|
|
3240
3257
|
: "For website/app/code/LaTeX/Python/C/shell tasks, create or edit real workspace files, run available build/compile/test commands, and surface artifacts through the canvas when useful.",
|
|
3241
|
-
|
|
3258
|
+
hostManagedDocumentCompilationRequested(config.goal)
|
|
3259
|
+
? "For this host-managed document task, create and verify the requested editable sources only. Do not invoke LaTeX, make, package managers, or document compilers; the host stage owns compilation and derived-PDF validation."
|
|
3260
|
+
: "For LaTeX/PDF tasks, check existing latexmk/pdflatex first and compile with the available host or Docker TeX toolchain before installing packages or rebuilding the sandbox.",
|
|
3242
3261
|
"For research or web-search tasks, use browser tools or safe shell network tools when the current policy allows; cite or save useful sources in workspace notes when the task needs traceability.",
|
|
3243
3262
|
completionRequirementCoverageInstruction(),
|
|
3244
3263
|
repositorySourcePrecedenceInstruction(),
|
|
@@ -10816,12 +10835,19 @@ export async function documentQualityCommitAssessment(
|
|
|
10816
10835
|
const requestedArtifactKinds = Array.isArray(contract.requiredArtifactKinds)
|
|
10817
10836
|
? contract.requiredArtifactKinds
|
|
10818
10837
|
: [];
|
|
10838
|
+
const hostManagedCompilation = Boolean(
|
|
10839
|
+
contract.hostManagedDocumentCompilation ||
|
|
10840
|
+
hostManagedDocumentCompilationRequested(completionContractGoal(config, state))
|
|
10841
|
+
);
|
|
10819
10842
|
const documentRequested = Boolean(
|
|
10820
|
-
exactOutputPaths.some((item) =>
|
|
10843
|
+
exactOutputPaths.some((item) =>
|
|
10844
|
+
/\.docx$/iu.test(String(item || "")) ||
|
|
10845
|
+
(!hostManagedCompilation && /\.pdf$/iu.test(String(item || "")))
|
|
10846
|
+
) ||
|
|
10821
10847
|
requestedArtifactKinds.some((item) =>
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10848
|
+
String(item?.format || item?.extension || item || "").toLowerCase() === ".docx" ||
|
|
10849
|
+
(!hostManagedCompilation &&
|
|
10850
|
+
String(item?.format || item?.extension || item || "").toLowerCase() === ".pdf")
|
|
10825
10851
|
)
|
|
10826
10852
|
);
|
|
10827
10853
|
if (!documentRequested) return null;
|
|
@@ -10837,15 +10863,7 @@ export async function documentQualityCommitAssessment(
|
|
|
10837
10863
|
candidateResult: "",
|
|
10838
10864
|
goal: completionContractGoal(config, state),
|
|
10839
10865
|
exactOutputPaths,
|
|
10840
|
-
exactInputPaths:
|
|
10841
|
-
...(Array.isArray(contract.exactInputPaths)
|
|
10842
|
-
? contract.exactInputPaths
|
|
10843
|
-
: []),
|
|
10844
|
-
...exactInputPathsForState(state),
|
|
10845
|
-
...successfulReadFileEvidencePaths(state.messages || []).map(
|
|
10846
|
-
(item) => item.path
|
|
10847
|
-
),
|
|
10848
|
-
],
|
|
10866
|
+
exactInputPaths: authoritativeDocumentInputPaths(state, contract),
|
|
10849
10867
|
requireVersioned: false,
|
|
10850
10868
|
});
|
|
10851
10869
|
} catch (error) {
|
|
@@ -10887,7 +10905,8 @@ export async function documentQualityCommitAssessment(
|
|
|
10887
10905
|
);
|
|
10888
10906
|
const documentArtifactProducerCommand = inferredLatexArtifactProducerCommand(
|
|
10889
10907
|
state,
|
|
10890
|
-
config
|
|
10908
|
+
config,
|
|
10909
|
+
contract
|
|
10891
10910
|
);
|
|
10892
10911
|
const repair = completionRepairMutationRequirement({
|
|
10893
10912
|
contract,
|
|
@@ -10956,11 +10975,12 @@ function expectedRepeatedObservationCommand(command = "") {
|
|
|
10956
10975
|
);
|
|
10957
10976
|
}
|
|
10958
10977
|
|
|
10959
|
-
function runCommandResultHasDurableProgress(toolResult = {}) {
|
|
10978
|
+
export function runCommandResultHasDurableProgress(toolResult = {}) {
|
|
10960
10979
|
const policy = toolResult.commandPolicy || {};
|
|
10961
10980
|
const policyAllowsMutation =
|
|
10962
|
-
policy.
|
|
10963
|
-
(policy.mayMutateProject ===
|
|
10981
|
+
policy.semanticMayMutateProject !== false &&
|
|
10982
|
+
(policy.mayMutateProject === true ||
|
|
10983
|
+
(policy.mayMutateProject === undefined && policy.writesWorkspace === true));
|
|
10964
10984
|
return Boolean(
|
|
10965
10985
|
policyAllowsMutation ||
|
|
10966
10986
|
policy.substantiveTest === true ||
|
|
@@ -10971,6 +10991,54 @@ function runCommandResultHasDurableProgress(toolResult = {}) {
|
|
|
10971
10991
|
);
|
|
10972
10992
|
}
|
|
10973
10993
|
|
|
10994
|
+
function failedCommandAttempt(toolLoop = {}, signature = "", stagnationEpoch = 0) {
|
|
10995
|
+
return (Array.isArray(toolLoop.failedCommandAttempts)
|
|
10996
|
+
? toolLoop.failedCommandAttempts
|
|
10997
|
+
: []
|
|
10998
|
+
).find(
|
|
10999
|
+
(entry) =>
|
|
11000
|
+
entry?.signature === signature &&
|
|
11001
|
+
Number(entry?.stagnationEpoch || 0) === Number(stagnationEpoch || 0)
|
|
11002
|
+
);
|
|
11003
|
+
}
|
|
11004
|
+
|
|
11005
|
+
export function recordFailedCommandAttempt(toolLoop = {}, entry = {}) {
|
|
11006
|
+
if (
|
|
11007
|
+
entry?.toolName !== "run_command" ||
|
|
11008
|
+
entry?.ok !== false ||
|
|
11009
|
+
entry?.blocked === true ||
|
|
11010
|
+
entry?.noProgressProbe !== true ||
|
|
11011
|
+
!entry?.signature
|
|
11012
|
+
) {
|
|
11013
|
+
return null;
|
|
11014
|
+
}
|
|
11015
|
+
toolLoop.failedCommandAttempts = Array.isArray(toolLoop.failedCommandAttempts)
|
|
11016
|
+
? toolLoop.failedCommandAttempts
|
|
11017
|
+
: [];
|
|
11018
|
+
const prior = failedCommandAttempt(
|
|
11019
|
+
toolLoop,
|
|
11020
|
+
entry.signature,
|
|
11021
|
+
entry.stagnationEpoch
|
|
11022
|
+
);
|
|
11023
|
+
if (prior) {
|
|
11024
|
+
prior.count = Math.max(0, Number(prior.count || 0)) + 1;
|
|
11025
|
+
prior.lastOutcomeFingerprint = String(entry.outcomeFingerprint || "");
|
|
11026
|
+
prior.lastAt = String(entry.at || new Date().toISOString());
|
|
11027
|
+
} else {
|
|
11028
|
+
toolLoop.failedCommandAttempts.push({
|
|
11029
|
+
signature: entry.signature,
|
|
11030
|
+
count: 1,
|
|
11031
|
+
stagnationEpoch: Math.max(0, Number(entry.stagnationEpoch || 0)),
|
|
11032
|
+
goalRevision: Math.max(0, Number(entry.goalRevision || 0)),
|
|
11033
|
+
mutationRevision: Math.max(0, Number(entry.mutationRevision || 0)),
|
|
11034
|
+
lastOutcomeFingerprint: String(entry.outcomeFingerprint || ""),
|
|
11035
|
+
lastAt: String(entry.at || new Date().toISOString()),
|
|
11036
|
+
});
|
|
11037
|
+
}
|
|
11038
|
+
toolLoop.failedCommandAttempts = toolLoop.failedCommandAttempts.slice(-40);
|
|
11039
|
+
return prior || toolLoop.failedCommandAttempts.at(-1);
|
|
11040
|
+
}
|
|
11041
|
+
|
|
10974
11042
|
function isStaticDiscoveryToolResult(toolResult = {}) {
|
|
10975
11043
|
if (isStaticDiscoveryToolCall(toolResult.toolName, toolResult.args || {})) return true;
|
|
10976
11044
|
if (toolResult.toolName !== "run_command") return false;
|
|
@@ -11163,6 +11231,30 @@ export function repeatedNoProgressToolBlock(state, toolName, args = {}, config =
|
|
|
11163
11231
|
Number(entry?.stagnationEpoch || 0) === stagnationEpoch &&
|
|
11164
11232
|
Boolean(entry?.outcomeFingerprint)
|
|
11165
11233
|
);
|
|
11234
|
+
const retainedFailure = failedCommandAttempt(
|
|
11235
|
+
toolLoop,
|
|
11236
|
+
signature,
|
|
11237
|
+
stagnationEpoch
|
|
11238
|
+
);
|
|
11239
|
+
if (Number(retainedFailure?.count || 0) >= 2) {
|
|
11240
|
+
return {
|
|
11241
|
+
reason:
|
|
11242
|
+
"The same command already failed twice without an intervening verified workspace, artifact, browser, or task-state change.",
|
|
11243
|
+
category: "repeated-no-progress-call",
|
|
11244
|
+
permissionAdvice: {
|
|
11245
|
+
category: "repeated-no-progress-call",
|
|
11246
|
+
autoRecover: true,
|
|
11247
|
+
summary: "This is a failed-command convergence guard, not a permission blocker.",
|
|
11248
|
+
instruction:
|
|
11249
|
+
"Do not rerun the command or a cosmetically equivalent form. Use the retained failure evidence, change the command or repair the implicated source, then run the smallest relevant validation.",
|
|
11250
|
+
options: [
|
|
11251
|
+
"Choose the correct compiler, interpreter, working directory, or command flags from the observed failure.",
|
|
11252
|
+
"Apply one bounded source repair that addresses the failure, then rerun validation.",
|
|
11253
|
+
"Finish with a concrete external blocker only when no enabled tool can make progress.",
|
|
11254
|
+
],
|
|
11255
|
+
},
|
|
11256
|
+
};
|
|
11257
|
+
}
|
|
11166
11258
|
if (matches.length < 2) return null;
|
|
11167
11259
|
const repeatedFailures = matches.slice(-2).every((entry) => entry?.ok === false);
|
|
11168
11260
|
if (repeatedFailures) {
|
|
@@ -16422,7 +16514,7 @@ export function nextStepRuntimeConfig(config = {}, state = {}) {
|
|
|
16422
16514
|
(item) => String(item?.id || "").toLowerCase() === "format:.tex"
|
|
16423
16515
|
)
|
|
16424
16516
|
)
|
|
16425
|
-
? inferredLatexArtifactProducerCommand(state, config)
|
|
16517
|
+
? inferredLatexArtifactProducerCommand(state, config, groundingExecutionContract)
|
|
16426
16518
|
: "";
|
|
16427
16519
|
const retainedRepairPacket = state.meta?.failedTestRecoveryPacket;
|
|
16428
16520
|
const repairedRetainedFailure = Boolean(
|
|
@@ -19847,6 +19939,7 @@ async function applyToolLoopGuard(state, toolResult, store, observers, config =
|
|
|
19847
19939
|
};
|
|
19848
19940
|
state.meta.toolLoop.recent.push(entry);
|
|
19849
19941
|
state.meta.toolLoop.recent = state.meta.toolLoop.recent.slice(-20);
|
|
19942
|
+
recordFailedCommandAttempt(state.meta.toolLoop, entry);
|
|
19850
19943
|
|
|
19851
19944
|
const activeRefresh = activePatchContextRefresh(state);
|
|
19852
19945
|
if (requiredPatchContextRefresh && !activeRefresh) {
|
|
@@ -22376,7 +22469,13 @@ export function completionRepairMutationRequirement({
|
|
|
22376
22469
|
};
|
|
22377
22470
|
}
|
|
22378
22471
|
|
|
22379
|
-
function inferredLatexArtifactProducerCommand(state = {}, config = {}) {
|
|
22472
|
+
function inferredLatexArtifactProducerCommand(state = {}, config = {}, contract = {}) {
|
|
22473
|
+
if (
|
|
22474
|
+
contract?.hostManagedDocumentCompilation === true ||
|
|
22475
|
+
hostManagedDocumentCompilationRequested(completionContractGoal(config, state))
|
|
22476
|
+
) {
|
|
22477
|
+
return "";
|
|
22478
|
+
}
|
|
22380
22479
|
const verification = state.meta?.projectVerification || {};
|
|
22381
22480
|
const sourcePath = [...(verification.mutationHistory || [])]
|
|
22382
22481
|
.reverse()
|
|
@@ -22465,13 +22564,21 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
|
|
|
22465
22564
|
...currentGoalDocumentArtifactPathsForState(state, config),
|
|
22466
22565
|
];
|
|
22467
22566
|
const documentContractText = `${completionContractGoal(config, state)}\n${candidateResult}`;
|
|
22567
|
+
const hostManagedCompilation = Boolean(
|
|
22568
|
+
assessment.contract?.hostManagedDocumentCompilation ||
|
|
22569
|
+
hostManagedDocumentCompilationRequested(completionContractGoal(config, state))
|
|
22570
|
+
);
|
|
22468
22571
|
const documentDeliverableRequested =
|
|
22469
22572
|
documentProfile === "word" ||
|
|
22470
|
-
documentExactOutputPaths.some((item) =>
|
|
22573
|
+
documentExactOutputPaths.some((item) =>
|
|
22574
|
+
/\.docx$/iu.test(String(item || "")) ||
|
|
22575
|
+
(!hostManagedCompilation && /\.pdf$/iu.test(String(item || "")))
|
|
22576
|
+
) ||
|
|
22471
22577
|
(["book", "latex", "paper", "writing"].includes(documentProfile) &&
|
|
22472
|
-
/(?:\b(?:docx|
|
|
22578
|
+
(/(?:\b(?:docx|word document)\b|可编辑.*(?:文档|文件)|(?:文档|文件).*可编辑)/iu.test(
|
|
22473
22579
|
documentContractText
|
|
22474
|
-
)
|
|
22580
|
+
) ||
|
|
22581
|
+
(!hostManagedCompilation && /\bpdf\b/iu.test(documentContractText))));
|
|
22475
22582
|
if (documentDeliverableRequested) {
|
|
22476
22583
|
try {
|
|
22477
22584
|
documentQuality = await validateWordDocumentArtifacts({
|
|
@@ -22479,11 +22586,7 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
|
|
|
22479
22586
|
candidateResult,
|
|
22480
22587
|
goal: completionContractGoal(config, state),
|
|
22481
22588
|
exactOutputPaths: documentExactOutputPaths,
|
|
22482
|
-
exactInputPaths:
|
|
22483
|
-
...(assessment.contract?.exactInputPaths || []),
|
|
22484
|
-
...exactInputPathsForState(state),
|
|
22485
|
-
...successfulReadFileEvidencePaths(state.messages || []).map((item) => item.path),
|
|
22486
|
-
],
|
|
22589
|
+
exactInputPaths: authoritativeDocumentInputPaths(state, assessment.contract),
|
|
22487
22590
|
requireVersioned: (assessment.contract?.requiredGitActions || []).includes("commit"),
|
|
22488
22591
|
});
|
|
22489
22592
|
} catch (error) {
|
|
@@ -22764,7 +22867,8 @@ async function completionEvidenceDecision({ config, state, store, observers, ste
|
|
|
22764
22867
|
: "";
|
|
22765
22868
|
const documentArtifactProducerCommand = inferredLatexArtifactProducerCommand(
|
|
22766
22869
|
state,
|
|
22767
|
-
config
|
|
22870
|
+
config,
|
|
22871
|
+
assessment.contract
|
|
22768
22872
|
);
|
|
22769
22873
|
const freshMutationRequirement = completionRepairMutationRequirement({
|
|
22770
22874
|
contract: assessment.contract,
|
package/src/command-policy.js
CHANGED
|
@@ -137,6 +137,25 @@ function isReadOnlyDiffCommand(command = "") {
|
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
function isReadOnlyXargsListFilter(command = "") {
|
|
141
|
+
const normalized = stripBenignRedirections(command);
|
|
142
|
+
if (hasActiveShellExpansion(normalized)) return false;
|
|
143
|
+
const tokens = tokenizeShellWords(normalized);
|
|
144
|
+
if (!tokens.length || tokens[0] !== "xargs") return false;
|
|
145
|
+
let index = 1;
|
|
146
|
+
while (
|
|
147
|
+
["-0", "--null", "-r", "--no-run-if-empty"].includes(tokens[index])
|
|
148
|
+
) {
|
|
149
|
+
index += 1;
|
|
150
|
+
}
|
|
151
|
+
if (tokens[index] !== "ls") return false;
|
|
152
|
+
return tokens.slice(index + 1).every((token) =>
|
|
153
|
+
/^(?:-[A-Za-z0-9]+|--(?:all|almost-all|directory|inode|long|numeric-uid-gid|reverse|size|human-readable))$/.test(
|
|
154
|
+
token
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
function isReadOnlyFindCommand(command = "") {
|
|
141
160
|
const normalized = stripBenignRedirections(command);
|
|
142
161
|
if (!/^find\s+/.test(normalized)) return false;
|
|
@@ -209,6 +228,16 @@ function isReadOnlyFindCommand(command = "") {
|
|
|
209
228
|
return parenthesisDepth === 0 && maxDepth !== null;
|
|
210
229
|
}
|
|
211
230
|
|
|
231
|
+
function isNonMutatingFindCommand(command = "") {
|
|
232
|
+
const normalized = stripBenignRedirections(command);
|
|
233
|
+
if (!/^find\s+/.test(normalized)) return false;
|
|
234
|
+
if (/(^|\s)(-delete|-exec|-execdir|-ok|-okdir|-fprint|-fprintf|-fls)\b/.test(normalized)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const unquoted = stripQuotedSegments(normalized);
|
|
238
|
+
return !/[|<>;&`$]/.test(unquoted) && !hasActiveShellExpansion(normalized);
|
|
239
|
+
}
|
|
240
|
+
|
|
212
241
|
function isUnboundedRecursiveGrep(command = "") {
|
|
213
242
|
const normalized = stripBenignRedirections(command);
|
|
214
243
|
if (!/^grep\s+/.test(normalized)) return false;
|
|
@@ -1860,6 +1889,7 @@ function classifySimpleCommand(normalized) {
|
|
|
1860
1889
|
isReadOnlyUniqFilter(commandForPatternMatching) ||
|
|
1861
1890
|
isReadOnlyDigestCommand(commandForPatternMatching) ||
|
|
1862
1891
|
isReadOnlyDiffCommand(commandForPatternMatching) ||
|
|
1892
|
+
isReadOnlyXargsListFilter(commandForPatternMatching) ||
|
|
1863
1893
|
isReadOnlyFindCommand(normalized) ||
|
|
1864
1894
|
(!hasActiveShellExpansion(benignRedirectCommand) && isReadOnlyShellCondition(benignRedirectCommand))
|
|
1865
1895
|
) {
|
|
@@ -1921,6 +1951,17 @@ function classifySimpleCommand(normalized) {
|
|
|
1921
1951
|
return { category: "env-setup", needsNetwork: false, writesWorkspace: true };
|
|
1922
1952
|
}
|
|
1923
1953
|
|
|
1954
|
+
if (isNonMutatingFindCommand(commandForPatternMatching)) {
|
|
1955
|
+
return {
|
|
1956
|
+
category: "general-shell",
|
|
1957
|
+
needsNetwork: false,
|
|
1958
|
+
writesWorkspace: true,
|
|
1959
|
+
semanticMayMutateProject: false,
|
|
1960
|
+
reason:
|
|
1961
|
+
"Unbounded find inspection remains under trusted shell policy but cannot mutate project state.",
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1924
1965
|
return {
|
|
1925
1966
|
category: "general-shell",
|
|
1926
1967
|
needsNetwork: false,
|
|
@@ -2770,6 +2811,13 @@ function classifyPipelineSequence(normalized) {
|
|
|
2770
2811
|
category: "general-shell",
|
|
2771
2812
|
needsNetwork: classifications.some((classification) => classification.needsNetwork),
|
|
2772
2813
|
writesWorkspace: classifications.some((classification) => classification.writesWorkspace),
|
|
2814
|
+
...(classifications.every(
|
|
2815
|
+
(classification) =>
|
|
2816
|
+
classification.category === "read-only" ||
|
|
2817
|
+
classification.semanticMayMutateProject === false
|
|
2818
|
+
)
|
|
2819
|
+
? { semanticMayMutateProject: false }
|
|
2820
|
+
: {}),
|
|
2773
2821
|
reason: `Shell pipeline includes a broad segment and requires trusted shell policy: ${normalized}`,
|
|
2774
2822
|
};
|
|
2775
2823
|
}
|
|
@@ -821,6 +821,32 @@ export async function collectDocumentSourceDocuments(commandCwd, exactInputPaths
|
|
|
821
821
|
const maxTotalBytes = 4 * 1024 * 1024;
|
|
822
822
|
const maxFileBytes = 512 * 1024;
|
|
823
823
|
|
|
824
|
+
const knownPaths = new Set();
|
|
825
|
+
for (const value of Array.isArray(exactInputPaths) ? exactInputPaths : []) {
|
|
826
|
+
if (documents.length >= maxFiles || totalBytes >= maxTotalBytes) break;
|
|
827
|
+
const absolutePath = path.resolve(commandCwd, String(value || ""));
|
|
828
|
+
if (!isInsideRoot(commandCwd, absolutePath)) continue;
|
|
829
|
+
const relativePath = portablePath(path.relative(commandCwd, absolutePath));
|
|
830
|
+
if (!relativePath || knownPaths.has(relativePath)) continue;
|
|
831
|
+
if (!DOCUMENT_EXTENSIONS.has(path.extname(absolutePath).toLowerCase())) continue;
|
|
832
|
+
try {
|
|
833
|
+
const stat = await fs.stat(absolutePath);
|
|
834
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > maxFileBytes) continue;
|
|
835
|
+
if (totalBytes + stat.size > maxTotalBytes) continue;
|
|
836
|
+
const text = await fs.readFile(absolutePath, "utf8");
|
|
837
|
+
documents.push({ path: relativePath, text });
|
|
838
|
+
knownPaths.add(relativePath);
|
|
839
|
+
totalBytes += stat.size;
|
|
840
|
+
} catch {
|
|
841
|
+
// Exact unreadable sources remain visible to the existing source/evidence gates.
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Declared task inputs are authoritative. Repository-wide discovery is a
|
|
846
|
+
// fallback for tasks that did not name a readable source, not extra context
|
|
847
|
+
// to blend into a scoped document review.
|
|
848
|
+
if (documents.length > 0) return documents;
|
|
849
|
+
|
|
824
850
|
async function visit(directory, sourceRoot = false, depth = 0) {
|
|
825
851
|
if (documents.length >= maxFiles || totalBytes >= maxTotalBytes || depth > 5) return;
|
|
826
852
|
let entries;
|
|
@@ -857,26 +883,6 @@ export async function collectDocumentSourceDocuments(commandCwd, exactInputPaths
|
|
|
857
883
|
}
|
|
858
884
|
|
|
859
885
|
await visit(commandCwd, false, 0);
|
|
860
|
-
const knownPaths = new Set(documents.map((item) => item.path));
|
|
861
|
-
for (const value of Array.isArray(exactInputPaths) ? exactInputPaths : []) {
|
|
862
|
-
if (documents.length >= maxFiles || totalBytes >= maxTotalBytes) break;
|
|
863
|
-
const absolutePath = path.resolve(commandCwd, String(value || ""));
|
|
864
|
-
if (!isInsideRoot(commandCwd, absolutePath)) continue;
|
|
865
|
-
const relativePath = portablePath(path.relative(commandCwd, absolutePath));
|
|
866
|
-
if (!relativePath || knownPaths.has(relativePath)) continue;
|
|
867
|
-
if (!DOCUMENT_EXTENSIONS.has(path.extname(absolutePath).toLowerCase())) continue;
|
|
868
|
-
try {
|
|
869
|
-
const stat = await fs.stat(absolutePath);
|
|
870
|
-
if (!stat.isFile() || stat.size <= 0 || stat.size > maxFileBytes) continue;
|
|
871
|
-
if (totalBytes + stat.size > maxTotalBytes) continue;
|
|
872
|
-
const text = await fs.readFile(absolutePath, "utf8");
|
|
873
|
-
documents.push({ path: relativePath, text });
|
|
874
|
-
knownPaths.add(relativePath);
|
|
875
|
-
totalBytes += stat.size;
|
|
876
|
-
} catch {
|
|
877
|
-
// Exact unreadable sources remain visible to the existing source/evidence gates.
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
886
|
return documents;
|
|
881
887
|
}
|
|
882
888
|
|
package/src/scs-evidence.js
CHANGED
|
@@ -670,9 +670,40 @@ function artifactRequestHasOutputIntent(goal = "", taskProfile = "") {
|
|
|
670
670
|
);
|
|
671
671
|
}
|
|
672
672
|
|
|
673
|
+
export function hostManagedDocumentCompilationRequested(goal = "") {
|
|
674
|
+
const source = String(goal || "").replace(/\s+/gu, " ").trim();
|
|
675
|
+
if (!source) return false;
|
|
676
|
+
if (
|
|
677
|
+
/\bdo\s+not\s+(?:invoke|run|use)\b[^.!?;。!?;]{0,180}\b(?:document\s+compiler|latexmk|pdflatex|xelatex|lualatex|latex|pandoc|make)\b[^.!?;。!?;]{0,180}\b(?:labcanvas\s+)?host\b/iu.test(
|
|
678
|
+
source
|
|
679
|
+
) ||
|
|
680
|
+
/(?:不要|不得|无需|不需要)(?:调用|运行|使用).{0,80}(?:LaTeX|latexmk|pdflatex|xelatex|文档编译器).{0,100}(?:由|交给)(?:\s*LabCanvas)?(?:主机|宿主)(?:编译|构建|渲染|验证)/u.test(
|
|
681
|
+
source
|
|
682
|
+
)
|
|
683
|
+
) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const ownershipPattern =
|
|
688
|
+
/\b(?:the\s+)?(?:labcanvas\s+)?host(?:\s+(?:compiler|recovery|stage)){0,2}\s+(?:alone\s+)?(?:owns|handles?|performs?|will\s+(?:build|compile|handle|perform|render|validate))\b[^.!?;。!?;]{0,120}\b(?:compil(?:ation|e)|document|latex|pdf|render|validation)\b/giu;
|
|
689
|
+
for (const match of source.matchAll(ownershipPattern)) {
|
|
690
|
+
const prefix = source.slice(Math.max(0, Number(match.index || 0) - 180), Number(match.index || 0));
|
|
691
|
+
if (
|
|
692
|
+
/\b(?:if|when)\b[^.!?;。!?;]{0,140}\b(?:cannot|can't|fails?|failure|unavailable|unsupported)\b/iu.test(
|
|
693
|
+
prefix
|
|
694
|
+
)
|
|
695
|
+
) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
|
|
673
703
|
export function inferRequestedArtifactRequirements(goal = "", taskProfile = "") {
|
|
674
704
|
const source = stripForbiddenLanguage(String(goal || ""));
|
|
675
705
|
if (!artifactRequestHasOutputIntent(source, taskProfile)) return [];
|
|
706
|
+
const hostManagedCompilation = hostManagedDocumentCompilationRequested(goal);
|
|
676
707
|
|
|
677
708
|
const requirements = [];
|
|
678
709
|
const add = (requirement) => {
|
|
@@ -681,6 +712,7 @@ export function inferRequestedArtifactRequirements(goal = "", taskProfile = "")
|
|
|
681
712
|
};
|
|
682
713
|
|
|
683
714
|
for (const format of REQUESTED_ARTIFACT_FORMATS) {
|
|
715
|
+
if (hostManagedCompilation && format.extension === ".pdf") continue;
|
|
684
716
|
if (!requestedArtifactFormatHasOutputIntent(source, format)) continue;
|
|
685
717
|
add({
|
|
686
718
|
id: `format:${format.extension}`,
|
|
@@ -702,6 +734,7 @@ export function inferRequestedArtifactRequirements(goal = "", taskProfile = "")
|
|
|
702
734
|
}
|
|
703
735
|
|
|
704
736
|
if (
|
|
737
|
+
!hostManagedCompilation &&
|
|
705
738
|
/\bprintable\b/i.test(source) &&
|
|
706
739
|
/\b(?:answer|deck|document|handout|material|practice|sheet|slides?|worksheet)\b/i.test(source)
|
|
707
740
|
) {
|
|
@@ -2170,9 +2203,16 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
|
|
|
2170
2203
|
inferExactOutputPaths(positiveEvidenceGoal),
|
|
2171
2204
|
excludedOutputPaths
|
|
2172
2205
|
);
|
|
2206
|
+
const hostManagedDocumentCompilation = hostManagedDocumentCompilationRequested(
|
|
2207
|
+
evidenceGoal
|
|
2208
|
+
);
|
|
2173
2209
|
const exactOutputPaths = filterExplicitlyExcludedOutputPaths(
|
|
2174
2210
|
applyScopedArtifactRoot(inferredOutputPaths, artifactRoot),
|
|
2175
2211
|
excludedOutputPaths
|
|
2212
|
+
).filter(
|
|
2213
|
+
(item) =>
|
|
2214
|
+
!hostManagedDocumentCompilation ||
|
|
2215
|
+
path.extname(String(item || "")).toLocaleLowerCase("en-US") !== ".pdf"
|
|
2176
2216
|
);
|
|
2177
2217
|
const exactInputPaths = filterExplicitlyExcludedOutputPaths(
|
|
2178
2218
|
inferExactInputPaths(positiveEvidenceGoal),
|
|
@@ -2198,6 +2238,7 @@ export function deriveScsTaskContract({ goal = "", taskProfile = "", acceptanceC
|
|
|
2198
2238
|
forbiddenActions: inferForbiddenActions(evidenceGoal),
|
|
2199
2239
|
exactOutputPaths,
|
|
2200
2240
|
requiredArtifactKinds,
|
|
2241
|
+
hostManagedDocumentCompilation,
|
|
2201
2242
|
scopedArtifactDeliverable,
|
|
2202
2243
|
scopedArtifactOperation,
|
|
2203
2244
|
excludedOutputPaths,
|