@lazyingart/agintiflow 0.20.298 → 0.20.300
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/perception-and-web-research.md +2 -1
- package/docs/runtime-modes-and-autonomy.md +4 -0
- package/docs/supervision-campaign-ledger.md +23 -0
- package/package.json +1 -1
- package/scripts/smoke-docker-command.js +42 -0
- package/scripts/smoke-dynamic-step-budget.js +92 -0
- package/scripts/smoke-perception-research.js +4 -0
- package/scripts/smoke-progressive-tool-selection.js +24 -0
- package/scripts/smoke-truthful-completion.js +5 -0
- package/src/agent-runner.js +36 -3
- package/src/docker-sandbox.js +64 -0
- package/src/perception-tools.js +4 -1
- package/src/progressive-tool-selection.js +14 -1
|
@@ -6,7 +6,7 @@ AgInTiFlow separates visual understanding, web search, and wrapper advice so eac
|
|
|
6
6
|
|
|
7
7
|
| Tool | Purpose | Evidence |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| `read_image` | Read workspace screenshots, plots, scanned text, diagrams, or allowed remote image URLs. | Saves JSON
|
|
9
|
+
| `read_image` | Read workspace screenshots, plots, scanned text, diagrams, or allowed remote image URLs. | Saves canonical JSON/Markdown evidence in the session artifact store, keeps the workspace-relative canvas copy under ignored `.aginti/artifacts/perception/`, and records image hashes. |
|
|
10
10
|
| `web_search` | Cheap raw search snippets or a bounded multi-provider ensemble. | Returns compact titles, canonical URLs, snippets, provider attempts, and per-provider discovery evidence. |
|
|
11
11
|
| `read_web_page` | Read one exact public source instead of relying on a snippet. | Returns bounded article/PDF text, metadata, canonical URL, relevant passages, retrieval time, and content hash. |
|
|
12
12
|
| `web_research` | Sourced research unit for current or external information. | Saves `artifacts/research/*-web-research.json` with query, mode, source list, and answer. |
|
|
@@ -18,6 +18,7 @@ AgInTiFlow separates visual understanding, web search, and wrapper advice so eac
|
|
|
18
18
|
- In a LocalLLM session, `read_image provider=auto` sends bounded image data to the loopback LocalLLM Chat Completions endpoint with `AGINTI_LOCALLLM_VISION_MODEL` or `localllm-vision-xl`. It does not try OpenAI or a wrapper after a local failure.
|
|
19
19
|
- In an OpenAI session, `read_image provider=auto` uses OpenAI Responses vision. A non-OpenAI session must set `allowHostedImagePerception=true` before `provider=openai` is accepted. `OPENAI_API_KEY` by itself grants no permission.
|
|
20
20
|
- Codex image reading requires both `provider=codex` and explicitly enabled wrapper tools. It is not an automatic fallback.
|
|
21
|
+
- Visual verification never creates a task-owned `artifacts/perception/` tree. Its workspace-relative Markdown copy stays under ignored `.aginti/` storage so a successful review cannot dirty an otherwise clean repository.
|
|
21
22
|
- OpenAI image reading defaults to `AGINTI_PERCEPTION_MODEL=gpt-5.4-mini` and `AGINTI_PERCEPTION_REASONING=medium`, with fallback models remaining inside the same explicitly selected OpenAI provider.
|
|
22
23
|
- `web_research` defaults to lightweight snippet mode. The active LocalLLM can synthesize those returned snippets and sources during the next agent step without a second provider call.
|
|
23
24
|
- `web_search provider=auto` falls back from DuckDuckGo HTML to Bing RSS. `provider=multi` merges both indexes and adds Brave only when explicitly configured. Standard/deep research defaults to this ensemble; quick research stays on auto.
|
|
@@ -132,6 +132,10 @@ This starts interactive chat with Docker workspace mode, file tools, shell tools
|
|
|
132
132
|
|
|
133
133
|
Docker workspace mode also mounts common host data roots, such as the user's home parent, read-only at their original absolute paths. This lets a task in `/home/lachlan/ProjectsLFS/aginti-test` inspect a sibling dataset such as `/home/lachlan/ProjectsLFS/ProteinStructure` without switching to host-write mode. Outputs and edits still belong under `/workspace` unless the user explicitly approves stronger host permissions. Set `AGINTI_DOCKER_READONLY_HOST_MOUNTS=/path/a:/path/b` to override the read-only mounts, or set it to `off` to disable them.
|
|
134
134
|
|
|
135
|
+
For a linked Git worktree, bounded Git mutation commands such as the runtime's path-enumerated `git add` plus `git commit` also receive a writable mount for the validated shared Git common directory. The mount is not added to ordinary shell commands or read-only Git inspection. This lets Docker workspace mode commit the current worktree without granting broad host-write access.
|
|
136
|
+
|
|
137
|
+
Outstanding exact project commands always run before Git completion. A pending producer, test, or user-declared validation command cannot be preempted by a commit merely because the current artifacts already exist.
|
|
138
|
+
|
|
135
139
|
Direct trusted host mode:
|
|
136
140
|
|
|
137
141
|
```bash
|
|
@@ -56,6 +56,29 @@ in `evidence_json` and `events`.
|
|
|
56
56
|
|
|
57
57
|
## Current Campaign Findings
|
|
58
58
|
|
|
59
|
+
### Retained dependencies, linked-worktree commits, and clean visual evidence
|
|
60
|
+
|
|
61
|
+
`data-analysis-retained-dependencies-060` resumed the original DeepSeek session
|
|
62
|
+
after an imperfect data-analysis repair had narrowed its context too far. The
|
|
63
|
+
agent retained the immutable configuration and CSV inputs referenced by the
|
|
64
|
+
mutable Python source, regenerated every required output, ran the canonical
|
|
65
|
+
producer and all four unit tests, inspected the 900 x 600 plot, and committed
|
|
66
|
+
target revision `55cadcd` from Docker workspace mode. Independent verification
|
|
67
|
+
confirmed 11 raw rows, one duplicate removal, two invalid-signal removals,
|
|
68
|
+
eight clean rows, the expected condition means, valid CSV/JSON/Markdown/PNG
|
|
69
|
+
artifacts, and a clean target worktree.
|
|
70
|
+
|
|
71
|
+
The run established three reusable runtime contracts:
|
|
72
|
+
|
|
73
|
+
- Failed-test recovery keeps bounded, previously read immutable dependencies
|
|
74
|
+
when the mutable source names them, while mutation scope remains limited to
|
|
75
|
+
the canonical repair paths.
|
|
76
|
+
- An outstanding exact producer or validation command runs before artifact Git
|
|
77
|
+
completion. In a linked worktree, only bounded Git mutation commands receive
|
|
78
|
+
a writable mount for the validated shared Git common directory.
|
|
79
|
+
- `read_image` stores its task-relative review copy under ignored `.aginti/`
|
|
80
|
+
runtime storage, so visual verification cannot dirty a completed repository.
|
|
81
|
+
|
|
59
82
|
### Data analysis retained-state recovery
|
|
60
83
|
|
|
61
84
|
`data-analysis-local-011` passed after reusable runtime fixes. AgInTi completed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.300",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
9
|
import {
|
|
10
|
+
dockerWritableGitMounts,
|
|
10
11
|
dockerWorkspaceAliasMounts,
|
|
11
12
|
ensureDockerGitIdentity,
|
|
12
13
|
} from "../src/docker-sandbox.js";
|
|
@@ -52,6 +53,47 @@ try {
|
|
|
52
53
|
assert.equal(storedEmail.stdout.trim(), "aginti-test@example.invalid");
|
|
53
54
|
assert.equal((await fs.stat(identityConfig)).mode & 0o777, 0o600, "Docker author config should be private");
|
|
54
55
|
|
|
56
|
+
const worktreeRoot = path.join(tempRoot, "worktree-root");
|
|
57
|
+
const linkedWorktree = path.join(tempRoot, "linked-worktree");
|
|
58
|
+
await execFileAsync("git", ["init", worktreeRoot]);
|
|
59
|
+
await execFileAsync("git", ["-C", worktreeRoot, "config", "user.name", "AgInTi Test Author"]);
|
|
60
|
+
await execFileAsync("git", ["-C", worktreeRoot, "config", "user.email", "aginti-test@example.invalid"]);
|
|
61
|
+
await fs.writeFile(path.join(worktreeRoot, "seed.txt"), "seed\n", "utf8");
|
|
62
|
+
await execFileAsync("git", ["-C", worktreeRoot, "add", "seed.txt"]);
|
|
63
|
+
await execFileAsync("git", ["-C", worktreeRoot, "commit", "-m", "seed"]);
|
|
64
|
+
await execFileAsync("git", ["-C", worktreeRoot, "worktree", "add", "-b", "linked-test", linkedWorktree]);
|
|
65
|
+
const linkedCommonDir = (await execFileAsync(
|
|
66
|
+
"git",
|
|
67
|
+
["-C", linkedWorktree, "rev-parse", "--path-format=absolute", "--git-common-dir"]
|
|
68
|
+
)).stdout.trim();
|
|
69
|
+
assert.deepEqual(
|
|
70
|
+
dockerWritableGitMounts(
|
|
71
|
+
"git add -- changed.txt && git commit -m 'linked worktree change'",
|
|
72
|
+
{ commandCwd: linkedWorktree, sandboxMode: "docker-workspace" },
|
|
73
|
+
{ gitOnly: true }
|
|
74
|
+
),
|
|
75
|
+
[linkedCommonDir],
|
|
76
|
+
"Docker Git mutation did not mount validated linked-worktree metadata writable"
|
|
77
|
+
);
|
|
78
|
+
assert.deepEqual(
|
|
79
|
+
dockerWritableGitMounts(
|
|
80
|
+
"git status --short",
|
|
81
|
+
{ commandCwd: linkedWorktree, sandboxMode: "docker-workspace" },
|
|
82
|
+
{ gitOnly: true }
|
|
83
|
+
),
|
|
84
|
+
[],
|
|
85
|
+
"read-only Git inspection received an unnecessary writable metadata mount"
|
|
86
|
+
);
|
|
87
|
+
assert.deepEqual(
|
|
88
|
+
dockerWritableGitMounts(
|
|
89
|
+
"git add -- seed.txt",
|
|
90
|
+
{ commandCwd: worktreeRoot, sandboxMode: "docker-workspace" },
|
|
91
|
+
{ gitOnly: true }
|
|
92
|
+
),
|
|
93
|
+
[],
|
|
94
|
+
"ordinary repositories should rely on the existing writable workspace mount"
|
|
95
|
+
);
|
|
96
|
+
|
|
55
97
|
const { stdout } = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "docker", "status", "--json", "--cwd", tempRoot], {
|
|
56
98
|
cwd: tempRoot,
|
|
57
99
|
env: {
|
|
@@ -572,6 +572,98 @@ try {
|
|
|
572
572
|
duplicateFocus?.duplicateDeclarations?.[0]?.name === "status_service",
|
|
573
573
|
"duplicate production declarations were not promoted into a focused failed-test repair"
|
|
574
574
|
);
|
|
575
|
+
const dependencyRecoveryWorkspace = path.join(tempRoot, "dependency-recovery-workspace");
|
|
576
|
+
await fs.mkdir(path.join(dependencyRecoveryWorkspace, "config"), { recursive: true });
|
|
577
|
+
await fs.mkdir(path.join(dependencyRecoveryWorkspace, "raw"), { recursive: true });
|
|
578
|
+
await fs.mkdir(path.join(dependencyRecoveryWorkspace, "tests"), { recursive: true });
|
|
579
|
+
await fs.writeFile(
|
|
580
|
+
path.join(dependencyRecoveryWorkspace, "analysis.py"),
|
|
581
|
+
[
|
|
582
|
+
"from pathlib import Path",
|
|
583
|
+
"import json",
|
|
584
|
+
"",
|
|
585
|
+
"ROOT = Path(__file__).resolve().parent",
|
|
586
|
+
"config = json.loads((ROOT / 'config' / 'experiment.json').read_text())",
|
|
587
|
+
"for source_file in ('run_a.csv', 'run_b.csv'):",
|
|
588
|
+
" print((ROOT / 'raw' / source_file).read_text())",
|
|
589
|
+
"",
|
|
590
|
+
].join("\n"),
|
|
591
|
+
"utf8"
|
|
592
|
+
);
|
|
593
|
+
await fs.writeFile(
|
|
594
|
+
path.join(dependencyRecoveryWorkspace, "tests", "test_analysis.py"),
|
|
595
|
+
"from analysis import config\n",
|
|
596
|
+
"utf8"
|
|
597
|
+
);
|
|
598
|
+
await fs.writeFile(
|
|
599
|
+
path.join(dependencyRecoveryWorkspace, "config", "experiment.json"),
|
|
600
|
+
'{"offset":10}\n',
|
|
601
|
+
"utf8"
|
|
602
|
+
);
|
|
603
|
+
await fs.writeFile(
|
|
604
|
+
path.join(dependencyRecoveryWorkspace, "raw", "run_a.csv"),
|
|
605
|
+
"sample,signal\nA,100\n",
|
|
606
|
+
"utf8"
|
|
607
|
+
);
|
|
608
|
+
await fs.writeFile(
|
|
609
|
+
path.join(dependencyRecoveryWorkspace, "raw", "run_b.csv"),
|
|
610
|
+
"sample,signal\nB,90\n",
|
|
611
|
+
"utf8"
|
|
612
|
+
);
|
|
613
|
+
await fs.writeFile(
|
|
614
|
+
path.join(dependencyRecoveryWorkspace, "raw", "unrelated.csv"),
|
|
615
|
+
"private,unrelated\nno,1\n",
|
|
616
|
+
"utf8"
|
|
617
|
+
);
|
|
618
|
+
const dependencyRecoveryState = {
|
|
619
|
+
meta: {
|
|
620
|
+
projectVerification: {
|
|
621
|
+
mutationRevision: 2,
|
|
622
|
+
lastMutation: {
|
|
623
|
+
revision: 2,
|
|
624
|
+
toolName: "apply_patch",
|
|
625
|
+
paths: ["analysis.py"],
|
|
626
|
+
},
|
|
627
|
+
discoveredTests: ["tests/test_analysis.py"],
|
|
628
|
+
testRuns: [{
|
|
629
|
+
command: "python analysis.py",
|
|
630
|
+
mutationRevision: 2,
|
|
631
|
+
passed: false,
|
|
632
|
+
failureEvidenceVersion: 2,
|
|
633
|
+
failureSignature: "source-dependency-schema-mismatch",
|
|
634
|
+
failureSummary: "File \"analysis.py\", line 7, in <module>: KeyError: signal_mv",
|
|
635
|
+
}],
|
|
636
|
+
},
|
|
637
|
+
toolLoop: {
|
|
638
|
+
recent: [
|
|
639
|
+
"config/experiment.json",
|
|
640
|
+
"raw/run_a.csv",
|
|
641
|
+
"raw/run_b.csv",
|
|
642
|
+
"raw/unrelated.csv",
|
|
643
|
+
].map((readPath) => ({
|
|
644
|
+
toolName: "read_file",
|
|
645
|
+
path: readPath,
|
|
646
|
+
ok: true,
|
|
647
|
+
blocked: false,
|
|
648
|
+
})),
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
const dependencyRecoveryPacket = await buildFailedTestRecoveryPacket(
|
|
653
|
+
{ commandCwd: dependencyRecoveryWorkspace },
|
|
654
|
+
dependencyRecoveryState
|
|
655
|
+
);
|
|
656
|
+
assert(
|
|
657
|
+
[
|
|
658
|
+
"analysis.py",
|
|
659
|
+
"config/experiment.json",
|
|
660
|
+
"raw/run_a.csv",
|
|
661
|
+
"raw/run_b.csv",
|
|
662
|
+
].every((item) => dependencyRecoveryPacket.paths.includes(item)) &&
|
|
663
|
+
!dependencyRecoveryPacket.paths.includes("raw/unrelated.csv") &&
|
|
664
|
+
JSON.stringify(dependencyRecoveryPacket.repairPaths) === JSON.stringify(["analysis.py"]),
|
|
665
|
+
`failed-test recovery dropped previously read immutable dependencies or widened mutation scope: ${JSON.stringify(dependencyRecoveryPacket)}`
|
|
666
|
+
);
|
|
575
667
|
const baselineWorkspace = path.join(tempRoot, "baseline-recovery-workspace");
|
|
576
668
|
await fs.mkdir(baselineWorkspace, { recursive: true });
|
|
577
669
|
const trackedBaselineSource = [
|
|
@@ -343,6 +343,10 @@ async function main() {
|
|
|
343
343
|
assert(image.artifactPath, "read_image did not persist a perception artifact");
|
|
344
344
|
assert(image.markdownArtifactPath, "read_image did not persist a Markdown perception artifact");
|
|
345
345
|
assert(image.markdownPath, "read_image did not persist a workspace Markdown report");
|
|
346
|
+
assert(
|
|
347
|
+
image.markdownPath.startsWith(".aginti/artifacts/perception/"),
|
|
348
|
+
`read_image dirtied the task-owned artifact tree: ${image.markdownPath}`
|
|
349
|
+
);
|
|
346
350
|
assert(
|
|
347
351
|
path.basename(image.markdownPath).includes("tiny-image-analysis"),
|
|
348
352
|
`read_image emitted a generic artifact filename: ${image.markdownPath}`
|
|
@@ -1584,6 +1584,30 @@ assertStrict.deepEqual(
|
|
|
1584
1584
|
"task-owned Git completion delegated path selection back to the model"
|
|
1585
1585
|
);
|
|
1586
1586
|
|
|
1587
|
+
const requiredCommandBeforeCommitTools = selectProgressiveTools(allTools, {
|
|
1588
|
+
config: {
|
|
1589
|
+
provider: "localllm",
|
|
1590
|
+
artifactValidationPhase: true,
|
|
1591
|
+
artifactValidationNeedsCommand: true,
|
|
1592
|
+
requiredProjectCommandPending: true,
|
|
1593
|
+
requiredProjectCommand: "python analysis.py",
|
|
1594
|
+
artifactValidationPendingGitActions: ["commit"],
|
|
1595
|
+
artifactValidationCommitPaths: ["analysis.py", "AGINTI.md"],
|
|
1596
|
+
},
|
|
1597
|
+
goal: "Regenerate the outputs, validate them, and commit the intended repair.",
|
|
1598
|
+
profile: "data",
|
|
1599
|
+
});
|
|
1600
|
+
sameNames(
|
|
1601
|
+
requiredCommandBeforeCommitTools,
|
|
1602
|
+
["run_command", "finish"],
|
|
1603
|
+
"artifact Git completion preempted an outstanding exact project command"
|
|
1604
|
+
);
|
|
1605
|
+
assertStrict.deepEqual(
|
|
1606
|
+
requiredCommandBeforeCommitTools[0].function.parameters.properties.command.enum,
|
|
1607
|
+
["python analysis.py"],
|
|
1608
|
+
"artifact recovery exposed an open-ended command instead of the retained requirement"
|
|
1609
|
+
);
|
|
1610
|
+
|
|
1587
1611
|
const resumedArtifactRuntime = nextStepRuntimeConfig(
|
|
1588
1612
|
{
|
|
1589
1613
|
goal: "Perform a visual screenshot inspection of the generated plot and commit the intentional changes.",
|
|
@@ -309,6 +309,11 @@ try {
|
|
|
309
309
|
String(explanation.state.messages.find((message) => message.role === "system")?.content || "").length < 10_000,
|
|
310
310
|
"focused runtime prompt did not use progressive disclosure"
|
|
311
311
|
);
|
|
312
|
+
assert.match(
|
|
313
|
+
String(explanation.state.messages.find((message) => message.role === "system")?.content || ""),
|
|
314
|
+
/run that command unchanged before probing --help, alternate wrappers/i,
|
|
315
|
+
"focused runtime did not prioritize exact established routine commands"
|
|
316
|
+
);
|
|
312
317
|
assert(
|
|
313
318
|
Math.max(
|
|
314
319
|
...explanation.state.messages
|
package/src/agent-runner.js
CHANGED
|
@@ -2801,6 +2801,7 @@ function focusedCapabilityContext(config = {}) {
|
|
|
2801
2801
|
? `Advisory wrapper: ${normalizeWrapperName(config.preferredWrapper)} (${wrapperStatusText()}).`
|
|
2802
2802
|
: "Advisory wrappers: disabled.",
|
|
2803
2803
|
config.allowAuxiliaryTools ? "Auxiliary generation tools: enabled when the requested artifact needs them." : "Auxiliary tools: disabled.",
|
|
2804
|
+
"When the current request, project instructions, or routine contract names an exact established command, run that command unchanged before probing --help, alternate wrappers, process lists, or implementation source. If its result answers the request, stop discovery and finish.",
|
|
2804
2805
|
"Discovery must be bounded: after a blocked path or search, change method once; never use recursive grep. Prefer exact manifests, workspace search, or targeted rg with an explicit path, globs, and result limit.",
|
|
2805
2806
|
isRetainedWorkspaceProfile(config)
|
|
2806
2807
|
? isRetainedVisionWorkspaceProfile(config)
|
|
@@ -8494,7 +8495,7 @@ export function taskForbidsExternalValidatorSourceRead(config = {}, state = {})
|
|
|
8494
8495
|
function recoveryEvidenceDependencies(
|
|
8495
8496
|
sourcePath = "",
|
|
8496
8497
|
content = "",
|
|
8497
|
-
{ commandCwd = "" } = {}
|
|
8498
|
+
{ commandCwd = "", knownReadPaths = [] } = {}
|
|
8498
8499
|
) {
|
|
8499
8500
|
const explicitDependencies = [];
|
|
8500
8501
|
const importCandidates = [];
|
|
@@ -8530,7 +8531,7 @@ function recoveryEvidenceDependencies(
|
|
|
8530
8531
|
append(explicitDependencies, path.posix.normalize(raw));
|
|
8531
8532
|
}
|
|
8532
8533
|
for (const match of String(content || "").matchAll(
|
|
8533
|
-
|
|
8534
|
+
/\b[A-Za-z_][A-Za-z0-9_]*(?<segments>(?:\s*\/\s*["'][^"'\r\n]+["']){1,8})/g
|
|
8534
8535
|
)) {
|
|
8535
8536
|
const segments = [...String(match.groups?.segments || "").matchAll(/["']([^"'\r\n]+)["']/g)]
|
|
8536
8537
|
.map((segment) => String(segment[1] || "").trim())
|
|
@@ -8539,6 +8540,22 @@ function recoveryEvidenceDependencies(
|
|
|
8539
8540
|
append(explicitDependencies, path.posix.normalize(segments.join("/")));
|
|
8540
8541
|
}
|
|
8541
8542
|
}
|
|
8543
|
+
const sourceText = String(content || "");
|
|
8544
|
+
for (const item of Array.isArray(knownReadPaths) ? knownReadPaths : []) {
|
|
8545
|
+
const candidate = safeRecoveryEvidencePath(item);
|
|
8546
|
+
if (!candidate || safeTaskOwnedCommitPath(candidate) !== candidate) continue;
|
|
8547
|
+
const basename = path.posix.basename(candidate);
|
|
8548
|
+
const parentBasename = path.posix.basename(path.posix.dirname(candidate));
|
|
8549
|
+
if (!basename || !sourceText.includes(basename)) continue;
|
|
8550
|
+
if (
|
|
8551
|
+
parentBasename &&
|
|
8552
|
+
parentBasename !== "." &&
|
|
8553
|
+
!sourceText.includes(parentBasename)
|
|
8554
|
+
) {
|
|
8555
|
+
continue;
|
|
8556
|
+
}
|
|
8557
|
+
append(explicitDependencies, candidate);
|
|
8558
|
+
}
|
|
8542
8559
|
if (path.posix.extname(sourcePath).toLowerCase() === ".java") {
|
|
8543
8560
|
const normalizedSource = String(sourcePath || "").replace(/\\/g, "/");
|
|
8544
8561
|
const packageMatch = String(content || "").match(
|
|
@@ -8959,6 +8976,19 @@ export async function buildFailedTestRecoveryPacket(config = {}, state = {}) {
|
|
|
8959
8976
|
state,
|
|
8960
8977
|
testRun
|
|
8961
8978
|
);
|
|
8979
|
+
const knownReadPaths = (Array.isArray(state.meta?.toolLoop?.recent)
|
|
8980
|
+
? state.meta.toolLoop.recent
|
|
8981
|
+
: [])
|
|
8982
|
+
.filter(
|
|
8983
|
+
(entry) =>
|
|
8984
|
+
entry?.ok === true &&
|
|
8985
|
+
entry?.blocked !== true &&
|
|
8986
|
+
String(entry?.toolName || "") === "read_file"
|
|
8987
|
+
)
|
|
8988
|
+
.map((entry) => safeRecoveryEvidencePath(toolLoopReadEvidencePath(entry) || entry?.path))
|
|
8989
|
+
.filter((item) => item && safeTaskOwnedCommitPath(item) === item)
|
|
8990
|
+
.filter((item, index, items) => items.indexOf(item) === index)
|
|
8991
|
+
.slice(-24);
|
|
8962
8992
|
const queue = [
|
|
8963
8993
|
...failureMentionedPaths.filter(failedTestPathIsTestEvidence),
|
|
8964
8994
|
...(Array.isArray(verification.discoveredTests) ? verification.discoveredTests : []),
|
|
@@ -9535,7 +9565,10 @@ export async function buildFailedTestRecoveryPacket(config = {}, state = {}) {
|
|
|
9535
9565
|
);
|
|
9536
9566
|
}
|
|
9537
9567
|
}
|
|
9538
|
-
for (const dependency of recoveryEvidenceDependencies(relativePath, raw, {
|
|
9568
|
+
for (const dependency of recoveryEvidenceDependencies(relativePath, raw, {
|
|
9569
|
+
commandCwd,
|
|
9570
|
+
knownReadPaths,
|
|
9571
|
+
})) {
|
|
9539
9572
|
if (!seen.has(dependency) && !queue.includes(dependency)) queue.push(dependency);
|
|
9540
9573
|
}
|
|
9541
9574
|
}
|
package/src/docker-sandbox.js
CHANGED
|
@@ -196,6 +196,63 @@ export function dockerWorkspaceAliasMounts(
|
|
|
196
196
|
return workspace === DOCKER_WORKSPACE ? [] : [workspace];
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
function pathIsWithin(parentPath, candidatePath) {
|
|
200
|
+
const relative = path.relative(parentPath, candidatePath);
|
|
201
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function linkedWorktreeCommonGitDir(workspace) {
|
|
205
|
+
const dotGitPath = path.join(workspace, ".git");
|
|
206
|
+
let dotGit;
|
|
207
|
+
try {
|
|
208
|
+
if (!fsSync.statSync(dotGitPath).isFile()) return "";
|
|
209
|
+
dotGit = fsSync.readFileSync(dotGitPath, "utf8");
|
|
210
|
+
} catch {
|
|
211
|
+
return "";
|
|
212
|
+
}
|
|
213
|
+
const match = String(dotGit).match(/^gitdir:\s*(.+?)\s*$/im);
|
|
214
|
+
if (!match?.[1]) return "";
|
|
215
|
+
const gitDir = path.resolve(workspace, match[1]);
|
|
216
|
+
let commonDir = gitDir;
|
|
217
|
+
try {
|
|
218
|
+
const commonPath = path.join(gitDir, "commondir");
|
|
219
|
+
if (fsSync.existsSync(commonPath)) {
|
|
220
|
+
commonDir = path.resolve(gitDir, fsSync.readFileSync(commonPath, "utf8").trim());
|
|
221
|
+
}
|
|
222
|
+
const resolvedGitDir = fsSync.realpathSync(gitDir);
|
|
223
|
+
const resolvedCommonDir = fsSync.realpathSync(commonDir);
|
|
224
|
+
if (
|
|
225
|
+
!pathIsWithin(resolvedCommonDir, resolvedGitDir) ||
|
|
226
|
+
!fsSync.existsSync(path.join(resolvedCommonDir, "HEAD")) ||
|
|
227
|
+
!fsSync.existsSync(path.join(resolvedCommonDir, "objects"))
|
|
228
|
+
) {
|
|
229
|
+
return "";
|
|
230
|
+
}
|
|
231
|
+
return resolvedCommonDir;
|
|
232
|
+
} catch {
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function dockerWritableGitMounts(
|
|
238
|
+
command = "",
|
|
239
|
+
config = {},
|
|
240
|
+
policy = {},
|
|
241
|
+
sandboxMode = normalizeSandboxMode(config.sandboxMode)
|
|
242
|
+
) {
|
|
243
|
+
if (
|
|
244
|
+
sandboxMode !== "docker-workspace" ||
|
|
245
|
+
policy.gitOnly !== true ||
|
|
246
|
+
!/\bgit\s+(?:add|commit|rm|mv)\b/i.test(String(command || ""))
|
|
247
|
+
) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
const workspace = path.resolve(String(config.commandCwd || process.cwd()));
|
|
251
|
+
const commonDir = linkedWorktreeCommonGitDir(workspace);
|
|
252
|
+
if (!commonDir || pathIsWithin(workspace, commonDir)) return [];
|
|
253
|
+
return uniqueHostMounts([commonDir]);
|
|
254
|
+
}
|
|
255
|
+
|
|
199
256
|
async function ensurePersistentDockerDirs(config) {
|
|
200
257
|
const dirs = persistentDockerDirs(config);
|
|
201
258
|
await Promise.all([fs.mkdir(dirs.home, { recursive: true }), fs.mkdir(dirs.cache, { recursive: true }), fs.mkdir(dirs.env, { recursive: true })]);
|
|
@@ -381,6 +438,12 @@ function dockerRunArgs(command, config, policy = evaluateCommandPolicy(command,
|
|
|
381
438
|
"-v",
|
|
382
439
|
`${mount}:${mount}:${mountMode}`,
|
|
383
440
|
]);
|
|
441
|
+
const writableGitMountArgs = dockerWritableGitMounts(
|
|
442
|
+
command,
|
|
443
|
+
config,
|
|
444
|
+
policy,
|
|
445
|
+
sandboxMode
|
|
446
|
+
).flatMap((mount) => ["-v", `${mount}:${mount}:rw`]);
|
|
384
447
|
const readOnlyArgs =
|
|
385
448
|
mountMode === "ro"
|
|
386
449
|
? ["--read-only", "--tmpfs", "/tmp:rw,nosuid,nodev,size=128m"]
|
|
@@ -412,6 +475,7 @@ function dockerRunArgs(command, config, policy = evaluateCommandPolicy(command,
|
|
|
412
475
|
? ["-e", `GIT_CONFIG_GLOBAL=${DOCKER_GIT_CONFIG}`]
|
|
413
476
|
: []),
|
|
414
477
|
...readOnlyHostMountArgs,
|
|
478
|
+
...writableGitMountArgs,
|
|
415
479
|
...workspaceAliasMountArgs,
|
|
416
480
|
"-v",
|
|
417
481
|
`${config.commandCwd}:${DOCKER_WORKSPACE}:${mountMode}`,
|
package/src/perception-tools.js
CHANGED
|
@@ -302,7 +302,10 @@ async function persistMarkdownArtifact(store, config, subdir, stem, payload) {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
if (config?.commandCwd) {
|
|
305
|
-
|
|
305
|
+
// Perception reports are runtime evidence, not task-owned source. Keep the
|
|
306
|
+
// workspace-relative copy under the standard ignored AgInTi directory so
|
|
307
|
+
// visual validation cannot dirty an otherwise completed repository.
|
|
308
|
+
const workspaceDir = path.join(config.commandCwd, ".aginti", "artifacts", subdir);
|
|
306
309
|
await fs.mkdir(workspaceDir, { recursive: true });
|
|
307
310
|
const workspacePath = path.join(workspaceDir, filename);
|
|
308
311
|
await fs.writeFile(workspacePath, content, "utf8");
|
|
@@ -2304,7 +2304,8 @@ export function selectProgressiveTools(
|
|
|
2304
2304
|
if (
|
|
2305
2305
|
config.taskOwnedCommitPending === true &&
|
|
2306
2306
|
config.testFailureRepairActive !== true &&
|
|
2307
|
-
config.testVerificationPending !== true
|
|
2307
|
+
config.testVerificationPending !== true &&
|
|
2308
|
+
config.requiredProjectCommandPending !== true
|
|
2308
2309
|
) {
|
|
2309
2310
|
const available = new Map(enabled.map(({ name, tool }) => [name, tool]));
|
|
2310
2311
|
const pendingGitActions = Array.isArray(config.taskOwnedPendingGitActions)
|
|
@@ -2332,6 +2333,18 @@ export function selectProgressiveTools(
|
|
|
2332
2333
|
config.testVerificationPending !== true
|
|
2333
2334
|
) {
|
|
2334
2335
|
const available = new Map(enabled.map(({ name, tool }) => [name, tool]));
|
|
2336
|
+
if (
|
|
2337
|
+
config.requiredProjectCommandPending === true &&
|
|
2338
|
+
typeof config.requiredProjectCommand === "string" &&
|
|
2339
|
+
config.requiredProjectCommand.trim()
|
|
2340
|
+
) {
|
|
2341
|
+
const requiredCommand = constrainRunCommand(
|
|
2342
|
+
available.get("run_command"),
|
|
2343
|
+
config.requiredProjectCommand,
|
|
2344
|
+
"Run this exact outstanding project command before Git completion or final artifact acceptance. Preserve the generated outputs and do not substitute another status, commit, or discovery command."
|
|
2345
|
+
);
|
|
2346
|
+
return [requiredCommand, finish].filter(Boolean);
|
|
2347
|
+
}
|
|
2335
2348
|
const pendingGitActions = Array.isArray(config.artifactValidationPendingGitActions)
|
|
2336
2349
|
? config.artifactValidationPendingGitActions.map((item) => String(item || "").toLowerCase())
|
|
2337
2350
|
: [];
|