@tea-agent/loop-agent 0.5.0 → 0.7.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 +142 -142
- package/CHANGELOG.md +132 -98
- package/README.md +195 -195
- package/bin/agent-worker.js +22 -22
- package/bin/loop-agent.js +21 -21
- package/dist/application/dag/args.js +9 -1
- package/dist/application/dag/run-dag.js +16 -2
- package/dist/cli/command-definitions.js +22 -4
- package/dist/cli/help.js +3 -2
- package/dist/cli/program.js +7 -5
- package/dist/commands/import-prd.js +76 -0
- package/dist/commands/init.js +467 -457
- package/dist/commands/instructions.js +90 -58
- 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/dag-pi-executor.js +1 -0
- package/dist/executors/pi-sdk-executor.js +63 -1
- package/dist/shared/preview.js +39 -0
- package/dist/task/config-types.js +3 -0
- package/dist/task/runtime.js +27 -27
- package/dist/task/source-references.js +221 -0
- package/dist/worker/cli.js +62 -1
- package/dist/worker/loop-agent/loop-agent-client.js +97 -5
- package/dist/worker/materialize/harness-task-materializer.js +166 -5
- package/dist/worker/observability/event-store.js +82 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/progress-composite.js +33 -0
- package/dist/worker/observability/read-model.js +1013 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/paths.js +64 -0
- package/dist/worker/observe/routes.js +423 -0
- package/dist/worker/observe/server.js +61 -0
- package/dist/worker/observe/static/app.js +1419 -0
- package/dist/worker/observe/static/index.html +63 -0
- package/dist/worker/observe/static/styles.css +613 -0
- package/dist/worker/pool/failure-routing.js +41 -6
- package/dist/worker/pool/run-store.js +50 -0
- package/dist/worker/progress-reporter.js +0 -18
- package/dist/worker/run-task/run-task.js +327 -92
- package/dist/worker/runner/run-ready.js +112 -4
- package/dist/worker/task-spec/schema.js +2 -1
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/init-hybrid.js +182 -21
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/docs/README.md +75 -72
- package/docs/agent-dag-recovery-playbook.md +184 -184
- package/docs/agent-dag-runner.md +42 -42
- package/docs/architecture/runtime-boundaries.md +162 -147
- package/docs/cursor-executor-usage.md +25 -25
- package/docs/decisions/README.md +3 -3
- package/docs/design/README.md +49 -36
- 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 +12 -7
- package/docs/exec-plans/completed/README.md +32 -19
- package/docs/feature-workflow.md +186 -186
- 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 +208 -199
- package/docs/loop-agent-harness.md +55 -42
- package/docs/production-readiness.md +96 -96
- package/docs/progress/README.md +3 -3
- package/docs/reports/README.md +9 -5
- 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/hybrid-dag.json +193 -193
- package/docs/templates/init-evolution-review.md +33 -33
- package/docs/templates/interactive-ui-round2-experiment.md +66 -0
- 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 -0
- package/docs/templates/worker-dogfood-setup.md +48 -0
- package/docs/verification-matrix.md +41 -41
- 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 +70 -69
- package/package.json +66 -66
- 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 -147
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +412 -403
- package/skills/loop-agent/references/harness-policy.md +263 -259
- 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 -84
- package/skills/loop-agent/references/verification-and-failure-handling.md +128 -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
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { access, readdir } from
|
|
2
|
-
import path from
|
|
3
|
-
import { getArtifactPath, getArtifactRelativePath, } from
|
|
4
|
-
import { defaultHybridDagOutputPath } from
|
|
5
|
-
import { getTaskDir, getTaskPaths, loadTaskConfig } from
|
|
1
|
+
import { access, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getArtifactPath, getArtifactRelativePath, } from "../shared/artifacts-core.js";
|
|
4
|
+
import { defaultHybridDagOutputPath } from "../workflows/dag/init-hybrid.js";
|
|
5
|
+
import { getTaskDir, getTaskPaths, loadTaskConfig } from "../task/runtime.js";
|
|
6
6
|
const STAGES = new Set([
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
"source",
|
|
8
|
+
"dag-draft",
|
|
9
|
+
"task-artifacts",
|
|
10
|
+
"promotion",
|
|
11
|
+
"closeout",
|
|
12
12
|
]);
|
|
13
13
|
export function parseInstructionsArgs(args) {
|
|
14
14
|
const [stageRaw, ...rest] = args;
|
|
15
15
|
if (!stageRaw || !STAGES.has(stageRaw)) {
|
|
16
|
-
throw new Error(
|
|
16
|
+
throw new Error("usage: instructions <source|dag-draft|task-artifacts|promotion|closeout> --task <task-id> [--json]");
|
|
17
17
|
}
|
|
18
18
|
let taskId;
|
|
19
19
|
let json = false;
|
|
20
20
|
for (let i = 0; i < rest.length; i += 1) {
|
|
21
21
|
const arg = rest[i];
|
|
22
|
-
if (arg ===
|
|
22
|
+
if (arg === "--task") {
|
|
23
23
|
taskId = rest[++i];
|
|
24
24
|
if (!taskId)
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error("instructions --task requires a task id");
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
|
-
if (arg.startsWith(
|
|
29
|
-
taskId = arg.slice(
|
|
28
|
+
if (arg.startsWith("--task=")) {
|
|
29
|
+
taskId = arg.slice("--task=".length);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
if (arg ===
|
|
32
|
+
if (arg === "--json") {
|
|
33
33
|
json = true;
|
|
34
34
|
continue;
|
|
35
35
|
}
|
|
36
36
|
throw new Error(`unknown instructions flag: ${arg}`);
|
|
37
37
|
}
|
|
38
38
|
if (!taskId)
|
|
39
|
-
throw new Error(
|
|
39
|
+
throw new Error("instructions requires --task <task-id>");
|
|
40
40
|
return { stage: stageRaw, taskId, json };
|
|
41
41
|
}
|
|
42
42
|
export async function runInstructions(repoRoot, args) {
|
|
@@ -54,13 +54,13 @@ export async function buildInstructions(repoRoot, stage, taskId) {
|
|
|
54
54
|
const taskConfig = await loadTaskConfig(repoRoot, taskId);
|
|
55
55
|
const allowedEditRoots = taskConfig.allowedPaths.length > 0
|
|
56
56
|
? taskConfig.allowedPaths
|
|
57
|
-
: [path.relative(repoRoot, taskDir).replace(/\\/g,
|
|
57
|
+
: [path.relative(repoRoot, taskDir).replace(/\\/g, "/")];
|
|
58
58
|
const writePolicy = {
|
|
59
|
-
mode: stage ===
|
|
60
|
-
?
|
|
61
|
-
: stage ===
|
|
62
|
-
?
|
|
63
|
-
:
|
|
59
|
+
mode: stage === "promotion"
|
|
60
|
+
? "run-evidence-read-only"
|
|
61
|
+
: stage === "closeout"
|
|
62
|
+
? "docs-progress"
|
|
63
|
+
: "task-owned",
|
|
64
64
|
allowedEditRoots,
|
|
65
65
|
forbiddenPaths: taskConfig.forbiddenPaths,
|
|
66
66
|
};
|
|
@@ -70,74 +70,106 @@ export async function buildInstructions(repoRoot, stage, taskId) {
|
|
|
70
70
|
existingFiles: await listExistingFiles(taskDir),
|
|
71
71
|
writePolicy,
|
|
72
72
|
};
|
|
73
|
-
if (stage ===
|
|
73
|
+
if (stage === "source") {
|
|
74
74
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
75
75
|
...base,
|
|
76
76
|
outputPath: paths.sourceDir,
|
|
77
77
|
dependencies: [paths.taskConfigPath],
|
|
78
|
-
template: {
|
|
78
|
+
template: {
|
|
79
|
+
requiredHeadings: [
|
|
80
|
+
"# 需求",
|
|
81
|
+
"# 执行约束",
|
|
82
|
+
"## 目标",
|
|
83
|
+
"## 验收标准",
|
|
84
|
+
"## 非目标",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
79
87
|
completionCriteria: [
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
"source/references/ holds the immutable original PRD (via import-prd or materialize); do not let AI rewrite it",
|
|
89
|
+
"source/需求.md is a derived execution contract with objective, scope, non-goals, and acceptance criteria mapped back to the original PRD",
|
|
90
|
+
"source/需求.md uses stable REQ/AC ids or explicit source anchors when the original PRD is long",
|
|
91
|
+
"source/执行约束.md exists when path or behavioral constraints matter",
|
|
92
|
+
"AI assumptions that are not in the original PRD stay under open questions, never as fake requirements",
|
|
82
93
|
],
|
|
83
94
|
}));
|
|
84
95
|
}
|
|
85
|
-
if (stage ===
|
|
96
|
+
if (stage === "dag-draft") {
|
|
86
97
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
87
98
|
...base,
|
|
88
99
|
outputPath: defaultHybridDagOutputPath(taskId),
|
|
89
100
|
dependencies: [
|
|
90
101
|
paths.taskConfigPath,
|
|
91
|
-
path.join(paths.sourceDir,
|
|
102
|
+
path.join(paths.sourceDir, "需求.md"),
|
|
92
103
|
],
|
|
93
|
-
template: {
|
|
104
|
+
template: {
|
|
105
|
+
requiredHeadings: [
|
|
106
|
+
"version",
|
|
107
|
+
"title",
|
|
108
|
+
"objective",
|
|
109
|
+
"tasks",
|
|
110
|
+
"executorModels",
|
|
111
|
+
],
|
|
112
|
+
},
|
|
94
113
|
completionCriteria: [
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
"dag draft validates with dag validate --strict-models --strict-governance",
|
|
115
|
+
"writeSet is narrow and matches task constraints",
|
|
97
116
|
],
|
|
98
117
|
}));
|
|
99
118
|
}
|
|
100
|
-
if (stage ===
|
|
119
|
+
if (stage === "promotion") {
|
|
101
120
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
102
121
|
...base,
|
|
103
|
-
outputPath: path.join(taskDir,
|
|
122
|
+
outputPath: path.join(taskDir, "artifacts"),
|
|
104
123
|
dependencies: [
|
|
105
|
-
path.join(repoRoot,
|
|
106
|
-
path.join(repoRoot,
|
|
124
|
+
path.join(repoRoot, ".harness", "dag-runs", "completed"),
|
|
125
|
+
path.join(repoRoot, ".harness", "runs", "completed"),
|
|
107
126
|
],
|
|
108
|
-
template: { requiredHeadings: [
|
|
127
|
+
template: { requiredHeadings: ["# 修改记录", "# 验证结果"] },
|
|
109
128
|
completionCriteria: [
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
"completed run facts remain read-only",
|
|
130
|
+
"task artifacts summarize real run evidence without inventing verification",
|
|
112
131
|
],
|
|
113
132
|
}));
|
|
114
133
|
}
|
|
115
|
-
if (stage ===
|
|
134
|
+
if (stage === "closeout") {
|
|
116
135
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
117
136
|
...base,
|
|
118
|
-
outputPath: path.join(repoRoot,
|
|
137
|
+
outputPath: path.join(repoRoot, "docs", "progress", `${taskId}.md`),
|
|
119
138
|
dependencies: [
|
|
120
|
-
getArtifactPath(taskDir,
|
|
121
|
-
getArtifactPath(taskDir,
|
|
139
|
+
getArtifactPath(taskDir, "implement"),
|
|
140
|
+
getArtifactPath(taskDir, "verify"),
|
|
122
141
|
],
|
|
123
|
-
template: {
|
|
142
|
+
template: {
|
|
143
|
+
requiredHeadings: [
|
|
144
|
+
"# Progress",
|
|
145
|
+
"## Summary",
|
|
146
|
+
"## Verification",
|
|
147
|
+
"## Remaining Risk",
|
|
148
|
+
],
|
|
149
|
+
},
|
|
124
150
|
completionCriteria: [
|
|
125
|
-
|
|
126
|
-
|
|
151
|
+
"records commands actually run and their outcome",
|
|
152
|
+
"links remaining risks or follow-up tasks",
|
|
127
153
|
],
|
|
128
154
|
}));
|
|
129
155
|
}
|
|
130
|
-
const artifactSteps = [
|
|
156
|
+
const artifactSteps = [
|
|
157
|
+
"analyze",
|
|
158
|
+
"plan",
|
|
159
|
+
"implement",
|
|
160
|
+
"verify",
|
|
161
|
+
"retrospective",
|
|
162
|
+
];
|
|
131
163
|
return refreshInstructionBlockers(withMissingDependencies({
|
|
132
164
|
...base,
|
|
133
|
-
outputPath: path.join(taskDir,
|
|
165
|
+
outputPath: path.join(taskDir, "artifacts"),
|
|
134
166
|
dependencies: [paths.taskConfigPath, paths.statePath],
|
|
135
167
|
template: {
|
|
136
168
|
requiredHeadings: artifactSteps.map((step) => getArtifactRelativePath(step)),
|
|
137
169
|
},
|
|
138
170
|
completionCriteria: [
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
"artifact content is substantive and not the new-task template",
|
|
172
|
+
"verification artifact names exact commands and pass/fail results",
|
|
141
173
|
],
|
|
142
174
|
}));
|
|
143
175
|
}
|
|
@@ -153,7 +185,7 @@ async function listExistingFiles(root) {
|
|
|
153
185
|
const names = await readdir(root, { recursive: true });
|
|
154
186
|
return names
|
|
155
187
|
.map((name) => String(name))
|
|
156
|
-
.filter((name) => !name.includes(
|
|
188
|
+
.filter((name) => !name.includes("/logs/"))
|
|
157
189
|
.sort();
|
|
158
190
|
}
|
|
159
191
|
catch {
|
|
@@ -179,17 +211,17 @@ export async function refreshInstructionBlockers(doc) {
|
|
|
179
211
|
function formatInstructionsMarkdown(doc) {
|
|
180
212
|
const lines = [
|
|
181
213
|
`# Instructions: ${doc.artifactId}`,
|
|
182
|
-
|
|
214
|
+
"",
|
|
183
215
|
`- task: \`${doc.taskId}\``,
|
|
184
216
|
`- output: \`${doc.outputPath}\``,
|
|
185
217
|
`- write policy: \`${doc.writePolicy.mode}\``,
|
|
186
|
-
`- blocked: ${doc.blocked ?
|
|
187
|
-
|
|
188
|
-
|
|
218
|
+
`- blocked: ${doc.blocked ? "yes" : "no"}`,
|
|
219
|
+
"",
|
|
220
|
+
"## Dependencies",
|
|
189
221
|
...doc.dependencies.map((dep) => `- \`${dep}\``),
|
|
190
|
-
|
|
191
|
-
|
|
222
|
+
"",
|
|
223
|
+
"## Completion Criteria",
|
|
192
224
|
...doc.completionCriteria.map((criterion) => `- ${criterion}`),
|
|
193
225
|
];
|
|
194
|
-
return `${lines.join(
|
|
226
|
+
return `${lines.join("\n")}\n`;
|
|
195
227
|
}
|
|
@@ -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:`,
|
|
@@ -256,6 +256,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
|
|
|
256
256
|
step,
|
|
257
257
|
toolNames: resolveDagPiToolNames(input.task),
|
|
258
258
|
userMessage: buildDagPiUserMessage(input.task, persona, step),
|
|
259
|
+
sessionEventsPath: path.join(meta.runDir, input.task.id, "session-events.jsonl"),
|
|
259
260
|
});
|
|
260
261
|
const context = {
|
|
261
262
|
channel: "dag",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { classifyPiFailure, DEFAULT_TIMEOUT_MS, extractAssistantTextFromPiJson, extractTokenUsageFromPiJson, } from './pi-executor.js';
|
|
2
4
|
import { serializeSessionEvent } from './pi-event-serializer.js';
|
|
3
5
|
let sdkSessionFactoryOverride;
|
|
@@ -132,6 +134,51 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
132
134
|
});
|
|
133
135
|
return created.session;
|
|
134
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Throttle high-noise Pi SDK events from session-events.jsonl.
|
|
139
|
+
* Always keeps tool lifecycle, turn/agent end, assistant messages, and errors.
|
|
140
|
+
* Skips thinking_delta and message_update token floods.
|
|
141
|
+
*/
|
|
142
|
+
export function shouldPersistSessionEvent(event) {
|
|
143
|
+
const type = typeof event.type === 'string' ? event.type : '';
|
|
144
|
+
if (type === 'tool_start' || type === 'tool_end'
|
|
145
|
+
|| type === 'tool_execution_start' || type === 'tool_execution_end'
|
|
146
|
+
|| type === 'turn_end' || type === 'agent_end'
|
|
147
|
+
|| type === 'assistant_message' || type === 'message_end') {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (type === 'thinking_delta' || type === 'message_update') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (type.includes('error') || event.isError === true) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
function createSessionEventAppender(filePath, onSessionEvent) {
|
|
159
|
+
let chain = Promise.resolve();
|
|
160
|
+
let dirEnsured = false;
|
|
161
|
+
return {
|
|
162
|
+
append(line, event) {
|
|
163
|
+
chain = chain.then(async () => {
|
|
164
|
+
try {
|
|
165
|
+
if (!dirEnsured) {
|
|
166
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
167
|
+
dirEnsured = true;
|
|
168
|
+
}
|
|
169
|
+
await appendFile(filePath, `${line}\n`, 'utf-8');
|
|
170
|
+
onSessionEvent?.(line, event);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// best-effort: never fail the pi step
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
drain() {
|
|
178
|
+
return chain;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
135
182
|
async function resolveSdkSessionFactory(reuseScope) {
|
|
136
183
|
if (sdkSessionFactoryOverride)
|
|
137
184
|
return sdkSessionFactoryOverride;
|
|
@@ -168,6 +215,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
168
215
|
const stdoutLines = [];
|
|
169
216
|
let session;
|
|
170
217
|
let timeoutHandle;
|
|
218
|
+
const sessionEventAppender = options.sessionEventsPath
|
|
219
|
+
? createSessionEventAppender(options.sessionEventsPath, options.onSessionEvent)
|
|
220
|
+
: undefined;
|
|
171
221
|
try {
|
|
172
222
|
const createSession = await resolveSdkSessionFactory(options.reuseScope);
|
|
173
223
|
session = await createSession({
|
|
@@ -179,7 +229,11 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
179
229
|
thinking: modelConfig.thinking,
|
|
180
230
|
});
|
|
181
231
|
const unsubscribe = session.subscribe((event) => {
|
|
182
|
-
|
|
232
|
+
const line = serializeSessionEvent(event);
|
|
233
|
+
stdoutLines.push(line);
|
|
234
|
+
if (sessionEventAppender && shouldPersistSessionEvent(event)) {
|
|
235
|
+
sessionEventAppender.append(line, event);
|
|
236
|
+
}
|
|
183
237
|
});
|
|
184
238
|
const filePrefix = options.attachedFiles.map((file) => `@${file}`).join(' ');
|
|
185
239
|
const promptMessage = filePrefix
|
|
@@ -225,6 +279,14 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
225
279
|
stderr = stderr ? `${stderr}\n${message}` : message;
|
|
226
280
|
}
|
|
227
281
|
}
|
|
282
|
+
if (sessionEventAppender) {
|
|
283
|
+
try {
|
|
284
|
+
await sessionEventAppender.drain();
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// best-effort: never fail the pi step
|
|
288
|
+
}
|
|
289
|
+
}
|
|
228
290
|
}
|
|
229
291
|
const stdout = stdoutLines.join('\n');
|
|
230
292
|
const durationMs = Date.now() - startedAt;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const ENCODER = new TextEncoder();
|
|
2
|
+
const DECODER = new TextDecoder();
|
|
3
|
+
export function redactSecrets(text) {
|
|
4
|
+
return text
|
|
5
|
+
.replace(/\bBearer\s+[^\s"']+/gi, "Bearer ***")
|
|
6
|
+
.replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, "$1***")
|
|
7
|
+
.replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1***");
|
|
8
|
+
}
|
|
9
|
+
export function truncateUtf8Preview(text, maxBytes = 4096) {
|
|
10
|
+
if (maxBytes <= 0)
|
|
11
|
+
return "";
|
|
12
|
+
const bytes = ENCODER.encode(text);
|
|
13
|
+
if (bytes.length <= maxBytes)
|
|
14
|
+
return text;
|
|
15
|
+
let prefixEnd = safeUtf8End(bytes, Math.max(0, maxBytes - 32));
|
|
16
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
17
|
+
const omittedBytes = bytes.length - prefixEnd;
|
|
18
|
+
const suffix = `…<truncated ${omittedBytes} bytes>`;
|
|
19
|
+
const suffixBytes = ENCODER.encode(suffix);
|
|
20
|
+
if (suffixBytes.length >= maxBytes) {
|
|
21
|
+
const suffixEnd = safeUtf8End(suffixBytes, maxBytes);
|
|
22
|
+
return DECODER.decode(suffixBytes.subarray(0, suffixEnd));
|
|
23
|
+
}
|
|
24
|
+
const nextPrefixEnd = safeUtf8End(bytes, maxBytes - suffixBytes.length);
|
|
25
|
+
if (nextPrefixEnd === prefixEnd) {
|
|
26
|
+
return DECODER.decode(bytes.subarray(0, prefixEnd)) + suffix;
|
|
27
|
+
}
|
|
28
|
+
prefixEnd = nextPrefixEnd;
|
|
29
|
+
}
|
|
30
|
+
const omittedBytes = bytes.length - prefixEnd;
|
|
31
|
+
return `${DECODER.decode(bytes.subarray(0, prefixEnd))}…<truncated ${omittedBytes} bytes>`;
|
|
32
|
+
}
|
|
33
|
+
function safeUtf8End(bytes, budget) {
|
|
34
|
+
let end = Math.min(bytes.length, Math.max(0, budget));
|
|
35
|
+
while (end > 0 && end < bytes.length && (bytes[end] & 0xc0) === 0x80) {
|
|
36
|
+
end -= 1;
|
|
37
|
+
}
|
|
38
|
+
return end;
|
|
39
|
+
}
|
|
@@ -19,6 +19,7 @@ export const referenceDocConfigSchema = z.object({
|
|
|
19
19
|
path: z.string().min(1),
|
|
20
20
|
});
|
|
21
21
|
export const taskComplexitySchema = z.enum(["small", "medium", "large"]);
|
|
22
|
+
export const taskCapabilitySchema = z.enum(["interactive-ui"]);
|
|
22
23
|
export const contextProfileSchema = z.enum(["full", "slim"]);
|
|
23
24
|
export const piSubagentModeSchema = z.enum(["off", "analyze-plan", "full"]);
|
|
24
25
|
export const verifyModeSchema = z.enum(["parallel", "serial"]);
|
|
@@ -68,6 +69,8 @@ export const taskConfigSchema = z.object({
|
|
|
68
69
|
timeoutMs: z.number().int().positive().optional(),
|
|
69
70
|
flow: taskFlowSchema.optional().default("auto"),
|
|
70
71
|
complexity: taskComplexitySchema.optional().default("medium"),
|
|
72
|
+
/** Explicit delivery capabilities that affect writer contracts/model tier without changing risk. */
|
|
73
|
+
capabilities: z.array(taskCapabilitySchema).optional(),
|
|
71
74
|
verifyMode: verifyModeSchema.optional().default("parallel"),
|
|
72
75
|
verifyPreset: verifyPresetSchema.optional().default("auto"),
|
|
73
76
|
/** Verification selection policy. Intermediate loops may use quota; final gates still run full required verification. */
|
package/dist/task/runtime.js
CHANGED
|
@@ -236,35 +236,35 @@ export function shouldInjectSubagentGuidance(step, mode) {
|
|
|
236
236
|
return false;
|
|
237
237
|
}
|
|
238
238
|
/** Advisory guidance for `analyze-plan` mode. */
|
|
239
|
-
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
240
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
241
|
-
Use it only for read-only tasks:
|
|
242
|
-
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
243
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
244
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
245
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
246
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
247
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
239
|
+
export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
|
|
240
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
241
|
+
Use it only for read-only tasks:
|
|
242
|
+
- Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
|
|
243
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
244
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
245
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
246
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
247
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
248
248
|
</subagent_guidance>`;
|
|
249
249
|
/** Strong guidance for `full` mode — prescriptive when to delegate. */
|
|
250
|
-
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
251
|
-
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
252
|
-
|
|
253
|
-
You SHOULD delegate to subagent scouts when:
|
|
254
|
-
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
255
|
-
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
256
|
-
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
257
|
-
|
|
258
|
-
Delegation saves context tokens and produces better results.
|
|
259
|
-
|
|
260
|
-
Allowed patterns:
|
|
261
|
-
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
262
|
-
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
263
|
-
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
264
|
-
|
|
265
|
-
Do NOT use subagent for writing, editing, or executing commands.
|
|
266
|
-
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
267
|
-
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
250
|
+
export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
|
|
251
|
+
You have access to the \`subagent\` tool for lightweight delegation within this step.
|
|
252
|
+
|
|
253
|
+
You SHOULD delegate to subagent scouts when:
|
|
254
|
+
- The task requires scanning 3+ directories or comparing implementations across modules
|
|
255
|
+
- You would otherwise need 5+ sequential read/grep calls to gather context
|
|
256
|
+
- A reviewer subagent can independently catch scope drift before you finalize your output
|
|
257
|
+
|
|
258
|
+
Delegation saves context tokens and produces better results.
|
|
259
|
+
|
|
260
|
+
Allowed patterns:
|
|
261
|
+
- Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
|
|
262
|
+
- Chain: scout -> planner (one subagent scouts, another plans based on findings)
|
|
263
|
+
- Reviewer: have a subagent review your analysis/plan before finalizing
|
|
264
|
+
|
|
265
|
+
Do NOT use subagent for writing, editing, or executing commands.
|
|
266
|
+
Subagent output is advisory only; always verify and incorporate findings into your own output.
|
|
267
|
+
Do NOT treat subagent results as authoritative state or artifact sources.
|
|
268
268
|
</subagent_guidance>`;
|
|
269
269
|
/** Preserved for backward compatibility (alias of STANDARD). */
|
|
270
270
|
export const SUBAGENT_GUIDANCE = SUBAGENT_GUIDANCE_STANDARD;
|