@tea-agent/loop-agent 0.7.5 → 0.9.0
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/AGENTS.md +145 -142
- package/CHANGELOG.md +161 -158
- package/README.md +206 -204
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/args.js +9 -2
- package/dist/commands/init.js +518 -488
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/executors/cursor-executor.js +1 -1
- package/dist/executors/shell-executor.js +74 -8
- package/dist/governance/manifest-types.js +1 -1
- package/dist/shared/reference-context.js +48 -22
- package/dist/task/config-types.js +1 -1
- package/dist/task/runtime.js +28 -28
- package/dist/worker/cli.js +3 -3
- package/dist/worker/loop-agent/loop-agent-client.js +51 -10
- package/dist/worker/observability/event-store.js +2 -1
- package/dist/worker/observability/read-model.js +13 -11
- package/dist/worker/observe/paths.js +2 -2
- package/dist/worker/observe/routes.js +82 -23
- package/dist/worker/observe/server.js +8 -6
- package/dist/worker/observe/static/app.js +1965 -1480
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/index.html +88 -63
- package/dist/worker/observe/static/styles.css +598 -722
- package/dist/worker/pool/run-store.js +7 -8
- package/dist/worker/run-task/run-task.js +11 -2
- package/dist/worker/runner/run-ready.js +1 -1
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/docs/README.md +84 -79
- package/docs/agent-dag-recovery-playbook.md +184 -184
- package/docs/agent-dag-runner.md +42 -42
- package/docs/architecture/runtime-boundaries.md +162 -162
- package/docs/cursor-executor-usage.md +25 -25
- package/docs/decisions/README.md +3 -3
- package/docs/design/README.md +49 -49
- package/docs/development-principles.md +73 -73
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -1749
- package/docs/exec-plans/README.md +6 -6
- package/docs/exec-plans/active/README.md +11 -11
- package/docs/exec-plans/completed/README.md +43 -34
- package/docs/feature-workflow.md +187 -187
- package/docs/harness-methodology-debugging.md +153 -153
- package/docs/harness-methodology-tdd.md +130 -130
- package/docs/harness-methodology-verification.md +27 -27
- package/docs/init-surface.manifest.json +245 -241
- package/docs/loop-agent-harness.md +63 -55
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +3 -3
- package/docs/reports/README.md +12 -9
- package/docs/skills/README.md +6 -6
- package/docs/skills/vetted-skill-registry.md +26 -26
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +454 -454
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/agent-dag.base.json +195 -195
- package/docs/templates/agent-dag.final-verification.json +190 -190
- package/docs/templates/agent-dag.schema.json +316 -316
- package/docs/templates/agent-dag.supervised-implementation.json +500 -500
- package/docs/templates/exec-plan.md +64 -64
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/harness.schema.json +218 -0
- package/docs/templates/hybrid-dag.json +193 -193
- package/docs/templates/init-evolution-review.md +33 -33
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/product-line/AGENTS.md +8 -8
- package/docs/templates/product-line/README.md +9 -9
- package/docs/templates/product-line/acceptance.yaml +14 -14
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/task-graph.yaml +15 -15
- package/docs/templates/product-line/task.yaml +65 -65
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/progress-log.md +17 -17
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +52 -52
- package/docs/templates/worker-dogfood-setup.md +48 -48
- package/docs/verification-matrix.md +49 -49
- package/examples/decision-gate-agent-dag.json +123 -123
- package/examples/example-dag.json +51 -51
- package/examples/hybrid-loop-agent-dag.json +194 -194
- package/harness.json +73 -71
- package/package.json +68 -67
- package/scripts/check-product-line-docs.sh +22 -22
- package/scripts/check-task-pool-root.sh +32 -0
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/init-capability-evolution/SKILL.md +69 -69
- package/skills/loop-agent/SKILL.md +149 -149
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +432 -412
- package/skills/loop-agent/references/harness-policy.md +263 -263
- package/skills/loop-agent/references/hybrid-dag.md +216 -216
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +59 -59
- package/skills/loop-agent/references/model-routing.md +36 -36
- package/skills/loop-agent/references/multi-worktree.md +54 -54
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +81 -81
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
- package/skills/loop-agent/references/task-workflow.md +89 -89
- package/skills/loop-agent/references/verification-and-failure-handling.md +133 -128
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/SKILL.md +296 -296
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/test-driven-development/SKILL.md +20 -20
- package/skills/verification-before-completion/SKILL.md +154 -154
- package/skills/webapp-testing/SKILL.md +19 -19
|
@@ -37,17 +37,17 @@ export function parseLoopBenchmarkArgs(args) {
|
|
|
37
37
|
return { json, markdown, outputPath };
|
|
38
38
|
}
|
|
39
39
|
export function printLoopBenchmarkUsage() {
|
|
40
|
-
console.log(`usage: loop-benchmark [options]
|
|
41
|
-
|
|
42
|
-
Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
|
|
43
|
-
|
|
44
|
-
Options:
|
|
45
|
-
--json Emit JSON (default when no format flag is set)
|
|
46
|
-
--markdown Emit Markdown report
|
|
47
|
-
--output <path> Write Markdown report to a repo-relative or absolute path
|
|
48
|
-
-h, --help Show this help
|
|
49
|
-
|
|
50
|
-
Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
|
|
40
|
+
console.log(`usage: loop-benchmark [options]
|
|
41
|
+
|
|
42
|
+
Deterministic loop-agent loop benchmark baseline (no live Pi/Cursor calls).
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
--json Emit JSON (default when no format flag is set)
|
|
46
|
+
--markdown Emit Markdown report
|
|
47
|
+
--output <path> Write Markdown report to a repo-relative or absolute path
|
|
48
|
+
-h, --help Show this help
|
|
49
|
+
|
|
50
|
+
Control groups: single-repair, 3-pass-convergence, 3-pass-convergence+quota.
|
|
51
51
|
Recommendation never changes convergence.enabled default.`);
|
|
52
52
|
}
|
|
53
53
|
export async function runLoopBenchmark(repoRoot, rawArgs) {
|
|
@@ -106,22 +106,22 @@ export function parsePiReuseBenchmarkArgs(args) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
export function printPiReuseBenchmarkUsage() {
|
|
109
|
-
console.log(`usage: pi-reuse-benchmark [options]
|
|
110
|
-
|
|
111
|
-
Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
|
|
112
|
-
|
|
113
|
-
Options:
|
|
114
|
-
--report <path> Benchmark report markdown (approval status)
|
|
115
|
-
--approval <path> Explicit approval JSON artifact
|
|
116
|
-
--off-executor <path> Baseline executor.jsonl (reuse off)
|
|
117
|
-
--on-executor <path> Treatment executor.jsonl (reuse on)
|
|
118
|
-
--off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
|
|
119
|
-
--on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
|
|
120
|
-
--json Emit JSON (default when no format flag is set)
|
|
121
|
-
--markdown Emit Markdown summary
|
|
122
|
-
-h, --help Show this help
|
|
123
|
-
|
|
124
|
-
Recommendations: defer | maintain-opt-in | eligible-for-human-review
|
|
109
|
+
console.log(`usage: pi-reuse-benchmark [options]
|
|
110
|
+
|
|
111
|
+
Deterministic Pi runtime reuse benchmark/decision summary (no live Pi calls).
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--report <path> Benchmark report markdown (approval status)
|
|
115
|
+
--approval <path> Explicit approval JSON artifact
|
|
116
|
+
--off-executor <path> Baseline executor.jsonl (reuse off)
|
|
117
|
+
--on-executor <path> Treatment executor.jsonl (reuse on)
|
|
118
|
+
--off-task <task-id> Resolve baseline from .harness/tasks/<id>/logs/executor.jsonl
|
|
119
|
+
--on-task <task-id> Resolve treatment from .harness/tasks/<id>/logs/executor.jsonl
|
|
120
|
+
--json Emit JSON (default when no format flag is set)
|
|
121
|
+
--markdown Emit Markdown summary
|
|
122
|
+
-h, --help Show this help
|
|
123
|
+
|
|
124
|
+
Recommendations: defer | maintain-opt-in | eligible-for-human-review
|
|
125
125
|
Never changes CODE_AGENT_PI_REUSE_RUNTIME default (off).`);
|
|
126
126
|
}
|
|
127
127
|
function resolveRepoRelative(repoRoot, filePath) {
|
|
@@ -29,7 +29,7 @@ export function resolveArtifactWriteDir(options) {
|
|
|
29
29
|
}
|
|
30
30
|
export function buildArtifactPathPrompt(writeDir) {
|
|
31
31
|
if (!writeDir)
|
|
32
|
-
return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
|
|
32
|
+
return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
|
|
33
33
|
${ARTIFACT_INSTRUCTIONS}`;
|
|
34
34
|
return [
|
|
35
35
|
`After changes, write the following files:`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { writeDagNodeTextArtifact } from "../infrastructure/harness/artifact-store.js";
|
|
5
5
|
import { truncateOutput } from "./cursor-executor.js";
|
|
@@ -11,6 +11,7 @@ const SUMMARY_STDOUT_MAX = 4_000;
|
|
|
11
11
|
const SUMMARY_STDERR_MAX = 2_000;
|
|
12
12
|
const NODE_STDOUT_MAX = 12_000;
|
|
13
13
|
const NODE_STDERR_MAX = 8_000;
|
|
14
|
+
const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
|
|
14
15
|
function resolveBashExecutable() {
|
|
15
16
|
const envCandidate = process.env.LOOP_AGENT_BASH ?? process.env.GIT_BASH;
|
|
16
17
|
if (envCandidate && existsSync(envCandidate))
|
|
@@ -85,8 +86,8 @@ export async function executeShellCommand(input) {
|
|
|
85
86
|
env: buildShellProcessEnv(input.envAllowlist, injectedEnv),
|
|
86
87
|
stdio: ["ignore", "pipe", "pipe"],
|
|
87
88
|
});
|
|
88
|
-
let stdout =
|
|
89
|
-
let stderr =
|
|
89
|
+
let stdout = createBoundedOutput();
|
|
90
|
+
let stderr = createBoundedOutput();
|
|
90
91
|
let timedOut = false;
|
|
91
92
|
let settled = false;
|
|
92
93
|
let timeoutHandle;
|
|
@@ -103,22 +104,36 @@ export async function executeShellCommand(input) {
|
|
|
103
104
|
command: input.command,
|
|
104
105
|
cwd: input.cwd,
|
|
105
106
|
durationMs: Date.now() - startedAt,
|
|
106
|
-
stdout,
|
|
107
|
-
|
|
107
|
+
stdout: formatBoundedOutput(stdout),
|
|
108
|
+
stdoutBytes: stdout.bytes,
|
|
109
|
+
stdoutTruncated: stdout.truncated,
|
|
110
|
+
stderr: formatBoundedOutput(stderr),
|
|
111
|
+
stderrBytes: stderr.bytes,
|
|
112
|
+
stderrTruncated: stderr.truncated,
|
|
113
|
+
...(input.outputArtifacts
|
|
114
|
+
? {
|
|
115
|
+
stdoutArtifactPath: input.outputArtifacts.stdoutPath,
|
|
116
|
+
stderrArtifactPath: input.outputArtifacts.stderrPath,
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
108
119
|
timedOut,
|
|
109
120
|
...result,
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
123
|
child.stdout.setEncoding("utf-8");
|
|
113
124
|
child.stderr.setEncoding("utf-8");
|
|
125
|
+
initializeOutputArtifacts(input.outputArtifacts);
|
|
114
126
|
child.stdout.on("data", (chunk) => {
|
|
115
|
-
stdout
|
|
127
|
+
stdout = appendBoundedOutput(stdout, chunk);
|
|
128
|
+
appendOutputArtifact(input.outputArtifacts?.stdoutPath, chunk);
|
|
116
129
|
});
|
|
117
130
|
child.stderr.on("data", (chunk) => {
|
|
118
|
-
stderr
|
|
131
|
+
stderr = appendBoundedOutput(stderr, chunk);
|
|
132
|
+
appendOutputArtifact(input.outputArtifacts?.stderrPath, chunk);
|
|
119
133
|
});
|
|
120
134
|
child.on("error", (error) => {
|
|
121
|
-
stderr
|
|
135
|
+
stderr = appendBoundedOutput(stderr, `${stderr.text ? "\n" : ""}${error.message}`);
|
|
136
|
+
appendOutputArtifact(input.outputArtifacts?.stderrPath, error.message);
|
|
122
137
|
finish({
|
|
123
138
|
exitCode: null,
|
|
124
139
|
failureCategory: "spawn-error",
|
|
@@ -149,6 +164,46 @@ export async function executeShellCommand(input) {
|
|
|
149
164
|
}
|
|
150
165
|
});
|
|
151
166
|
}
|
|
167
|
+
function createBoundedOutput() {
|
|
168
|
+
return { bytes: 0, text: "", truncated: false };
|
|
169
|
+
}
|
|
170
|
+
function appendBoundedOutput(current, chunk) {
|
|
171
|
+
const bytes = current.bytes + Buffer.byteLength(chunk);
|
|
172
|
+
const combined = current.text + chunk;
|
|
173
|
+
if (Buffer.byteLength(combined) <= MAX_BUFFERED_OUTPUT_BYTES) {
|
|
174
|
+
return { bytes, text: combined, truncated: current.truncated };
|
|
175
|
+
}
|
|
176
|
+
const tail = Buffer.from(combined)
|
|
177
|
+
.subarray(-MAX_BUFFERED_OUTPUT_BYTES)
|
|
178
|
+
.toString("utf-8")
|
|
179
|
+
.replace(/^\uFFFD/, "");
|
|
180
|
+
return { bytes, text: tail, truncated: true };
|
|
181
|
+
}
|
|
182
|
+
function formatBoundedOutput(output) {
|
|
183
|
+
return output.truncated ? `${output.text}\n...[truncated]` : output.text;
|
|
184
|
+
}
|
|
185
|
+
function initializeOutputArtifacts(paths) {
|
|
186
|
+
if (!paths)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(path.dirname(paths.stdoutPath), { recursive: true });
|
|
190
|
+
writeFileSync(paths.stdoutPath, "", "utf-8");
|
|
191
|
+
writeFileSync(paths.stderrPath, "", "utf-8");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Artifact persistence is best-effort; the bounded result remains available.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function appendOutputArtifact(filePath, chunk) {
|
|
198
|
+
if (!filePath)
|
|
199
|
+
return;
|
|
200
|
+
try {
|
|
201
|
+
appendFileSync(filePath, chunk, "utf-8");
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Keep command execution independent from a transient artifact write failure.
|
|
205
|
+
}
|
|
206
|
+
}
|
|
152
207
|
function summarizeCommandResults(results) {
|
|
153
208
|
const stdout = results
|
|
154
209
|
.map((result) => [`$ ${result.command}`, result.stdout.trim()].filter(Boolean).join("\n"))
|
|
@@ -195,11 +250,17 @@ export function buildShellResultSummaryMarkdown(input) {
|
|
|
195
250
|
lines.push("", `## Command ${index + 1}: ${result.command}`, "", "### stdout", "", "```text", stdout.text, "```");
|
|
196
251
|
if (stdout.truncated)
|
|
197
252
|
lines.push("", "_(stdout truncated)_");
|
|
253
|
+
if (result.stdoutArtifactPath) {
|
|
254
|
+
lines.push("", `Full stdout artifact: ${result.stdoutArtifactPath}`);
|
|
255
|
+
}
|
|
198
256
|
if (stderr.text.trim()) {
|
|
199
257
|
lines.push("", "### stderr", "", "```text", stderr.text, "```");
|
|
200
258
|
if (stderr.truncated)
|
|
201
259
|
lines.push("", "_(stderr truncated)_");
|
|
202
260
|
}
|
|
261
|
+
if (result.stderrArtifactPath) {
|
|
262
|
+
lines.push("", `Full stderr artifact: ${result.stderrArtifactPath}`);
|
|
263
|
+
}
|
|
203
264
|
}
|
|
204
265
|
return `${lines.join("\n")}\n`;
|
|
205
266
|
}
|
|
@@ -235,12 +296,17 @@ export async function executeDagShellNode(input, meta) {
|
|
|
235
296
|
beforeStatus = undefined;
|
|
236
297
|
}
|
|
237
298
|
for (const command of commands) {
|
|
299
|
+
const commandNumber = results.length + 1;
|
|
238
300
|
const result = await executeShellCommand({
|
|
239
301
|
command,
|
|
240
302
|
cwd,
|
|
241
303
|
timeoutMs,
|
|
242
304
|
envAllowlist: shell.envAllowlist,
|
|
243
305
|
dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
|
|
306
|
+
outputArtifacts: {
|
|
307
|
+
stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
|
|
308
|
+
stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
|
|
309
|
+
},
|
|
244
310
|
});
|
|
245
311
|
results.push(result);
|
|
246
312
|
if (!result.ok)
|
|
@@ -46,7 +46,7 @@ export const workflowPolicySchema = z
|
|
|
46
46
|
.object({
|
|
47
47
|
/** Preferred implementation workflow for recoverable loop-agent work. Declarative policy; callers may still require explicit CLI flags. */
|
|
48
48
|
defaultImplementationWorkflow: z
|
|
49
|
-
.enum(["agent-dag"
|
|
49
|
+
.enum(["agent-dag"])
|
|
50
50
|
.optional()
|
|
51
51
|
.default("agent-dag"),
|
|
52
52
|
dag: z
|
|
@@ -7,6 +7,17 @@ import path from 'node:path';
|
|
|
7
7
|
const DEFAULT_MAX_FILES_PER_REPO = 24;
|
|
8
8
|
const DEFAULT_MAX_TOTAL_FILES = 48;
|
|
9
9
|
const DEFAULT_MAX_FILE_BYTES = 512_000;
|
|
10
|
+
const MAX_REFERENCE_ENTRIES_PER_REPO = 1_024;
|
|
11
|
+
const SKIPPED_REFERENCE_DIRECTORIES = new Set([
|
|
12
|
+
'.git',
|
|
13
|
+
'.hg',
|
|
14
|
+
'.svn',
|
|
15
|
+
'.venv',
|
|
16
|
+
'build',
|
|
17
|
+
'dist',
|
|
18
|
+
'node_modules',
|
|
19
|
+
'target',
|
|
20
|
+
]);
|
|
10
21
|
export function isFeatureStudyTask(taskConfig) {
|
|
11
22
|
return taskConfig.taskKind === 'feature-study';
|
|
12
23
|
}
|
|
@@ -102,8 +113,8 @@ function uniqueKeepOrder(items) {
|
|
|
102
113
|
return result;
|
|
103
114
|
}
|
|
104
115
|
async function collectRepoFiles(root, focusGlobs, maxFiles) {
|
|
105
|
-
const candidates = await listFilesRecursive(root);
|
|
106
|
-
const matched = candidates
|
|
116
|
+
const candidates = await listFilesRecursive(root, MAX_REFERENCE_ENTRIES_PER_REPO);
|
|
117
|
+
const matched = candidates.files
|
|
107
118
|
.map((absolutePath) => ({
|
|
108
119
|
absolutePath,
|
|
109
120
|
relativePath: path.relative(root, absolutePath).replace(/\\/g, '/'),
|
|
@@ -111,7 +122,7 @@ async function collectRepoFiles(root, focusGlobs, maxFiles) {
|
|
|
111
122
|
.filter((entry) => focusGlobs.some((pattern) => matchGlob(pattern, entry.relativePath)))
|
|
112
123
|
.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
113
124
|
const files = [];
|
|
114
|
-
let truncated =
|
|
125
|
+
let truncated = candidates.truncated;
|
|
115
126
|
for (const entry of matched) {
|
|
116
127
|
if (files.length >= maxFiles) {
|
|
117
128
|
truncated = true;
|
|
@@ -122,10 +133,6 @@ async function collectRepoFiles(root, focusGlobs, maxFiles) {
|
|
|
122
133
|
continue;
|
|
123
134
|
if (fileStat.size > DEFAULT_MAX_FILE_BYTES)
|
|
124
135
|
continue;
|
|
125
|
-
const lower = entry.relativePath.toLowerCase();
|
|
126
|
-
if (lower.includes('/target/') || lower.includes('/node_modules/') || lower.includes('/.git/')) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
136
|
files.push({
|
|
130
137
|
relativePath: entry.relativePath,
|
|
131
138
|
absolutePath: entry.absolutePath,
|
|
@@ -134,21 +141,40 @@ async function collectRepoFiles(root, focusGlobs, maxFiles) {
|
|
|
134
141
|
}
|
|
135
142
|
return { files, truncated };
|
|
136
143
|
}
|
|
137
|
-
async function listFilesRecursive(dir) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
async function listFilesRecursive(dir, maxEntries) {
|
|
145
|
+
const files = [];
|
|
146
|
+
let visited = 0;
|
|
147
|
+
let truncated = false;
|
|
148
|
+
const walk = async (current) => {
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (visited >= maxEntries) {
|
|
158
|
+
truncated = true;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
visited += 1;
|
|
162
|
+
if (entry.isDirectory() && SKIPPED_REFERENCE_DIRECTORIES.has(entry.name.toLowerCase())) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const target = path.join(current, entry.name);
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
await walk(target);
|
|
168
|
+
if (truncated)
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
files.push(target);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
await walk(dir);
|
|
177
|
+
return { files, truncated };
|
|
152
178
|
}
|
|
153
179
|
export function matchGlob(pattern, relativePath) {
|
|
154
180
|
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
@@ -76,7 +76,7 @@ export const taskConfigSchema = z.object({
|
|
|
76
76
|
complexity: taskComplexitySchema.optional().default("medium"),
|
|
77
77
|
/** Explicit delivery capabilities that affect writer contracts/model tier without changing risk. */
|
|
78
78
|
capabilities: z.array(taskCapabilitySchema).optional(),
|
|
79
|
-
verifyMode: verifyModeSchema.optional().default("
|
|
79
|
+
verifyMode: verifyModeSchema.optional().default("serial"),
|
|
80
80
|
verifyPreset: verifyPresetSchema.optional().default("auto"),
|
|
81
81
|
/** Task-contract commands that must be included in final DAG shell verification. */
|
|
82
82
|
verifyCommands: z.array(taskVerifyCommandSchema).optional().default([]),
|
package/dist/task/runtime.js
CHANGED
|
@@ -102,7 +102,7 @@ export async function createTask(repoRoot, taskId, title) {
|
|
|
102
102
|
complexity: 'medium',
|
|
103
103
|
dagFallbackReason: '',
|
|
104
104
|
contextProfile: 'full',
|
|
105
|
-
verifyMode: '
|
|
105
|
+
verifyMode: 'serial',
|
|
106
106
|
verifyPreset: 'auto',
|
|
107
107
|
verifyCommands: [],
|
|
108
108
|
verifyQuota: 'full',
|
|
@@ -237,35 +237,35 @@ export function shouldInjectSubagentGuidance(step, mode) {
|
|
|
237
237
|
return false;
|
|
238
238
|
}
|
|
239
239
|
/** Advisory guidance for `analyze-plan` mode. */
|
|
240
|
-
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
241
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
242
|
-
Use it only for read-only tasks:
|
|
243
|
-
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
244
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
245
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
246
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
247
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
248
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
240
|
+
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
241
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
242
|
+
Use it only for read-only tasks:
|
|
243
|
+
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
244
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
245
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
246
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
247
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
248
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
249
249
|
</subagent_guidance>`;
|
|
250
250
|
/** Strong guidance for `full` mode — prescriptive when to delegate. */
|
|
251
|
-
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
252
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
253
|
-
|
|
254
|
-
You SHOULD delegate to subagent scouts when:
|
|
255
|
-
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
256
|
-
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
257
|
-
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
258
|
-
|
|
259
|
-
Delegation saves context tokens and produces better results.
|
|
260
|
-
|
|
261
|
-
Allowed patterns:
|
|
262
|
-
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
263
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
264
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
265
|
-
|
|
266
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
267
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
268
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
251
|
+
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
252
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
253
|
+
|
|
254
|
+
You SHOULD delegate to subagent scouts when:
|
|
255
|
+
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
256
|
+
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
257
|
+
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
258
|
+
|
|
259
|
+
Delegation saves context tokens and produces better results.
|
|
260
|
+
|
|
261
|
+
Allowed patterns:
|
|
262
|
+
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
263
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
264
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
265
|
+
|
|
266
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
267
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
268
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
269
269
|
</subagent_guidance>`;
|
|
270
270
|
/** Preserved for backward compatibility (alias of STANDARD). */
|
|
271
271
|
export const SUBAGENT_GUIDANCE = SUBAGENT_GUIDANCE_STANDARD;
|
package/dist/worker/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createCompositeProgressReporter } from "./observability/progress-compos
|
|
|
13
13
|
import { createRoutedWorkerEventStore } from "./observability/event-store.js";
|
|
14
14
|
import { buildGlobalSnapshot } from "./observability/read-model.js";
|
|
15
15
|
import { createObserveServer } from "./observe/server.js";
|
|
16
|
-
import { prepareTaskPoolRetry } from "./pool/run-store.js";
|
|
16
|
+
import { getTaskPoolRoot, prepareTaskPoolRetry } from "./pool/run-store.js";
|
|
17
17
|
import { taskSpecSchema } from "./task-spec/schema.js";
|
|
18
18
|
import { validateTaskSpec } from "./task-spec/validate.js";
|
|
19
19
|
import { validateFeatureTaskGraph } from "./task-graph/validate.js";
|
|
@@ -91,7 +91,7 @@ export function buildAgentWorkerProgram() {
|
|
|
91
91
|
const batchRunId = options.batchRunId ?? buildBatchRunId(new Date());
|
|
92
92
|
const client = new LoopAgentClient({
|
|
93
93
|
loopAgentBin: options.loopAgentBin,
|
|
94
|
-
artifactRoot: path.join(repoRoot, "
|
|
94
|
+
artifactRoot: path.join(getTaskPoolRoot(repoRoot), "artifacts", batchRunId),
|
|
95
95
|
});
|
|
96
96
|
let progress;
|
|
97
97
|
try {
|
|
@@ -161,7 +161,7 @@ export function buildAgentWorkerProgram() {
|
|
|
161
161
|
.action(async (options) => {
|
|
162
162
|
const repoRoot = path.resolve(options.repo);
|
|
163
163
|
const outputPath = options.output ??
|
|
164
|
-
path.join(repoRoot, "
|
|
164
|
+
path.join(getTaskPoolRoot(repoRoot), "reports", "morning-report.md");
|
|
165
165
|
await writeMorningReport({
|
|
166
166
|
repoRoot,
|
|
167
167
|
...(options.batchRunId ? { batchRunId: options.batchRunId } : {}),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { appendFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { parseCommandJson } from "./parse-json.js";
|
|
6
6
|
export const DEFAULT_WORKER_COMMAND_TIMEOUT_MS = 120_000;
|
|
7
7
|
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000;
|
|
8
|
+
const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
|
|
8
9
|
export class LoopAgentClient {
|
|
9
10
|
loopAgentBin;
|
|
10
11
|
baseArgs;
|
|
@@ -35,7 +36,7 @@ export class LoopAgentClient {
|
|
|
35
36
|
const stdoutPath = path.join(artifactDir, "stdout.txt");
|
|
36
37
|
const stderrPath = path.join(artifactDir, "stderr.txt");
|
|
37
38
|
const resultPath = path.join(artifactDir, "result.json");
|
|
38
|
-
const { stdout, stderr, exitCode, timedOut } = await spawnCommand({
|
|
39
|
+
const { stdout, stdoutBytes, stdoutTruncated, stderr, stderrBytes, stderrTruncated, exitCode, timedOut, } = await spawnCommand({
|
|
39
40
|
command,
|
|
40
41
|
args: commandArgs,
|
|
41
42
|
cwd: options.cwd,
|
|
@@ -58,7 +59,11 @@ export class LoopAgentClient {
|
|
|
58
59
|
durationMs: Date.now() - startedAt,
|
|
59
60
|
exitCode,
|
|
60
61
|
stdout,
|
|
62
|
+
stdoutBytes,
|
|
63
|
+
stdoutTruncated,
|
|
61
64
|
stderr,
|
|
65
|
+
stderrBytes,
|
|
66
|
+
stderrTruncated,
|
|
62
67
|
timedOut,
|
|
63
68
|
artifacts: {
|
|
64
69
|
dir: artifactDir,
|
|
@@ -68,7 +73,10 @@ export class LoopAgentClient {
|
|
|
68
73
|
},
|
|
69
74
|
};
|
|
70
75
|
if (options.expectJson) {
|
|
71
|
-
const
|
|
76
|
+
const jsonSource = stdoutTruncated
|
|
77
|
+
? await readFullOutputArtifact(stdoutPath, stdout)
|
|
78
|
+
: stdout;
|
|
79
|
+
const parsed = parseCommandJson(jsonSource);
|
|
72
80
|
if (parsed.ok) {
|
|
73
81
|
result.json = parsed.value;
|
|
74
82
|
}
|
|
@@ -77,8 +85,6 @@ export class LoopAgentClient {
|
|
|
77
85
|
result.parseFailure = parsed.failure;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
|
-
await writeFile(result.artifacts.stdoutPath, stdout, "utf-8");
|
|
81
|
-
await writeFile(result.artifacts.stderrPath, stderr, "utf-8");
|
|
82
88
|
await writeFile(result.artifacts.resultPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
|
|
83
89
|
return result;
|
|
84
90
|
}
|
|
@@ -109,8 +115,8 @@ function spawnCommand(input) {
|
|
|
109
115
|
resultPath: input.resultPath,
|
|
110
116
|
},
|
|
111
117
|
});
|
|
112
|
-
let stdout =
|
|
113
|
-
let stderr =
|
|
118
|
+
let stdout = createBoundedOutput();
|
|
119
|
+
let stderr = createBoundedOutput();
|
|
114
120
|
let timedOut = false;
|
|
115
121
|
const timeout = setTimeout(() => {
|
|
116
122
|
timedOut = true;
|
|
@@ -132,12 +138,12 @@ function spawnCommand(input) {
|
|
|
132
138
|
child.stdout.setEncoding("utf8");
|
|
133
139
|
child.stderr.setEncoding("utf8");
|
|
134
140
|
child.stdout.on("data", (chunk) => {
|
|
135
|
-
stdout
|
|
141
|
+
stdout = appendBoundedOutput(stdout, chunk);
|
|
136
142
|
appendChunkBestEffort(input.stdoutPath, chunk);
|
|
137
143
|
invokeChunkCallback(input.onStdout, chunk);
|
|
138
144
|
});
|
|
139
145
|
child.stderr.on("data", (chunk) => {
|
|
140
|
-
stderr
|
|
146
|
+
stderr = appendBoundedOutput(stderr, chunk);
|
|
141
147
|
appendChunkBestEffort(input.stderrPath, chunk);
|
|
142
148
|
invokeChunkCallback(input.onStderr, chunk);
|
|
143
149
|
});
|
|
@@ -147,10 +153,45 @@ function spawnCommand(input) {
|
|
|
147
153
|
});
|
|
148
154
|
child.on("close", (exitCode) => {
|
|
149
155
|
clearTimers();
|
|
150
|
-
resolve({
|
|
156
|
+
resolve({
|
|
157
|
+
stdout: formatBoundedOutput(stdout),
|
|
158
|
+
stdoutBytes: stdout.bytes,
|
|
159
|
+
stdoutTruncated: stdout.truncated,
|
|
160
|
+
stderr: formatBoundedOutput(stderr),
|
|
161
|
+
stderrBytes: stderr.bytes,
|
|
162
|
+
stderrTruncated: stderr.truncated,
|
|
163
|
+
exitCode,
|
|
164
|
+
timedOut,
|
|
165
|
+
});
|
|
151
166
|
});
|
|
152
167
|
});
|
|
153
168
|
}
|
|
169
|
+
function createBoundedOutput() {
|
|
170
|
+
return { bytes: 0, text: "", truncated: false };
|
|
171
|
+
}
|
|
172
|
+
function appendBoundedOutput(current, chunk) {
|
|
173
|
+
const bytes = current.bytes + Buffer.byteLength(chunk);
|
|
174
|
+
const combined = current.text + chunk;
|
|
175
|
+
if (Buffer.byteLength(combined) <= MAX_BUFFERED_OUTPUT_BYTES) {
|
|
176
|
+
return { bytes, text: combined, truncated: current.truncated };
|
|
177
|
+
}
|
|
178
|
+
const tail = Buffer.from(combined)
|
|
179
|
+
.subarray(-MAX_BUFFERED_OUTPUT_BYTES)
|
|
180
|
+
.toString("utf-8")
|
|
181
|
+
.replace(/^\uFFFD/, "");
|
|
182
|
+
return { bytes, text: tail, truncated: true };
|
|
183
|
+
}
|
|
184
|
+
function formatBoundedOutput(output) {
|
|
185
|
+
return output.truncated ? `${output.text}\n...[truncated]` : output.text;
|
|
186
|
+
}
|
|
187
|
+
async function readFullOutputArtifact(filePath, fallback) {
|
|
188
|
+
try {
|
|
189
|
+
return await readFile(filePath, "utf-8");
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return fallback;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
154
195
|
function appendChunkBestEffort(filePath, chunk) {
|
|
155
196
|
try {
|
|
156
197
|
appendFileSync(filePath, chunk, "utf-8");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { getTaskPoolRoot } from "../pool/run-store.js";
|
|
4
5
|
function writeDiagnostic(context, error) {
|
|
5
6
|
const message = error instanceof Error ? error.message : String(error);
|
|
6
7
|
process.stderr.write(`[worker-event-store] ${context}: ${message}\n`);
|
|
@@ -43,7 +44,7 @@ export function createWorkerEventStore(jsonlPath) {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
export function createRoutedWorkerEventStore(repoRoot) {
|
|
46
|
-
const observabilityRoot = path.join(path.resolve(repoRoot), "
|
|
47
|
+
const observabilityRoot = path.join(getTaskPoolRoot(path.resolve(repoRoot)), "observability");
|
|
47
48
|
const globalStore = createWorkerEventStore(path.join(observabilityRoot, "events.jsonl"));
|
|
48
49
|
let queue = Promise.resolve();
|
|
49
50
|
function storesFor(event) {
|