@lazyingart/agintiflow 0.20.247 → 0.20.249
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/docs/supervision-campaign-ledger.md +49 -0
- package/package.json +1 -1
- package/scripts/smoke-capabilities.js +28 -3
- package/scripts/smoke-context-budget-recovery.js +116 -0
- package/scripts/smoke-deep-research.js +45 -0
- package/scripts/smoke-dynamic-step-budget.js +104 -0
- package/scripts/smoke-progressive-tool-selection.js +53 -0
- package/scripts/smoke-run-stdin.js +7 -1
- package/scripts/smoke-scs-evidence-visibility.js +41 -0
- package/src/agent-runner.js +96 -23
- package/src/cli.js +8 -1
- package/src/research-routing.js +20 -0
- package/src/scs-evidence.js +33 -9
- package/src/step-budget-controller.js +20 -2
|
@@ -330,3 +330,52 @@ event sequence, a project handoff with exact status and verification commands,
|
|
|
330
330
|
and no lingering job tmux session. No source patch or npm release was needed;
|
|
331
331
|
this run validates the published package's general long-job, resume, context,
|
|
332
332
|
deduplication, and cleanup contracts without task-specific routing.
|
|
333
|
+
|
|
334
|
+
### Host-managed response roles and permission integrity
|
|
335
|
+
|
|
336
|
+
`permission-resilient-synthesis-030` traced a real LabCanvas career-report
|
|
337
|
+
failure to the integration boundary rather than weakening AgInTiFlow's
|
|
338
|
+
permission guard. LabCanvas invoked the host-managed report under the role
|
|
339
|
+
`career_research`, while its response-only classifier recognized only the older
|
|
340
|
+
`career_daily` alias. The model was consequently offered file tools in a safe,
|
|
341
|
+
read-only run; when it tried to save its already synthesized report,
|
|
342
|
+
AgInTiFlow correctly persisted a `permission_required` pause.
|
|
343
|
+
|
|
344
|
+
LabCanvas now classifies the full `career-research-*` and
|
|
345
|
+
`daily-organizer-*` role families as host-managed response turns. Those turns
|
|
346
|
+
use AgInTi's `chatops` profile with shell, file, and auxiliary tools disabled;
|
|
347
|
+
the host owns persistence, compilation, quality validation, and delivery.
|
|
348
|
+
General worker roles retain their existing writable Docker contract, and safe
|
|
349
|
+
mode still blocks genuine unapproved writes.
|
|
350
|
+
|
|
351
|
+
A fresh installed `0.20.248` DeepSeek run completed the imperfect synthesis
|
|
352
|
+
prompt in one model turn. Independent event inspection found zero tool calls,
|
|
353
|
+
zero permission events, one normal `session.finished`, a complete 1,893-byte
|
|
354
|
+
answer, and no task artifact mutation. This was an AgenticApp integration fix;
|
|
355
|
+
no AgInTiFlow runtime or npm release change was required.
|
|
356
|
+
|
|
357
|
+
### Reader-facing report quality and local editorial routing
|
|
358
|
+
|
|
359
|
+
`research-pdf-quality-034` exercised a normal scheduled-research packet through
|
|
360
|
+
the AgInTi-backed LabCanvas worker. DeepSeek revised an existing local report
|
|
361
|
+
into a 17,557-byte Chinese scientific review with nine traceable sources,
|
|
362
|
+
source-level methods/results/limitations, cross-source synthesis and tensions,
|
|
363
|
+
explicit evidence boundaries, actionable experiments, and references. The
|
|
364
|
+
first host compile exposed a nearly empty final page, and the prior agent repair
|
|
365
|
+
claimed success without changing the source.
|
|
366
|
+
|
|
367
|
+
LabCanvas now treats the PDF as a different deliverable from the concise chat
|
|
368
|
+
brief. It audits every configured content dimension, keeps orchestration
|
|
369
|
+
provenance out of the reader document, extracts text per page, renders private
|
|
370
|
+
page previews, rejects orphan pages, retries one conservative compact layout,
|
|
371
|
+
rebuilds stale sibling PDFs, and can deterministically adopt a corrected
|
|
372
|
+
host-built PDF during stored-result repair. The accepted four-page PDF has
|
|
373
|
+
embedded CJK fonts, page body counts `1584, 1988, 1416, 2058`, and no visual or
|
|
374
|
+
layout issues. Stored replay covered every task item with no model rerun or
|
|
375
|
+
external write.
|
|
376
|
+
|
|
377
|
+
The scenario also exposed two general AgInTi routing false positives. Explicit
|
|
378
|
+
local report paths and existing-document revision language now count as local
|
|
379
|
+
workspace intent, so surrounding research policy does not force a new deep
|
|
380
|
+
research route. The phrase `page-safe` no longer creates a browser-evidence
|
|
381
|
+
requirement. Focused routing/evidence smokes and the full npm suite pass.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.249",
|
|
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",
|
|
@@ -49,6 +49,20 @@ async function runCliIn(cwd, args, envOverrides = {}) {
|
|
|
49
49
|
return result.stdout;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
async function runCliAllowStopped(args, envOverrides = {}) {
|
|
53
|
+
try {
|
|
54
|
+
const stdout = await runCli(args, envOverrides);
|
|
55
|
+
return { stdout, stderr: "", exitCode: 0 };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (!Number.isInteger(error?.code)) throw error;
|
|
58
|
+
return {
|
|
59
|
+
stdout: String(error.stdout || ""),
|
|
60
|
+
stderr: String(error.stderr || ""),
|
|
61
|
+
exitCode: error.code,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
try {
|
|
53
67
|
await runCli(["init"]);
|
|
54
68
|
const agintiMd = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
|
|
@@ -188,7 +202,7 @@ try {
|
|
|
188
202
|
const doctor = JSON.parse(await runCli(["doctor", "--capabilities", "--json"]));
|
|
189
203
|
assert(doctor.project.root === tempRoot, "doctor --capabilities used the wrong project root");
|
|
190
204
|
assert(doctor.project.instructionsPresent, "doctor --capabilities did not report AGINTI.md");
|
|
191
|
-
const envSandboxRun = await
|
|
205
|
+
const envSandboxRun = await runCliAllowStopped(
|
|
192
206
|
["--provider", "mock", "--routing", "manual", "--model", "mock-agent", "--max-steps", "1", "env sandbox smoke"],
|
|
193
207
|
{
|
|
194
208
|
SANDBOX_MODE: "host",
|
|
@@ -196,8 +210,19 @@ try {
|
|
|
196
210
|
USE_DOCKER_SANDBOX: "false",
|
|
197
211
|
}
|
|
198
212
|
);
|
|
199
|
-
assert(envSandboxRun.
|
|
200
|
-
assert(
|
|
213
|
+
assert(envSandboxRun.exitCode === 1, "a step-budget-stopped one-shot CLI run did not report failure");
|
|
214
|
+
assert(
|
|
215
|
+
envSandboxRun.stderr.includes("Stopped after 1 steps without finish()."),
|
|
216
|
+
"a step-budget-stopped one-shot CLI run did not expose its terminal reason"
|
|
217
|
+
);
|
|
218
|
+
assert(
|
|
219
|
+
envSandboxRun.stdout.includes("Shell: host policy=allow"),
|
|
220
|
+
"one-shot CLI did not respect host sandbox env defaults"
|
|
221
|
+
);
|
|
222
|
+
assert(
|
|
223
|
+
!envSandboxRun.stdout.includes("Docker workspace:"),
|
|
224
|
+
"one-shot CLI forced Docker despite host sandbox env defaults"
|
|
225
|
+
);
|
|
201
226
|
|
|
202
227
|
console.log(
|
|
203
228
|
JSON.stringify(
|
|
@@ -415,11 +415,126 @@ function boundedOutputReadPair(index, generation) {
|
|
|
415
415
|
];
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
function exactInputEvidencePair(index) {
|
|
419
|
+
const id = `exact-input-${index}`;
|
|
420
|
+
const startLine = 1 + (index - 1) * 45;
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
role: "assistant",
|
|
424
|
+
content: "",
|
|
425
|
+
reasoning_content: "Read one bounded exact-input evidence range.",
|
|
426
|
+
tool_calls: [
|
|
427
|
+
{
|
|
428
|
+
id,
|
|
429
|
+
type: "function",
|
|
430
|
+
function: {
|
|
431
|
+
name: "read_file",
|
|
432
|
+
arguments: JSON.stringify({
|
|
433
|
+
path: "tmp/reliability-evidence-pass.md",
|
|
434
|
+
startLine,
|
|
435
|
+
lineLimit: 45,
|
|
436
|
+
}),
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
role: "tool",
|
|
443
|
+
tool_call_id: id,
|
|
444
|
+
content: JSON.stringify({
|
|
445
|
+
ok: true,
|
|
446
|
+
toolName: "read_file",
|
|
447
|
+
path: "tmp/reliability-evidence-pass.md",
|
|
448
|
+
startLine,
|
|
449
|
+
lineLimit: 45,
|
|
450
|
+
lineCount: 180,
|
|
451
|
+
bytes: 24000,
|
|
452
|
+
sha256: "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
|
453
|
+
contentTruncated: false,
|
|
454
|
+
content: `EXACT-INPUT-RANGE-${index}\n${`source evidence ${index} `.repeat(220)}`,
|
|
455
|
+
}),
|
|
456
|
+
},
|
|
457
|
+
];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function readOnlyDiagnosticPair(index) {
|
|
461
|
+
const id = `diagnostic-${index}`;
|
|
462
|
+
const command = `python3 -c "print('diagnostic ${index}')"`;
|
|
463
|
+
return [
|
|
464
|
+
{
|
|
465
|
+
role: "assistant",
|
|
466
|
+
content: "",
|
|
467
|
+
reasoning_content: "Inspect one diagnostic without changing task outputs.",
|
|
468
|
+
tool_calls: [
|
|
469
|
+
{
|
|
470
|
+
id,
|
|
471
|
+
type: "function",
|
|
472
|
+
function: { name: "run_command", arguments: JSON.stringify({ command }) },
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
role: "tool",
|
|
478
|
+
tool_call_id: id,
|
|
479
|
+
content: JSON.stringify({
|
|
480
|
+
ok: true,
|
|
481
|
+
toolName: "run_command",
|
|
482
|
+
args: { command },
|
|
483
|
+
exitCode: 0,
|
|
484
|
+
stdout: `DIAGNOSTIC-${index}\n${"read-only shell output ".repeat(80)}`,
|
|
485
|
+
}),
|
|
486
|
+
},
|
|
487
|
+
];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const exactInputCoverageState = {
|
|
491
|
+
goal: [
|
|
492
|
+
"Read tmp/reliability-evidence-pass.md as the exact read-only input.",
|
|
493
|
+
"Rewrite agent-reliability-evidence-review.md.",
|
|
494
|
+
"Rebuild sources.json.",
|
|
495
|
+
].join("\n"),
|
|
496
|
+
plan: "Use retained evidence and create the two outputs.",
|
|
497
|
+
meta: {
|
|
498
|
+
scs: {
|
|
499
|
+
taskContract: {
|
|
500
|
+
exactInputPaths: ["tmp/reliability-evidence-pass.md"],
|
|
501
|
+
exactOutputPaths: ["agent-reliability-evidence-review.md", "sources.json"],
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
messages: [
|
|
506
|
+
{ role: "system", content: `SYSTEM-INPUT-COVERAGE\n${"policy ".repeat(5000)}` },
|
|
507
|
+
{ role: "user", content: "Create a source-grounded reader-facing report." },
|
|
508
|
+
...Array.from({ length: 4 }, (_, index) => exactInputEvidencePair(index + 1)).flat(),
|
|
509
|
+
...Array.from({ length: 10 }, (_, index) => readOnlyDiagnosticPair(index + 1)).flat(),
|
|
510
|
+
...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 9)).flat(),
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
const exactInputCoverage = buildContextBudgetCompactionMessages(
|
|
514
|
+
exactInputCoverageState,
|
|
515
|
+
{ ...config, provider: "deepseek", model: "deepseek-chat" },
|
|
516
|
+
{ title: "", url: "" },
|
|
517
|
+
20,
|
|
518
|
+
{ reason: "preserve all exact-input source ranges over diagnostics" }
|
|
519
|
+
);
|
|
520
|
+
const exactInputCoverageText = exactInputCoverage.map((message) => message.content || "").join("\n");
|
|
521
|
+
for (let index = 1; index <= 4; index += 1) {
|
|
522
|
+
assert.ok(
|
|
523
|
+
exactInputCoverageText.includes(`EXACT-INPUT-RANGE-${index}`),
|
|
524
|
+
`compaction lost exact input evidence range ${index}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
assert.ok(
|
|
528
|
+
estimateMessageTokens(exactInputCoverage) <= 12288,
|
|
529
|
+
"exact-input evidence retention exceeded the bounded retry target"
|
|
530
|
+
);
|
|
531
|
+
|
|
418
532
|
const twiceCompactedState = {
|
|
419
533
|
...compactionState,
|
|
420
534
|
meta: {
|
|
421
535
|
scs: {
|
|
422
536
|
taskContract: {
|
|
537
|
+
exactInputPaths: ["reports/reliability.md"],
|
|
423
538
|
exactOutputPaths: ["agent-reliability-evidence-review.md", "sources.json"],
|
|
424
539
|
},
|
|
425
540
|
},
|
|
@@ -427,6 +542,7 @@ const twiceCompactedState = {
|
|
|
427
542
|
messages: [
|
|
428
543
|
...deepSeekRuntimeMessages,
|
|
429
544
|
...Array.from({ length: 14 }, (_, index) => noisyFullReadPair(index + 1, 2)).flat(),
|
|
545
|
+
...Array.from({ length: 8 }, (_, index) => readOnlyDiagnosticPair(index + 1)).flat(),
|
|
430
546
|
...Array.from({ length: 6 }, (_, index) => boundedOutputReadPair(index + 1, 2)).flat(),
|
|
431
547
|
],
|
|
432
548
|
};
|
|
@@ -9,6 +9,7 @@ import { flushHousekeeping } from "../src/housekeeping.js";
|
|
|
9
9
|
import { requestNextStep, toolChoiceForProvider } from "../src/model-client.js";
|
|
10
10
|
import { providerStructuredOutputAttempts } from "../src/provider-contract.js";
|
|
11
11
|
import {
|
|
12
|
+
hasExplicitDeepResearchSuppression,
|
|
12
13
|
hasExplicitDeepResearchIntent,
|
|
13
14
|
hasLocalResearchWorkspaceIntent,
|
|
14
15
|
shouldStartWithDeepResearch,
|
|
@@ -107,6 +108,50 @@ async function main() {
|
|
|
107
108
|
shouldStartWithDeepResearch("Write a deep web research report comparing three primary papers."),
|
|
108
109
|
"standalone deep research no longer starts with the bounded research workflow"
|
|
109
110
|
);
|
|
111
|
+
const retainedResearchGoal = [
|
|
112
|
+
"Continue the interrupted evidence-review task from its saved state.",
|
|
113
|
+
"Do not run deep_research again. Reuse the completed research artifact.",
|
|
114
|
+
"Rewrite agent-reliability-evidence-review.md from the retained evidence.",
|
|
115
|
+
].join("\n");
|
|
116
|
+
assert(
|
|
117
|
+
hasExplicitDeepResearchSuppression(retainedResearchGoal),
|
|
118
|
+
"an explicit completed-research reuse instruction was not detected"
|
|
119
|
+
);
|
|
120
|
+
assert(
|
|
121
|
+
!shouldStartWithDeepResearch(retainedResearchGoal, inspectedLocalEvidenceMessages),
|
|
122
|
+
"an explicit prohibition still narrowed the next turn to deep_research"
|
|
123
|
+
);
|
|
124
|
+
const coordinatedSuppressionGoal = [
|
|
125
|
+
"Resume the saved evidence-review task.",
|
|
126
|
+
"Do not restart the task, run deep_research, or reopen broad discovery.",
|
|
127
|
+
"Use the retained completed evidence and rebuild sources.json.",
|
|
128
|
+
].join("\n");
|
|
129
|
+
assert(
|
|
130
|
+
hasExplicitDeepResearchSuppression(coordinatedSuppressionGoal),
|
|
131
|
+
"a coordinated do-not clause did not suppress deep_research"
|
|
132
|
+
);
|
|
133
|
+
assert(
|
|
134
|
+
!shouldStartWithDeepResearch(localEvidenceGoal, [
|
|
135
|
+
{ role: "user", content: coordinatedSuppressionGoal },
|
|
136
|
+
...inspectedLocalEvidenceMessages.slice(1),
|
|
137
|
+
]),
|
|
138
|
+
"a resumed retained-evidence repair was forced back into deep_research after inspection"
|
|
139
|
+
);
|
|
140
|
+
assert(
|
|
141
|
+
shouldStartWithDeepResearch(localEvidenceGoal, [
|
|
142
|
+
...inspectedLocalEvidenceMessages,
|
|
143
|
+
{ role: "user", content: "Run a fresh deep research pass now; the retained evidence is stale." },
|
|
144
|
+
{
|
|
145
|
+
role: "assistant",
|
|
146
|
+
tool_calls: [{ id: "fresh-report", function: { name: "read_file", arguments: '{"path":"agent-reliability-evidence-review.md"}' } }],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
role: "assistant",
|
|
150
|
+
tool_calls: [{ id: "fresh-sources", function: { name: "read_file", arguments: '{"path":"sources.json"}' } }],
|
|
151
|
+
},
|
|
152
|
+
]),
|
|
153
|
+
"an older completed-research context suppressed a newer explicit refresh request"
|
|
154
|
+
);
|
|
110
155
|
assert(
|
|
111
156
|
!hasExplicitDeepResearchIntent("Create a phone-friendly document from this folder.", [
|
|
112
157
|
{
|
|
@@ -1364,6 +1364,33 @@ try {
|
|
|
1364
1364
|
`a status wrapper changed the inner command's mutation identity: ${command}`
|
|
1365
1365
|
);
|
|
1366
1366
|
}
|
|
1367
|
+
const readOnlyValidatorState = { meta: { goalContract: { revision: 1 } } };
|
|
1368
|
+
const readOnlyValidatorResult = {
|
|
1369
|
+
toolName: "run_command",
|
|
1370
|
+
ok: true,
|
|
1371
|
+
exitCode: 0,
|
|
1372
|
+
args: {
|
|
1373
|
+
command:
|
|
1374
|
+
'python3 tmp/external_agent_reliability_quality.py . ; echo "EXIT=$?"',
|
|
1375
|
+
},
|
|
1376
|
+
stdout: "agent reliability research contract passed\nEXIT=1\n",
|
|
1377
|
+
stderr: "agent reliability research quality failed\n",
|
|
1378
|
+
commandPolicy: {
|
|
1379
|
+
category: "toolchain",
|
|
1380
|
+
writesWorkspace: true,
|
|
1381
|
+
mayMutateProject: false,
|
|
1382
|
+
substantiveTest: false,
|
|
1383
|
+
},
|
|
1384
|
+
};
|
|
1385
|
+
recordProjectVerificationOutcome(readOnlyValidatorState, readOnlyValidatorResult, {
|
|
1386
|
+
commandCwd: workspace,
|
|
1387
|
+
taskProfile: "writing",
|
|
1388
|
+
});
|
|
1389
|
+
assert(
|
|
1390
|
+
readOnlyValidatorState.meta.projectVerification?.mutationRevision === 0 &&
|
|
1391
|
+
readOnlyValidatorResult.projectMutationRevision === 0,
|
|
1392
|
+
"a semantically read-only validator fabricated project mutation progress"
|
|
1393
|
+
);
|
|
1367
1394
|
const shellMutationState = { meta: { goalContract: { revision: 1 } } };
|
|
1368
1395
|
const shellMutationResult = {
|
|
1369
1396
|
toolName: "run_command",
|
|
@@ -2479,6 +2506,13 @@ try {
|
|
|
2479
2506
|
args: { command: "npm test && git pull --ff-only" },
|
|
2480
2507
|
stdout: "1 test passed",
|
|
2481
2508
|
stderr: "",
|
|
2509
|
+
commandPolicy: {
|
|
2510
|
+
category: "git-remote",
|
|
2511
|
+
writesWorkspace: true,
|
|
2512
|
+
mayMutateProject: false,
|
|
2513
|
+
substantiveTest: true,
|
|
2514
|
+
gitOnly: false,
|
|
2515
|
+
},
|
|
2482
2516
|
};
|
|
2483
2517
|
recordProjectVerificationOutcome(testBeforePullState, testBeforePullResult, {
|
|
2484
2518
|
commandCwd: workspace,
|
|
@@ -3067,6 +3101,21 @@ try {
|
|
|
3067
3101
|
shouldResetStaticDiscoveryPhase({ ok: true, toolName: "write_file", args: { path: "report.md" } }),
|
|
3068
3102
|
"successful output creation should reset static discovery convergence"
|
|
3069
3103
|
);
|
|
3104
|
+
assert(
|
|
3105
|
+
!shouldResetStaticDiscoveryPhase({
|
|
3106
|
+
ok: true,
|
|
3107
|
+
toolName: "run_command",
|
|
3108
|
+
args: { command: "python3 - <<'PY'\nprint('inspect only')\nPY" },
|
|
3109
|
+
commandPolicy: {
|
|
3110
|
+
writesWorkspace: true,
|
|
3111
|
+
mayMutateProject: false,
|
|
3112
|
+
substantiveTest: false,
|
|
3113
|
+
},
|
|
3114
|
+
exitCode: 0,
|
|
3115
|
+
stdout: "inspect only",
|
|
3116
|
+
}),
|
|
3117
|
+
"a read-only diagnostic shell probe reset static discovery because of a conservative write heuristic"
|
|
3118
|
+
);
|
|
3070
3119
|
const uniqueDiscovery = {};
|
|
3071
3120
|
recordStaticDiscoveryProgress(uniqueDiscovery, "read_file:/reference/A.md");
|
|
3072
3121
|
recordStaticDiscoveryProgress(uniqueDiscovery, "read_file:/reference/A.md");
|
|
@@ -3095,6 +3144,32 @@ try {
|
|
|
3095
3144
|
JSON.stringify(compactedDiscoveryState.meta.toolLoop.warned) === JSON.stringify(["run_command:keep"]),
|
|
3096
3145
|
"context recovery did not clear only stale static-read warnings"
|
|
3097
3146
|
);
|
|
3147
|
+
const retainedDiscoveryState = {
|
|
3148
|
+
meta: {
|
|
3149
|
+
toolLoop: {
|
|
3150
|
+
recent: [],
|
|
3151
|
+
warned: ["file-read:/reference/A.md"],
|
|
3152
|
+
staticCounts: { "file-read:/reference/A.md": 2 },
|
|
3153
|
+
staticOrder: ["file-read:/reference/A.md"],
|
|
3154
|
+
staticTotal: 1,
|
|
3155
|
+
staticCallTotal: 2,
|
|
3156
|
+
},
|
|
3157
|
+
},
|
|
3158
|
+
};
|
|
3159
|
+
resetStaticDiscoveryAfterContextLoss(
|
|
3160
|
+
retainedDiscoveryState,
|
|
3161
|
+
"proactive-context-compaction",
|
|
3162
|
+
{ preserveStaticEvidence: true }
|
|
3163
|
+
);
|
|
3164
|
+
assert(
|
|
3165
|
+
retainedDiscoveryState.meta.toolLoop.staticTotal === 1 &&
|
|
3166
|
+
retainedDiscoveryState.meta.toolLoop.staticCounts["file-read:/reference/A.md"] === 2,
|
|
3167
|
+
"lossless context compaction reopened already completed discovery"
|
|
3168
|
+
);
|
|
3169
|
+
assert(
|
|
3170
|
+
retainedDiscoveryState.meta.toolLoop.lastContextRecovery?.preservedStaticEvidence === true,
|
|
3171
|
+
"lossless context compaction did not record preserved discovery evidence"
|
|
3172
|
+
);
|
|
3098
3173
|
const exactReadSignature = staticToolCallSignature("read_file", { path: "/reference/A.md" }, {
|
|
3099
3174
|
commandCwd: workspace,
|
|
3100
3175
|
});
|
|
@@ -6065,6 +6140,35 @@ try {
|
|
|
6065
6140
|
events: [],
|
|
6066
6141
|
});
|
|
6067
6142
|
assert(!staticOnlyDecision.approved, "budget gate treated static discovery alone as implementation progress");
|
|
6143
|
+
const readOnlyShellDecision = decideStepBudgetExtension({
|
|
6144
|
+
config: { scsActive: true, commandCwd: "/tmp/workspace" },
|
|
6145
|
+
budget: createStepBudgetState(
|
|
6146
|
+
{ provider: "localllm", maxSteps: 12, dynamicSteps: "on", dynamicStepExtensionLimit: 2, scsActive: true },
|
|
6147
|
+
{ meta: {}, stepsCompleted: 0 }
|
|
6148
|
+
),
|
|
6149
|
+
step: 11,
|
|
6150
|
+
state: {
|
|
6151
|
+
messages: Array.from({ length: 5 }, (_, index) =>
|
|
6152
|
+
toolMessage({
|
|
6153
|
+
toolName: "run_command",
|
|
6154
|
+
ok: true,
|
|
6155
|
+
args: { command: `python3 -c "print('inspect ${index}')"` },
|
|
6156
|
+
commandPolicy: {
|
|
6157
|
+
writesWorkspace: true,
|
|
6158
|
+
mayMutateProject: false,
|
|
6159
|
+
substantiveTest: false,
|
|
6160
|
+
},
|
|
6161
|
+
exitCode: 0,
|
|
6162
|
+
stdout: `inspection ${index}`,
|
|
6163
|
+
})
|
|
6164
|
+
),
|
|
6165
|
+
},
|
|
6166
|
+
events: [],
|
|
6167
|
+
});
|
|
6168
|
+
assert(
|
|
6169
|
+
!readOnlyShellDecision.approved,
|
|
6170
|
+
"budget gate extended a run containing only read-only diagnostic shell output"
|
|
6171
|
+
);
|
|
6068
6172
|
|
|
6069
6173
|
const mockAutoBudget = createStepBudgetState({ provider: "mock", maxSteps: 4, dynamicSteps: "auto" }, { meta: {}, stepsCompleted: 0 });
|
|
6070
6174
|
assert(!mockAutoBudget.enabled, "mock provider should not auto-extend unless explicitly enabled");
|
|
@@ -1515,6 +1515,43 @@ sameNames(
|
|
|
1515
1515
|
["deep_research", "finish"],
|
|
1516
1516
|
"local evidence research did not enter the bounded research engine after inspection"
|
|
1517
1517
|
);
|
|
1518
|
+
const retainedEvidenceManifestRepair = selectProgressiveTools(allTools, {
|
|
1519
|
+
config: { provider: "deepseek" },
|
|
1520
|
+
goal:
|
|
1521
|
+
"Investigate the reliability problem in this folder, write an evidence review and sources.json, then commit the intentional work.",
|
|
1522
|
+
profile: "research",
|
|
1523
|
+
messages: [
|
|
1524
|
+
{
|
|
1525
|
+
role: "user",
|
|
1526
|
+
content: [
|
|
1527
|
+
"Resume the saved evidence-review task.",
|
|
1528
|
+
"Do not restart the task, run deep_research, or reopen broad discovery.",
|
|
1529
|
+
"Use the retained completed evidence and rebuild sources.json.",
|
|
1530
|
+
].join("\n"),
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
role: "assistant",
|
|
1534
|
+
tool_calls: [{ id: "retained-sources", function: { name: "read_file", arguments: '{"path":"sources.json"}' } }],
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
role: "assistant",
|
|
1538
|
+
tool_calls: [{ id: "retained-evidence", function: { name: "read_file", arguments: '{"path":"tmp/reliability-evidence-pass.md"}' } }],
|
|
1539
|
+
},
|
|
1540
|
+
],
|
|
1541
|
+
});
|
|
1542
|
+
assert(
|
|
1543
|
+
names(retainedEvidenceManifestRepair).includes("write_file"),
|
|
1544
|
+
"retained-evidence manifest repair omitted write_file after inspection"
|
|
1545
|
+
);
|
|
1546
|
+
assert(
|
|
1547
|
+
names(retainedEvidenceManifestRepair).includes("read_file"),
|
|
1548
|
+
"retained-evidence manifest repair omitted bounded source reads"
|
|
1549
|
+
);
|
|
1550
|
+
assert(
|
|
1551
|
+
!(names(retainedEvidenceManifestRepair).length === 2 &&
|
|
1552
|
+
names(retainedEvidenceManifestRepair)[0] === "deep_research"),
|
|
1553
|
+
"explicit retained-evidence reuse was forced back into deep_research"
|
|
1554
|
+
);
|
|
1518
1555
|
const localEvidenceAfterDeepResearchCompaction = selectProgressiveTools(allTools, {
|
|
1519
1556
|
config: { provider: "deepseek" },
|
|
1520
1557
|
goal:
|
|
@@ -1610,6 +1647,22 @@ assert(
|
|
|
1610
1647
|
"surrounding policy prose incorrectly forced a scoped artifact task into deep research"
|
|
1611
1648
|
);
|
|
1612
1649
|
|
|
1650
|
+
const scopedExistingReportEditPrompt = `Generic research worker policy.
|
|
1651
|
+
AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"task","request":"Revise the exact existing research report at output/wechat_worker/task/report.md. Preserve the evidence, write the revised Markdown under this task's artifact directory, and use page-safe tables. The host owns PDF compilation."}
|
|
1652
|
+
Surrounding routine text mentions literature review, evidence review, research report, and web sources.`;
|
|
1653
|
+
const scopedExistingReportEditTools = selectProgressiveTools(allTools, {
|
|
1654
|
+
config: { provider: "deepseek", progressiveTools: true },
|
|
1655
|
+
goal: scopedExistingReportEditPrompt,
|
|
1656
|
+
profile: "auto",
|
|
1657
|
+
messages: [{ role: "user", content: scopedExistingReportEditPrompt }],
|
|
1658
|
+
});
|
|
1659
|
+
assert(names(scopedExistingReportEditTools).includes("read_file"), "scoped existing-report edit omitted read_file");
|
|
1660
|
+
assert(names(scopedExistingReportEditTools).includes("write_file"), "scoped existing-report edit omitted write_file");
|
|
1661
|
+
assert(
|
|
1662
|
+
!(names(scopedExistingReportEditTools).length === 2 && names(scopedExistingReportEditTools)[0] === "deep_research"),
|
|
1663
|
+
"scoped existing-report edit was incorrectly forced into deep_research"
|
|
1664
|
+
);
|
|
1665
|
+
|
|
1613
1666
|
const scopedDeepResearchPrompt = `Generic workspace policy.
|
|
1614
1667
|
AGINTI_EVIDENCE_SCOPE_JSON: {"mode":"task","request":"Write a deep research evidence review comparing three primary papers."}`;
|
|
1615
1668
|
const scopedDeepResearchTools = selectProgressiveTools(allTools, {
|
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { machineRunPayload } from "../src/cli.js";
|
|
7
|
+
import { machineRunPayload, runResultExitCode } from "../src/cli.js";
|
|
8
8
|
import { showProjectSession } from "../src/project.js";
|
|
9
9
|
|
|
10
10
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -36,6 +36,12 @@ const stoppedPayload = machineRunPayload({
|
|
|
36
36
|
if (stoppedPayload.ok !== false || stoppedPayload.failed !== true || stoppedPayload.stopped !== true) {
|
|
37
37
|
throw new Error(`stopped machine run was reported as success: ${JSON.stringify(stoppedPayload)}`);
|
|
38
38
|
}
|
|
39
|
+
if (runResultExitCode({ stopped: true, reason: "tool_contract_violation" }) !== 1) {
|
|
40
|
+
throw new Error("a stopped non-JSON agent run still reports shell success");
|
|
41
|
+
}
|
|
42
|
+
if (runResultExitCode({ stopped: false, result: "done" }) !== 0) {
|
|
43
|
+
throw new Error("a successful agent run was assigned a failing shell exit status");
|
|
44
|
+
}
|
|
39
45
|
|
|
40
46
|
async function runMachine(label, commandArgs, stdin = "") {
|
|
41
47
|
const command = [cliPath, ...commandArgs];
|
|
@@ -36,6 +36,26 @@ function fakeStudentClient(json) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const pageSafeReportContract = deriveScsTaskContract({
|
|
40
|
+
goal: [
|
|
41
|
+
"AGINTI_EVIDENCE_SCOPE_JSON: {\"mode\":\"task\",\"request\":\"Revise the exact existing research report at output/wechat_worker/task/report.md, write a complete Markdown report, and use page-safe tables. The host owns PDF compilation.\"}",
|
|
42
|
+
"Surrounding browser and research policy text is not part of the exact request.",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
taskProfile: "auto",
|
|
45
|
+
});
|
|
46
|
+
assert(
|
|
47
|
+
pageSafeReportContract.requiredEvidence.some((item) => item.category === "file"),
|
|
48
|
+
"a scoped existing-report edit did not require file evidence"
|
|
49
|
+
);
|
|
50
|
+
assert(
|
|
51
|
+
pageSafeReportContract.requiredEvidence.some((item) => item.category === "artifact"),
|
|
52
|
+
"a scoped existing-report edit did not require artifact evidence"
|
|
53
|
+
);
|
|
54
|
+
assert(
|
|
55
|
+
!pageSafeReportContract.requiredEvidence.some((item) => item.category === "browser"),
|
|
56
|
+
"the editorial phrase page-safe incorrectly required browser evidence"
|
|
57
|
+
);
|
|
58
|
+
|
|
39
59
|
const noEvidenceProgress = {
|
|
40
60
|
role: "student",
|
|
41
61
|
decision: "reject_phase",
|
|
@@ -264,6 +284,27 @@ assert(
|
|
|
264
284
|
"excluding output filenames also removed a real required text term"
|
|
265
285
|
);
|
|
266
286
|
|
|
287
|
+
const wrappedManifestRepairContract = deriveScsTaskContract({
|
|
288
|
+
goal: [
|
|
289
|
+
"The only unresolved content work",
|
|
290
|
+
"is rebuilding the stale sources.json for the current report.",
|
|
291
|
+
"The verified claims are retained in",
|
|
292
|
+
"tmp/reliability-evidence-pass.md. Then write",
|
|
293
|
+
"sources.json immediately. Do not read any other file.",
|
|
294
|
+
].join("\n"),
|
|
295
|
+
taskProfile: "research",
|
|
296
|
+
});
|
|
297
|
+
assert.deepEqual(
|
|
298
|
+
wrappedManifestRepairContract.exactOutputPaths,
|
|
299
|
+
["sources.json"],
|
|
300
|
+
"an inflected, soft-wrapped output instruction did not classify its manifest as an exact output"
|
|
301
|
+
);
|
|
302
|
+
assert.deepEqual(
|
|
303
|
+
wrappedManifestRepairContract.exactInputPaths,
|
|
304
|
+
["tmp/reliability-evidence-pass.md"],
|
|
305
|
+
"a mutable exact output leaked into exact inputs through a later negated read clause"
|
|
306
|
+
);
|
|
307
|
+
|
|
267
308
|
const wordDocumentContract = deriveScsTaskContract({
|
|
268
309
|
goal: "Create an editable DOCX and a phone-friendly PDF, then verify both outputs.",
|
|
269
310
|
taskProfile: "word",
|
package/src/agent-runner.js
CHANGED
|
@@ -1018,7 +1018,7 @@ function compactRetainedToolPayload(toolName, payload = {}, args = {}) {
|
|
|
1018
1018
|
return redactValue(result);
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
|
-
function
|
|
1021
|
+
function retainedPathMatchesAny(sourcePath = "", candidatePaths = []) {
|
|
1022
1022
|
const normalize = (value = "") =>
|
|
1023
1023
|
String(value || "")
|
|
1024
1024
|
.replace(/\\/g, "/")
|
|
@@ -1027,13 +1027,13 @@ function retainedPathMatchesOutput(sourcePath = "", outputPaths = []) {
|
|
|
1027
1027
|
.replace(/\/$/, "");
|
|
1028
1028
|
const source = normalize(sourcePath);
|
|
1029
1029
|
if (!source) return false;
|
|
1030
|
-
return
|
|
1030
|
+
return candidatePaths.some((candidate) => {
|
|
1031
1031
|
const output = normalize(candidate);
|
|
1032
1032
|
return output && (source === output || source.endsWith(`/${output}`) || output.endsWith(`/${source}`));
|
|
1033
1033
|
});
|
|
1034
1034
|
}
|
|
1035
1035
|
|
|
1036
|
-
function retainedToolRecordPriority(record = {}, outputPaths = []) {
|
|
1036
|
+
function retainedToolRecordPriority(record = {}, outputPaths = [], inputPaths = []) {
|
|
1037
1037
|
const name = String(record.name || "");
|
|
1038
1038
|
const args = record.args || {};
|
|
1039
1039
|
const payload = record.payload || {};
|
|
@@ -1048,12 +1048,13 @@ function retainedToolRecordPriority(record = {}, outputPaths = []) {
|
|
|
1048
1048
|
const range = retainedReadRange(payload, args);
|
|
1049
1049
|
priority = range.lineLimit > 0 ? 750 : 600;
|
|
1050
1050
|
const sourcePath = String(payload.path || args.path || "").trim();
|
|
1051
|
-
if (
|
|
1051
|
+
if (retainedPathMatchesAny(sourcePath, inputPaths)) priority += 260;
|
|
1052
|
+
if (retainedPathMatchesAny(sourcePath, outputPaths)) priority -= 220;
|
|
1052
1053
|
} else if (["search_files", "list_files"].includes(name)) priority = 400;
|
|
1053
1054
|
return priority + Math.min(0.999, Math.max(0, Number(record.ordinal) || 0) / 100000);
|
|
1054
1055
|
}
|
|
1055
1056
|
|
|
1056
|
-
function retainedToolStateMessages(messages = [], limit = 12, outputPaths = []) {
|
|
1057
|
+
function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [], inputPaths = []) {
|
|
1057
1058
|
const callsById = new Map();
|
|
1058
1059
|
const recordsByKey = new Map();
|
|
1059
1060
|
let ordinal = 0;
|
|
@@ -1103,7 +1104,8 @@ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [])
|
|
|
1103
1104
|
const selected = [...records]
|
|
1104
1105
|
.sort(
|
|
1105
1106
|
(left, right) =>
|
|
1106
|
-
retainedToolRecordPriority(right, outputPaths
|
|
1107
|
+
retainedToolRecordPriority(right, outputPaths, inputPaths) -
|
|
1108
|
+
retainedToolRecordPriority(left, outputPaths, inputPaths) ||
|
|
1107
1109
|
right.ordinal - left.ordinal
|
|
1108
1110
|
)
|
|
1109
1111
|
.slice(0, Math.max(1, Number(limit) || 12))
|
|
@@ -1135,8 +1137,8 @@ function retainedToolStateMessages(messages = [], limit = 12, outputPaths = [])
|
|
|
1135
1137
|
});
|
|
1136
1138
|
}
|
|
1137
1139
|
|
|
1138
|
-
function retainedToolStateTextMessages(messages = [], limit = 12, outputPaths = []) {
|
|
1139
|
-
const nativeMessages = retainedToolStateMessages(messages, limit, outputPaths);
|
|
1140
|
+
function retainedToolStateTextMessages(messages = [], limit = 12, outputPaths = [], inputPaths = []) {
|
|
1141
|
+
const nativeMessages = retainedToolStateMessages(messages, limit, outputPaths, inputPaths);
|
|
1140
1142
|
const retained = [];
|
|
1141
1143
|
for (let index = 0; index < nativeMessages.length; index += 2) {
|
|
1142
1144
|
const assistantMessage = nativeMessages[index];
|
|
@@ -1156,13 +1158,17 @@ function retainedToolStateTextMessages(messages = [], limit = 12, outputPaths =
|
|
|
1156
1158
|
return retained;
|
|
1157
1159
|
}
|
|
1158
1160
|
|
|
1159
|
-
function retainedToolPairPriority(pair = [], order = 0, outputPaths = []) {
|
|
1161
|
+
function retainedToolPairPriority(pair = [], order = 0, outputPaths = [], inputPaths = []) {
|
|
1160
1162
|
const assistantCall = pair[0]?.tool_calls?.[0];
|
|
1161
1163
|
const retained = pair.length === 1 ? parseRetainedToolEvidenceMessage(pair[0]) : null;
|
|
1162
1164
|
const name = String(retained?.name || assistantCall?.function?.name || "");
|
|
1163
1165
|
const args = retained?.args || safeParseToolContent(assistantCall?.function?.arguments) || {};
|
|
1164
1166
|
const payload = retained?.payload || safeParseToolContent(pair[1]?.content) || {};
|
|
1165
|
-
return retainedToolRecordPriority(
|
|
1167
|
+
return retainedToolRecordPriority(
|
|
1168
|
+
{ name, args, payload, ordinal: order },
|
|
1169
|
+
outputPaths,
|
|
1170
|
+
inputPaths
|
|
1171
|
+
);
|
|
1166
1172
|
}
|
|
1167
1173
|
|
|
1168
1174
|
function isRuntimeCompactionRequest(content = "") {
|
|
@@ -1345,9 +1351,10 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
|
|
|
1345
1351
|
// context instead of fabricating assistant reasoning.
|
|
1346
1352
|
const deepSeekCompaction = normalizeProviderId(config.provider, "") === "deepseek";
|
|
1347
1353
|
const exactOutputPaths = exactOutputPathsForState(state);
|
|
1354
|
+
const exactInputPaths = exactInputPathsForState(state);
|
|
1348
1355
|
const retainedToolMessages = deepSeekCompaction
|
|
1349
|
-
? retainedToolStateTextMessages(messages, 12, exactOutputPaths)
|
|
1350
|
-
: retainedToolStateMessages(messages, 12, exactOutputPaths);
|
|
1356
|
+
? retainedToolStateTextMessages(messages, 12, exactOutputPaths, exactInputPaths)
|
|
1357
|
+
: retainedToolStateMessages(messages, 12, exactOutputPaths, exactInputPaths);
|
|
1351
1358
|
const snapshotSummary = {
|
|
1352
1359
|
step,
|
|
1353
1360
|
maxSteps: config.maxSteps,
|
|
@@ -1425,7 +1432,7 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
|
|
|
1425
1432
|
}));
|
|
1426
1433
|
const boundedContent = compactTextForTokenBudget(
|
|
1427
1434
|
compactedContent,
|
|
1428
|
-
Math.max(1024, Math.floor(targetTokens * (retainedToolMessages.length ? 0.
|
|
1435
|
+
Math.max(1024, Math.floor(targetTokens * (retainedToolMessages.length ? 0.28 : 0.52))),
|
|
1429
1436
|
{ headFraction: 0.58 }
|
|
1430
1437
|
);
|
|
1431
1438
|
const baseMessages = [
|
|
@@ -1441,7 +1448,12 @@ function buildCompactedRuntimeMessages(state, config, snapshot, step, options =
|
|
|
1441
1448
|
retainedPairs.push({
|
|
1442
1449
|
pair,
|
|
1443
1450
|
order: retainedPairs.length,
|
|
1444
|
-
priority: retainedToolPairPriority(
|
|
1451
|
+
priority: retainedToolPairPriority(
|
|
1452
|
+
pair,
|
|
1453
|
+
retainedPairs.length,
|
|
1454
|
+
exactOutputPaths,
|
|
1455
|
+
exactInputPaths
|
|
1456
|
+
),
|
|
1445
1457
|
});
|
|
1446
1458
|
}
|
|
1447
1459
|
const selectedPairs = [];
|
|
@@ -4356,7 +4368,12 @@ export function recordProjectVerificationOutcome(state = {}, toolResult = {}, co
|
|
|
4356
4368
|
// authorize a new invocation of the same command. A recognized trailing
|
|
4357
4369
|
// status probe reports evidence but does not change the inner command's
|
|
4358
4370
|
// mutation capability.
|
|
4359
|
-
const commandPolicy =
|
|
4371
|
+
const commandPolicy = {
|
|
4372
|
+
...classifyCommand(mutationCommand),
|
|
4373
|
+
...(toolResult.commandPolicy && typeof toolResult.commandPolicy === "object"
|
|
4374
|
+
? toolResult.commandPolicy
|
|
4375
|
+
: {}),
|
|
4376
|
+
};
|
|
4360
4377
|
const requiredCommands = effectiveRequiredProjectCommands(state, verification, config);
|
|
4361
4378
|
const requiredCommand = requiredCommands.find(
|
|
4362
4379
|
(candidate) => projectCommandsEquivalent(candidate, exitProbe.command || command, config)
|
|
@@ -5335,16 +5352,31 @@ function expectedRepeatedObservationCommand(command = "") {
|
|
|
5335
5352
|
);
|
|
5336
5353
|
}
|
|
5337
5354
|
|
|
5355
|
+
function runCommandResultHasDurableProgress(toolResult = {}) {
|
|
5356
|
+
const policy = toolResult.commandPolicy || {};
|
|
5357
|
+
const policyAllowsMutation =
|
|
5358
|
+
policy.mayMutateProject === true ||
|
|
5359
|
+
(policy.mayMutateProject === undefined && policy.writesWorkspace === true);
|
|
5360
|
+
return Boolean(
|
|
5361
|
+
policyAllowsMutation ||
|
|
5362
|
+
policy.substantiveTest === true ||
|
|
5363
|
+
(Array.isArray(toolResult.verifiedGeneratedOutputPaths) &&
|
|
5364
|
+
toolResult.verifiedGeneratedOutputPaths.length > 0)
|
|
5365
|
+
);
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5338
5368
|
function isStaticDiscoveryToolResult(toolResult = {}) {
|
|
5339
5369
|
if (isStaticDiscoveryToolCall(toolResult.toolName, toolResult.args || {})) return true;
|
|
5340
5370
|
if (toolResult.toolName !== "run_command") return false;
|
|
5341
|
-
if (toolResult
|
|
5371
|
+
if (runCommandResultHasDurableProgress(toolResult)) return false;
|
|
5342
5372
|
return !expectedRepeatedObservationCommand(toolResult.args?.command);
|
|
5343
5373
|
}
|
|
5344
5374
|
|
|
5345
5375
|
function successfulToolStateProgress(toolResult = {}) {
|
|
5346
5376
|
if (!toolResult || toolResult.done || toolResult.ok === false || toolResult.blocked || toolResult.skipped) return false;
|
|
5347
|
-
if (toolResult.toolName === "run_command")
|
|
5377
|
+
if (toolResult.toolName === "run_command") {
|
|
5378
|
+
return runCommandResultHasDurableProgress(toolResult);
|
|
5379
|
+
}
|
|
5348
5380
|
if (["write_file", "apply_patch"].includes(String(toolResult.toolName || ""))) {
|
|
5349
5381
|
return successfulProjectMutationPaths(toolResult).length > 0;
|
|
5350
5382
|
}
|
|
@@ -5365,7 +5397,7 @@ function noProgressOutcomeFingerprint(toolResult = {}) {
|
|
|
5365
5397
|
toolResult?.toolName !== "run_command" ||
|
|
5366
5398
|
toolResult?.ok === false ||
|
|
5367
5399
|
toolResult?.blocked ||
|
|
5368
|
-
toolResult
|
|
5400
|
+
successfulToolStateProgress(toolResult) ||
|
|
5369
5401
|
expectedRepeatedObservationCommand(toolResult?.args?.command)
|
|
5370
5402
|
) {
|
|
5371
5403
|
return "";
|
|
@@ -6179,6 +6211,16 @@ function commandWritesOnlyPrivateVerificationEvidence(command = "") {
|
|
|
6179
6211
|
}
|
|
6180
6212
|
|
|
6181
6213
|
function commandCanMutateProjectContent(command = "", commandPolicy = {}) {
|
|
6214
|
+
// The classifier may conservatively mark an interpreter or compound shell
|
|
6215
|
+
// command as workspace-writing while still proving that this exact command
|
|
6216
|
+
// cannot mutate project content. Preserve that stronger semantic result so
|
|
6217
|
+
// validators and inspection probes do not fabricate mutation progress. Git
|
|
6218
|
+
// sequences remain structurally inspected because an aggregate Git policy
|
|
6219
|
+
// can be conservative even when one segment changes the worktree.
|
|
6220
|
+
const category = String(commandPolicy.category || "");
|
|
6221
|
+
const requiresGitMutationInspection =
|
|
6222
|
+
["git-workflow", "git-remote"].includes(category);
|
|
6223
|
+
if (commandPolicy.mayMutateProject === false && !requiresGitMutationInspection) return false;
|
|
6182
6224
|
if (commandPolicy.writesWorkspace !== true && commandPolicy.mayMutateProject !== true) return false;
|
|
6183
6225
|
const sequence = parseTopLevelShellSequence(String(command || ""));
|
|
6184
6226
|
if (
|
|
@@ -6192,7 +6234,6 @@ function commandCanMutateProjectContent(command = "", commandPolicy = {}) {
|
|
|
6192
6234
|
);
|
|
6193
6235
|
}
|
|
6194
6236
|
if (commandWritesOnlyPrivateVerificationEvidence(command)) return false;
|
|
6195
|
-
const category = String(commandPolicy.category || "");
|
|
6196
6237
|
if (!["git-workflow", "git-remote"].includes(category)) return true;
|
|
6197
6238
|
if (/\bgit\s+clone\b/i.test(String(command || ""))) return true;
|
|
6198
6239
|
// An aggregate Git category can still contain a non-Git build/generator
|
|
@@ -6700,6 +6741,18 @@ function exactOutputPathsForState(state = {}) {
|
|
|
6700
6741
|
])].slice(0, 32);
|
|
6701
6742
|
}
|
|
6702
6743
|
|
|
6744
|
+
function exactInputPathsForState(state = {}) {
|
|
6745
|
+
const scsInputPaths = Array.isArray(state.meta?.scs?.taskContract?.exactInputPaths)
|
|
6746
|
+
? state.meta.scs.taskContract.exactInputPaths.filter(Boolean)
|
|
6747
|
+
: [];
|
|
6748
|
+
const outputPaths = new Set(
|
|
6749
|
+
exactOutputPathsForState(state).map((item) => String(item).replace(/\\/g, "/").replace(/^\.\//, ""))
|
|
6750
|
+
);
|
|
6751
|
+
return [...new Set(scsInputPaths)]
|
|
6752
|
+
.filter((item) => !outputPaths.has(String(item).replace(/\\/g, "/").replace(/^\.\//, "")))
|
|
6753
|
+
.slice(0, 32);
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6703
6756
|
async function hashExactOutputFile(absolutePath) {
|
|
6704
6757
|
return await new Promise((resolve, reject) => {
|
|
6705
6758
|
const digest = crypto.createHash("sha256");
|
|
@@ -7314,7 +7367,11 @@ export function recordStaticDiscoveryProgress(toolLoop = {}, signature = "") {
|
|
|
7314
7367
|
};
|
|
7315
7368
|
}
|
|
7316
7369
|
|
|
7317
|
-
export function resetStaticDiscoveryAfterContextLoss(
|
|
7370
|
+
export function resetStaticDiscoveryAfterContextLoss(
|
|
7371
|
+
state = {},
|
|
7372
|
+
reason = "context-compaction",
|
|
7373
|
+
options = {}
|
|
7374
|
+
) {
|
|
7318
7375
|
state.meta = state.meta || {};
|
|
7319
7376
|
const toolLoop = state.meta.toolLoop && typeof state.meta.toolLoop === "object"
|
|
7320
7377
|
? state.meta.toolLoop
|
|
@@ -7323,6 +7380,16 @@ export function resetStaticDiscoveryAfterContextLoss(state = {}, reason = "conte
|
|
|
7323
7380
|
const priorCounts = toolLoop.staticCounts && typeof toolLoop.staticCounts === "object"
|
|
7324
7381
|
? toolLoop.staticCounts
|
|
7325
7382
|
: {};
|
|
7383
|
+
if (options.preserveStaticEvidence === true) {
|
|
7384
|
+
toolLoop.lastContextRecovery = {
|
|
7385
|
+
reason: String(reason || "context-compaction"),
|
|
7386
|
+
at: new Date().toISOString(),
|
|
7387
|
+
priorStaticTotal: Number(priorOrder.length),
|
|
7388
|
+
preservedStaticEvidence: true,
|
|
7389
|
+
};
|
|
7390
|
+
state.meta.toolLoop = toolLoop;
|
|
7391
|
+
return toolLoop.lastContextRecovery;
|
|
7392
|
+
}
|
|
7326
7393
|
if (priorOrder.length || Object.keys(priorCounts).length) {
|
|
7327
7394
|
const history = Array.isArray(toolLoop.staticHistory) ? toolLoop.staticHistory : [];
|
|
7328
7395
|
history.push({
|
|
@@ -11043,7 +11110,9 @@ export async function runAgent(config) {
|
|
|
11043
11110
|
const tokensAfter = estimateMessageTokens(compactMessages);
|
|
11044
11111
|
if (charsAfter < contextDecision.charsBefore) {
|
|
11045
11112
|
state.messages = compactMessages;
|
|
11046
|
-
resetStaticDiscoveryAfterContextLoss(state, "proactive-context-compaction"
|
|
11113
|
+
resetStaticDiscoveryAfterContextLoss(state, "proactive-context-compaction", {
|
|
11114
|
+
preserveStaticEvidence: true,
|
|
11115
|
+
});
|
|
11047
11116
|
state.meta.contextBudget = recordContextCompaction(contextBudget, {
|
|
11048
11117
|
step,
|
|
11049
11118
|
charsBefore: contextDecision.charsBefore,
|
|
@@ -11159,7 +11228,9 @@ export async function runAgent(config) {
|
|
|
11159
11228
|
};
|
|
11160
11229
|
state.messages = compactMessages;
|
|
11161
11230
|
requestMessages = compactMessages;
|
|
11162
|
-
resetStaticDiscoveryAfterContextLoss(state, "local-context-budget-retry"
|
|
11231
|
+
resetStaticDiscoveryAfterContextLoss(state, "local-context-budget-retry", {
|
|
11232
|
+
preserveStaticEvidence: true,
|
|
11233
|
+
});
|
|
11163
11234
|
state.meta.localContextBudgetRetries = {
|
|
11164
11235
|
...contextRetriedSteps,
|
|
11165
11236
|
[retryKey]: true,
|
|
@@ -11207,7 +11278,9 @@ export async function runAgent(config) {
|
|
|
11207
11278
|
};
|
|
11208
11279
|
state.messages = compactMessages;
|
|
11209
11280
|
requestMessages = compactMessages;
|
|
11210
|
-
resetStaticDiscoveryAfterContextLoss(state, "model-timeout-retry"
|
|
11281
|
+
resetStaticDiscoveryAfterContextLoss(state, "model-timeout-retry", {
|
|
11282
|
+
preserveStaticEvidence: true,
|
|
11283
|
+
});
|
|
11211
11284
|
state.meta.modelTimeoutRetries = {
|
|
11212
11285
|
...retriedSteps,
|
|
11213
11286
|
[retryKey]: true,
|
package/src/cli.js
CHANGED
|
@@ -383,6 +383,10 @@ export function machineRunPayload(run, fallbackSessionId = "", metadata = {}) {
|
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
export function runResultExitCode(run = {}) {
|
|
387
|
+
return run?.stopped === true || run?.failed === true ? 1 : 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
386
390
|
function printMachineRunResult(run, fallbackSessionId = "", metadata = {}) {
|
|
387
391
|
const payload = machineRunPayload(run, fallbackSessionId, metadata);
|
|
388
392
|
console.log(JSON.stringify(payload));
|
|
@@ -2507,6 +2511,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
2507
2511
|
...(jsonFlag ? { onConsole: () => {} } : {}),
|
|
2508
2512
|
});
|
|
2509
2513
|
const run = await runAgent(config);
|
|
2514
|
+
if (runResultExitCode(run) !== 0) process.exitCode = 1;
|
|
2510
2515
|
if (jsonFlag) {
|
|
2511
2516
|
printMachineRunResult(run, runArgs.sessionId, {
|
|
2512
2517
|
provider: config.provider,
|
|
@@ -2604,6 +2609,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
2604
2609
|
config.expectedRuntimeRevision = preparedRuntime.expectedRuntimeRevision;
|
|
2605
2610
|
if (preparedRuntime.runtimePatch) config.runtimePatch = preparedRuntime.runtimePatch;
|
|
2606
2611
|
const run = await runAgent(config);
|
|
2612
|
+
if (runResultExitCode(run) !== 0) process.exitCode = 1;
|
|
2607
2613
|
if (jsonFlag) {
|
|
2608
2614
|
printMachineRunResult(run, sessionId, {
|
|
2609
2615
|
provider: config.provider,
|
|
@@ -2765,5 +2771,6 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
2765
2771
|
config.expectedRuntimeRevision = preparedRuntime.expectedRuntimeRevision;
|
|
2766
2772
|
if (preparedRuntime.runtimePatch) config.runtimePatch = preparedRuntime.runtimePatch;
|
|
2767
2773
|
}
|
|
2768
|
-
await runAgent(config);
|
|
2774
|
+
const run = await runAgent(config);
|
|
2775
|
+
if (runResultExitCode(run) !== 0) process.exitCode = 1;
|
|
2769
2776
|
}
|
package/src/research-routing.js
CHANGED
|
@@ -89,18 +89,38 @@ export function hasLocalResearchWorkspaceIntent(goal = "", messages = []) {
|
|
|
89
89
|
const text = `${scopedChatopsEvidenceGoal(goal)}\n${recent}`;
|
|
90
90
|
return (
|
|
91
91
|
/\b(?:this|current|existing|project|workspace|local)\s+(?:folder|directory|repo(?:sitory)?|files?|notes?|sources?|artifacts?)\b/i.test(text) ||
|
|
92
|
+
/(?:^|[\s`'"(])(?:~\/|\.{1,2}\/|\/)?(?:[^\s`'"()\/]+\/)+[^\s`'"()\/]+\.(?:bib|csv|docx?|json|md|pdf|tex|txt|ya?ml)\b/i.test(text) ||
|
|
93
|
+
/\b(?:edit|revise|rewrite|proofread|correct|polish|read|inspect|update)\b[^.\n;]{0,160}\b(?:exact|existing|current|saved|local)\b[^.\n;]{0,80}\b(?:document|file|manuscript|markdown|notes?|report|source)\b/i.test(text) ||
|
|
92
94
|
/\b(?:task|project|source|research|evidence|notes?|manifest|readme)[-_A-Za-z0-9]*\.(?:md|json|ya?ml|txt|csv|bib|tex)\b/i.test(text) ||
|
|
93
95
|
/\b(?:inspect|read|reconcile|correct|rewrite|update)\b.{0,120}\b(?:workspace|folder|directory|repo(?:sitory)?|local files?|project notes?|existing notes?)\b/i.test(text) ||
|
|
94
96
|
/\b(?:git\s+)?commit\b/i.test(text)
|
|
95
97
|
);
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
export function hasExplicitDeepResearchSuppression(goal = "", messages = []) {
|
|
101
|
+
const current = currentIntentMessages(messages);
|
|
102
|
+
const latestUserIntent = [...current]
|
|
103
|
+
.reverse()
|
|
104
|
+
.find((message) => message?.role === "user" && !isRuntimeUserMessage(messageText(message.content)));
|
|
105
|
+
const text = `${scopedChatopsEvidenceGoal(goal)}\n${scopedChatopsEvidenceGoal(
|
|
106
|
+
messageText(latestUserIntent?.content)
|
|
107
|
+
)}`;
|
|
108
|
+
return (
|
|
109
|
+
/\b(?:do not|don't|must not|never)\s+(?:run|rerun|re-run|repeat|restart|invoke|call|start)\s+(?:the\s+)?(?:deep[_ -]?research|research workflow)\b/i.test(text) ||
|
|
110
|
+
/\b(?:do not|don't|must not|never)\b[^.!?\n]{0,140}\b(?:run|rerun|re-run|repeat|restart|invoke|call|start)\s+(?:the\s+)?(?:deep[_ -]?research|research workflow)\b/i.test(text) ||
|
|
111
|
+
/\b(?:reuse|use|continue from|recover from)\b.{0,140}\b(?:completed|existing|retained|saved)\b.{0,180}\b(?:deep[_ -]?research|research (?:result|artifact|evidence|pass))\b/i.test(text) ||
|
|
112
|
+
/(?:不要|无需|不必|禁止).{0,20}(?:重新|再次|重复)?(?:运行|调用|启动)?(?:深度研究|深入研究|deep[_ -]?research)/iu.test(text) ||
|
|
113
|
+
/(?:ディープリサーチ|深い調査).{0,20}(?:再実行しない|繰り返さない|呼び出さない)/u.test(text)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
export function toolWasRequested(messages = [], toolName = "") {
|
|
99
118
|
return requestedToolCount(messages, toolName) > 0;
|
|
100
119
|
}
|
|
101
120
|
|
|
102
121
|
export function shouldStartWithDeepResearch(goal = "", messages = []) {
|
|
103
122
|
if (!hasExplicitDeepResearchIntent(goal, messages)) return false;
|
|
123
|
+
if (hasExplicitDeepResearchSuppression(goal, messages)) return false;
|
|
104
124
|
const current = currentIntentMessages(messages);
|
|
105
125
|
if (toolWasRequested(current, "deep_research")) return false;
|
|
106
126
|
if (hasLocalResearchWorkspaceIntent(goal, messages) && !localWorkspaceInspectionReady(current)) return false;
|
package/src/scs-evidence.js
CHANGED
|
@@ -373,9 +373,9 @@ function inferExactOutputPaths(goal = "") {
|
|
|
373
373
|
"gi"
|
|
374
374
|
);
|
|
375
375
|
const directOutputAction =
|
|
376
|
-
/\b(
|
|
376
|
+
/\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
|
|
377
377
|
const directOutputActionGlobal =
|
|
378
|
-
/\b(
|
|
378
|
+
/\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/gi;
|
|
379
379
|
const outputListHeader =
|
|
380
380
|
/^(?:#+\s*)?(?:(?:required|final|expected|declared|target|pilot|deliverable)\s+)*(?:create|created files?|files? to create|outputs?|output structure|required outputs?|artifacts?|deliverables?|generated files?|writer requirements|renderer requirements|生成文件|输出结构|輸出結構|输出文件|輸出文件|创建文件|建立文件)(?:\s+(?:outputs?|artifacts?|deliverables?))?\s*[::]?\s*$/i;
|
|
381
381
|
const nonOutputToolLine =
|
|
@@ -410,8 +410,15 @@ function inferExactOutputPaths(goal = "") {
|
|
|
410
410
|
}
|
|
411
411
|
return matches[0]?.index ?? -1;
|
|
412
412
|
};
|
|
413
|
-
for (
|
|
414
|
-
const
|
|
413
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
414
|
+
const rawLine = lines[lineIndex];
|
|
415
|
+
const currentLine = String(rawLine || "").trim();
|
|
416
|
+
const previousLine = String(lines[lineIndex - 1] || "").trim();
|
|
417
|
+
const wrappedOutputInstruction =
|
|
418
|
+
previousLine &&
|
|
419
|
+
!/[.!?。!?;;]$/.test(previousLine) &&
|
|
420
|
+
directOutputAction.test(previousLine);
|
|
421
|
+
const line = wrappedOutputInstruction ? `${previousLine} ${currentLine}`.trim() : currentLine;
|
|
415
422
|
if (!line) {
|
|
416
423
|
if (inOutputList) inOutputList = false;
|
|
417
424
|
continue;
|
|
@@ -468,16 +475,23 @@ function inferExactInputPaths(goal = "") {
|
|
|
468
475
|
"gi"
|
|
469
476
|
);
|
|
470
477
|
const inputAction =
|
|
471
|
-
/\b(use|using|read|load|fill|upload|attach|import|select|choose|reference|input|from|fix|repair|patch|correct)\b
|
|
478
|
+
/\b(?:use|using|read|load|fill|upload|attach|import|select|choose|reference|input|from|retain(?:ed|ing)?|fix|repair|patch|correct)\b|使用|读取|讀取|加载|載入|填写|填入|上传|上傳|附加|导入|導入|选择|選擇|选取|選取|参考|參考|素材|图片|圖片|照片|提示词|提示詞|保留|修复|修正|更正|从|從/i;
|
|
472
479
|
const directOutputAction =
|
|
473
|
-
/\b(
|
|
480
|
+
/\b(?:sav(?:e|es|ing)|writ(?:e|es|ing)|rewrit(?:e|es|ing)|output(?:s|ting)?|creat(?:e|es|ing)|rebuild(?:s|ing)?|replac(?:e|es|ing)|regenerat(?:e|es|ing)|generat(?:e|es|ing)|stor(?:e|es|ing)|updat(?:e|es|ing)|modif(?:y|ies|ying)|edit(?:s|ing)?)\b|保存|写入|寫入|重写|重寫|输出|輸出|创建|建立|重建|替换|替換|重新生成|生成|更新|修改|编辑|編輯/i;
|
|
474
481
|
const pushPath = (raw = "") => {
|
|
475
482
|
const cleaned = String(raw || "").trim();
|
|
476
483
|
if (!cleaned || /[{}]/.test(cleaned)) return;
|
|
477
484
|
paths.push(cleaned);
|
|
478
485
|
};
|
|
479
|
-
for (
|
|
480
|
-
const
|
|
486
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
487
|
+
const rawLine = lines[lineIndex];
|
|
488
|
+
const currentLine = String(rawLine || "").trim();
|
|
489
|
+
const previousLine = String(lines[lineIndex - 1] || "").trim();
|
|
490
|
+
const wrappedInputInstruction =
|
|
491
|
+
previousLine &&
|
|
492
|
+
!/[.!?。!?;;]$/.test(previousLine) &&
|
|
493
|
+
inputAction.test(previousLine);
|
|
494
|
+
const fullLine = wrappedInputInstruction ? `${previousLine} ${currentLine}`.trim() : currentLine;
|
|
481
495
|
const outputIndex = fullLine.search(directOutputAction);
|
|
482
496
|
const line = outputIndex > 0 ? fullLine.slice(0, outputIndex).trim() : fullLine;
|
|
483
497
|
if (!line || !inputAction.test(line)) continue;
|
|
@@ -779,7 +793,17 @@ function inferRequirementCategories(goal = "", taskProfile = "", acceptanceCrite
|
|
|
779
793
|
if (textHas(mandatoryEvidenceText, /\b(artifact|canvas|pdf|image|video|screenshot|cover|plot|chart|figure|docx|archive|copy to|export|generated|generate|draft)\b/) || /输出|产物|图片|视频|截图|封面|生成/.test(mandatoryEvidenceText)) {
|
|
780
794
|
categories.add("artifact");
|
|
781
795
|
}
|
|
782
|
-
if (
|
|
796
|
+
if (
|
|
797
|
+
textHas(
|
|
798
|
+
mandatoryEvidenceText,
|
|
799
|
+
/\b(browser|chrome|chromium|cdp|devtools|playwright|selenium|web[- ]?(?:ui|page)|website|tab|composer|click|type|upload|attach|submit|form)\b/
|
|
800
|
+
) ||
|
|
801
|
+
textHas(
|
|
802
|
+
mandatoryEvidenceText,
|
|
803
|
+
/\b(?:browse|navigate|open|refresh|visit)\b[^.\n;]{0,60}\b(?:page|site)\b|\b(?:page|site)\b[^.\n;]{0,60}\b(?:click|open|submit|upload)\b/
|
|
804
|
+
) ||
|
|
805
|
+
/浏览器|网页|页面|上传|提交|附件|资产库/.test(mandatoryEvidenceText)
|
|
806
|
+
) {
|
|
783
807
|
categories.add("browser");
|
|
784
808
|
}
|
|
785
809
|
if (textHas(mandatoryEvidenceText, /\b(screenshot|visible|visual|see|inspect image|open image|read_image|thumbnail)\b/) || /截图|可见|缩略图/.test(mandatoryEvidenceText)) {
|
|
@@ -208,7 +208,25 @@ export function staticToolCallSignature(toolName, args = {}, context = {}) {
|
|
|
208
208
|
|
|
209
209
|
function isStaticDiscoveryResult(result = {}) {
|
|
210
210
|
if (!result || result.ok === false || result.blocked || result.done) return false;
|
|
211
|
-
|
|
211
|
+
if (isStaticDiscoveryToolCall(result.toolName, result.args || {})) return true;
|
|
212
|
+
if (result.toolName !== "run_command") return false;
|
|
213
|
+
if (/\b(?:watch|poll|status|queue|sleep)\b|tail\s+-f|tmux\s+capture-pane/i.test(String(result.args?.command || ""))) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
return !runCommandHasConcreteProgress(result);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function runCommandHasConcreteProgress(result = {}) {
|
|
220
|
+
const policy = result.commandPolicy || {};
|
|
221
|
+
const policyAllowsMutation =
|
|
222
|
+
policy.mayMutateProject === true ||
|
|
223
|
+
(policy.mayMutateProject === undefined && policy.writesWorkspace === true);
|
|
224
|
+
return Boolean(
|
|
225
|
+
policyAllowsMutation ||
|
|
226
|
+
policy.substantiveTest === true ||
|
|
227
|
+
(Array.isArray(result.verifiedGeneratedOutputPaths) &&
|
|
228
|
+
result.verifiedGeneratedOutputPaths.length > 0)
|
|
229
|
+
);
|
|
212
230
|
}
|
|
213
231
|
|
|
214
232
|
export function summarizeRepeatedStaticDiscovery(recentToolResults = [], context = {}) {
|
|
@@ -251,7 +269,7 @@ function hasConcreteProgress(recentToolResults = [], events = []) {
|
|
|
251
269
|
if (result.ok === false || result.blocked || result.done) return false;
|
|
252
270
|
if (isStaticDiscoveryToolCall(result.toolName, result.args || {})) return false;
|
|
253
271
|
if (!PROGRESS_TOOL_NAMES.has(result.toolName)) return false;
|
|
254
|
-
if (result.toolName === "run_command") return
|
|
272
|
+
if (result.toolName === "run_command") return runCommandHasConcreteProgress(result);
|
|
255
273
|
return Boolean(
|
|
256
274
|
result.path ||
|
|
257
275
|
result.artifactPath ||
|