@tea-agent/loop-agent 0.1.0 → 0.2.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/CHANGELOG.md +17 -1
- package/README.md +127 -92
- package/dist/adapters/index.js +3 -2
- package/dist/adapters/loop-agent.js +44 -2
- package/dist/application/dag/args.js +420 -0
- package/dist/application/dag/generate-task-dag.js +280 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +93 -0
- package/dist/application/dag/validate-dag.js +101 -0
- package/dist/application/loop/run-action.js +23 -0
- package/dist/cli/catalog.js +2 -237
- package/dist/cli/command-definitions.js +571 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/program.js +65 -1
- package/dist/cli/router.js +13 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/commands/dag-report.js +6 -107
- package/dist/commands/dag-run-task.js +8 -466
- package/dist/commands/dag-validate.js +7 -179
- package/dist/commands/examples.js +90 -0
- package/dist/commands/init.js +1495 -0
- package/dist/commands/loop.js +57 -31
- package/dist/commands/pi-prompt.js +2 -9
- package/dist/commands/run-dag.js +7 -180
- package/dist/executors/cursor-executor-artifacts.js +3 -4
- package/dist/executors/cursor-worker-client.js +13 -3
- package/dist/executors/dag-cursor-executor.js +2 -3
- package/dist/executors/dag-pi-executor.js +3 -4
- package/dist/executors/dag-static-executor.js +2 -5
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/shell-executor.js +12 -20
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +49 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +23 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +77 -0
- package/dist/records/one-shot-runs.js +26 -61
- package/dist/records/promotion.js +3 -4
- package/dist/shared/artifacts-core.js +5 -5
- package/dist/shared/logger.js +9 -15
- package/dist/task/delegate.js +4 -4
- package/dist/task/runtime.js +5 -7
- package/dist/task/state.js +6 -20
- package/dist/workflows/dag/convergence/controller.js +277 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +156 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +185 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +133 -0
- package/dist/workflows/dag/lifecycle.js +6 -5
- package/dist/workflows/dag/node-execution.js +262 -0
- package/dist/workflows/dag/run-store.js +36 -0
- package/dist/workflows/dag/runner.js +82 -1341
- package/dist/workflows/dag/scheduler.js +84 -0
- package/dist/workflows/dag/upstream-artifacts.js +20 -18
- package/dist/workflows/loop/actions/cursor-fix.js +191 -0
- package/dist/workflows/loop/actions/dag-action.js +130 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +55 -1212
- package/dist/workflows/loop/closeout.js +5 -4
- package/dist/workflows/loop/context.js +2 -3
- package/dist/workflows/loop/events.js +3 -2
- package/dist/workflows/loop/policy/auto-policy.js +104 -0
- package/dist/workflows/loop/policy/cursor-fix-policy.js +31 -0
- package/dist/workflows/loop/rounds.js +3 -3
- package/dist/workflows/loop/signals.js +4 -7
- package/dist/workflows/loop/state.js +11 -11
- package/docs/README.md +3 -2
- package/docs/architecture/runtime-boundaries.md +147 -0
- package/docs/exec-plans/active/README.md +4 -0
- package/docs/exec-plans/completed/README.md +6 -2
- package/package.json +2 -1
- package/skills/ai-engineering-context/SKILL.md +21 -21
- package/skills/loop-agent/SKILL.md +73 -188
- package/skills/loop-agent/references/README.md +6 -2
- package/skills/loop-agent/references/harness-policy.md +113 -113
- package/skills/loop-agent/references/learned/README.md +13 -13
- package/skills/loop-agent/references/long-running-loop.md +59 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +0 -2
- package/skills/loop-agent/references/verification-and-failure-handling.md +18 -0
- package/skills/requesting-code-review/SKILL.md +40 -40
- package/skills/requesting-code-review/code-reviewer.md +4 -4
- package/skills/systematic-debugging/CREATION-LOG.md +43 -43
- package/skills/systematic-debugging/SKILL.md +113 -113
- package/skills/systematic-debugging/condition-based-waiting.md +20 -20
- package/skills/systematic-debugging/defense-in-depth.md +27 -27
- package/skills/systematic-debugging/root-cause-tracing.md +38 -38
- package/skills/systematic-debugging/test-academic.md +6 -6
- package/skills/systematic-debugging/test-pressure-1.md +6 -6
- package/skills/systematic-debugging/test-pressure-2.md +2 -2
- package/skills/systematic-debugging/test-pressure-3.md +6 -6
- package/skills/verification-before-completion/SKILL.md +37 -37
package/dist/commands/loop.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { appendLoopSignal, appendLoopRound, generateLoopCloseout, initializeLoop, loadLoopState, parseLoopSignalType, rewriteLoopContext, runLoopAuto,
|
|
1
|
+
import { appendLoopSignal, appendLoopRound, generateLoopCloseout, initializeLoop, loadLoopState, parseLoopSignalType, rewriteLoopContext, runLoopAuto, } from "../workflows/loop/index.js";
|
|
2
|
+
import { runLoopAction } from "../application/loop/run-action.js";
|
|
2
3
|
function usage() {
|
|
3
4
|
return [
|
|
4
5
|
"usage: loop <init|status|run|record-round|add-signal|closeout> <task-id> [options]",
|
|
@@ -159,20 +160,30 @@ export async function runLoop(repoRoot, args) {
|
|
|
159
160
|
timeoutMs = parsed;
|
|
160
161
|
}
|
|
161
162
|
if (action === "pi-review") {
|
|
162
|
-
const record = await
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
const record = await runLoopAction({
|
|
164
|
+
action,
|
|
165
|
+
repoRoot,
|
|
166
|
+
taskId,
|
|
167
|
+
options: {
|
|
168
|
+
timeoutMs,
|
|
169
|
+
cwd: readFlag(rest, "--cwd"),
|
|
170
|
+
provider: readFlag(rest, "--provider"),
|
|
171
|
+
model: readFlag(rest, "--model"),
|
|
172
|
+
},
|
|
167
173
|
});
|
|
168
174
|
console.log(JSON.stringify(record, null, 2));
|
|
169
175
|
return;
|
|
170
176
|
}
|
|
171
177
|
if (action === "cursor-fix") {
|
|
172
|
-
const record = await
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
const record = await runLoopAction({
|
|
179
|
+
action,
|
|
180
|
+
repoRoot,
|
|
181
|
+
taskId,
|
|
182
|
+
options: {
|
|
183
|
+
timeoutMs,
|
|
184
|
+
cwd: readFlag(rest, "--cwd"),
|
|
185
|
+
model: readFlag(rest, "--model"),
|
|
186
|
+
},
|
|
176
187
|
});
|
|
177
188
|
console.log(JSON.stringify(record, null, 2));
|
|
178
189
|
return;
|
|
@@ -187,13 +198,18 @@ export async function runLoop(repoRoot, args) {
|
|
|
187
198
|
}
|
|
188
199
|
maxConcurrent = parsed;
|
|
189
200
|
}
|
|
190
|
-
const record = await
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
const record = await runLoopAction({
|
|
202
|
+
action,
|
|
203
|
+
repoRoot,
|
|
204
|
+
taskId,
|
|
205
|
+
options: {
|
|
206
|
+
mode: rest.includes("--execute") ? "execute" : "review",
|
|
207
|
+
cwd: readFlag(rest, "--cwd"),
|
|
208
|
+
dagPath: readFlag(rest, "--dag"),
|
|
209
|
+
runId: readFlag(rest, "--run-id"),
|
|
210
|
+
maxConcurrent,
|
|
211
|
+
noCursor: rest.includes("--no-cursor"),
|
|
212
|
+
},
|
|
197
213
|
});
|
|
198
214
|
console.log(JSON.stringify(record, null, 2));
|
|
199
215
|
return;
|
|
@@ -212,16 +228,21 @@ export async function runLoop(repoRoot, args) {
|
|
|
212
228
|
if (profile && profile !== "pr-review") {
|
|
213
229
|
throw new Error(`unsupported workflow profile: ${profile}`);
|
|
214
230
|
}
|
|
215
|
-
const record = await
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
231
|
+
const record = await runLoopAction({
|
|
232
|
+
action,
|
|
233
|
+
repoRoot,
|
|
234
|
+
taskId,
|
|
235
|
+
options: {
|
|
236
|
+
mode: rest.includes("--execute") ? "execute" : "review",
|
|
237
|
+
cwd: readFlag(rest, "--cwd"),
|
|
238
|
+
profile: profile,
|
|
239
|
+
workflowPath: readFlag(rest, "--workflow"),
|
|
240
|
+
runId: readFlag(rest, "--run-id"),
|
|
241
|
+
maxConcurrent,
|
|
242
|
+
base: readFlag(rest, "--base"),
|
|
243
|
+
head: readFlag(rest, "--head"),
|
|
244
|
+
changedFiles: readRepeatedFlag(rest, "--changed-file"),
|
|
245
|
+
},
|
|
225
246
|
});
|
|
226
247
|
console.log(JSON.stringify(record, null, 2));
|
|
227
248
|
return;
|
|
@@ -229,10 +250,15 @@ export async function runLoop(repoRoot, args) {
|
|
|
229
250
|
if (action !== "shell-verify") {
|
|
230
251
|
throw new Error("loop run currently supports --action shell-verify|pi-review|cursor-fix|dag|workflow");
|
|
231
252
|
}
|
|
232
|
-
const record = await
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
253
|
+
const record = await runLoopAction({
|
|
254
|
+
action: "shell-verify",
|
|
255
|
+
repoRoot,
|
|
256
|
+
taskId,
|
|
257
|
+
options: {
|
|
258
|
+
commands: readRepeatedFlag(rest, "--command"),
|
|
259
|
+
timeoutMs,
|
|
260
|
+
cwd: readFlag(rest, "--cwd"),
|
|
261
|
+
},
|
|
236
262
|
});
|
|
237
263
|
console.log(JSON.stringify(record, null, 2));
|
|
238
264
|
return;
|
|
@@ -9,17 +9,10 @@
|
|
|
9
9
|
* pi-prompt --cwd /path --timeout 120000 "task"
|
|
10
10
|
*/
|
|
11
11
|
import { DEFAULT_TIMEOUT_MS } from "../executors/pi-executor.js";
|
|
12
|
+
import { DEFAULT_PI_MODEL, DEFAULT_PI_PROVIDER, DEFAULT_PI_TOOLS, } from "../executors/pi-defaults.js";
|
|
12
13
|
import { createOneShotPromptParseAccum, formatUnknownFlagError, parseOneShotCommonFlag, printOneShotHelpAndExit, resolveOneShotCwd, resolveOneShotTask, } from "../shared/one-shot-prompt-args.js";
|
|
13
14
|
import { executeSingleSdkAttempt } from "../executors/pi-sdk-executor.js";
|
|
14
|
-
export
|
|
15
|
-
export const DEFAULT_PI_MODEL = "glm-5.2";
|
|
16
|
-
export const DEFAULT_PI_TOOLS = [
|
|
17
|
-
"read",
|
|
18
|
-
"bash",
|
|
19
|
-
"ast_grep_search",
|
|
20
|
-
"lsp_diagnostics",
|
|
21
|
-
"subagent",
|
|
22
|
-
];
|
|
15
|
+
export { DEFAULT_PI_MODEL, DEFAULT_PI_PROVIDER, DEFAULT_PI_TOOLS };
|
|
23
16
|
const PI_PROMPT_USAGE = [
|
|
24
17
|
"usage: pi-prompt [options] [task]",
|
|
25
18
|
"",
|
package/dist/commands/run-dag.js
CHANGED
|
@@ -1,184 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import { loadDagSpecFromFile, runDag } from "../workflows/dag/runner.js";
|
|
5
|
-
import { assertValidDagSpec, collectForbiddenExecutorIssues, } from "../workflows/dag/validate.js";
|
|
6
|
-
export function parseRunDagArgs(args, defaultCwd) {
|
|
7
|
-
if (args.length === 0) {
|
|
8
|
-
throw new Error("usage: run-dag --dag <path> [--cwd <dir>] [--init-only] [--dry-run] [--max-concurrent N] [--run-id id] [--canvas-path <abs-path> | --canvas <name> [--canvases-dir <dir>]]");
|
|
9
|
-
}
|
|
10
|
-
let dagPath;
|
|
11
|
-
let cwd;
|
|
12
|
-
let initOnly = false;
|
|
13
|
-
let dryRun = false;
|
|
14
|
-
let maxConcurrent;
|
|
15
|
-
let runId;
|
|
16
|
-
let noCursor = false;
|
|
17
|
-
let canvasPath;
|
|
18
|
-
let canvasName;
|
|
19
|
-
let canvasesDir;
|
|
20
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
21
|
-
const arg = args[i];
|
|
22
|
-
if (arg === "--dag") {
|
|
23
|
-
dagPath = args[++i];
|
|
24
|
-
}
|
|
25
|
-
else if (arg.startsWith("--dag=")) {
|
|
26
|
-
dagPath = arg.slice(6);
|
|
27
|
-
}
|
|
28
|
-
else if (arg === "--cwd" || arg === "-C") {
|
|
29
|
-
cwd = args[++i];
|
|
30
|
-
}
|
|
31
|
-
else if (arg.startsWith("--cwd=")) {
|
|
32
|
-
cwd = arg.slice(6);
|
|
33
|
-
}
|
|
34
|
-
else if (arg === "--init-only") {
|
|
35
|
-
initOnly = true;
|
|
36
|
-
}
|
|
37
|
-
else if (arg === "--dry-run") {
|
|
38
|
-
dryRun = true;
|
|
39
|
-
}
|
|
40
|
-
else if (arg === "--max-concurrent") {
|
|
41
|
-
maxConcurrent = Number(args[++i]);
|
|
42
|
-
}
|
|
43
|
-
else if (arg === "--run-id") {
|
|
44
|
-
runId = args[++i];
|
|
45
|
-
}
|
|
46
|
-
else if (arg === "--no-cursor") {
|
|
47
|
-
noCursor = true;
|
|
48
|
-
}
|
|
49
|
-
else if (arg === "--canvas-path") {
|
|
50
|
-
canvasPath = args[++i];
|
|
51
|
-
}
|
|
52
|
-
else if (arg.startsWith("--canvas-path=")) {
|
|
53
|
-
canvasPath = arg.slice(14);
|
|
54
|
-
}
|
|
55
|
-
else if (arg === "--canvas") {
|
|
56
|
-
canvasName = args[++i];
|
|
57
|
-
}
|
|
58
|
-
else if (arg.startsWith("--canvas=")) {
|
|
59
|
-
canvasName = arg.slice(9);
|
|
60
|
-
}
|
|
61
|
-
else if (arg === "--canvases-dir") {
|
|
62
|
-
canvasesDir = args[++i];
|
|
63
|
-
}
|
|
64
|
-
else if (arg.startsWith("--canvases-dir=")) {
|
|
65
|
-
canvasesDir = arg.slice(15);
|
|
66
|
-
}
|
|
67
|
-
else if (!arg.startsWith("-")) {
|
|
68
|
-
if (!dagPath)
|
|
69
|
-
dagPath = arg;
|
|
70
|
-
else
|
|
71
|
-
throw new Error(`unexpected positional argument: ${arg}`);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
throw new Error(`unknown run-dag flag: ${arg}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (!dagPath) {
|
|
78
|
-
throw new Error("run-dag requires --dag <path>");
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
dagPath: path.resolve(dagPath),
|
|
82
|
-
cwd: path.resolve(cwd ?? defaultCwd ?? process.cwd()),
|
|
83
|
-
initOnly,
|
|
84
|
-
dryRun,
|
|
85
|
-
maxConcurrent,
|
|
86
|
-
runId,
|
|
87
|
-
noCursor,
|
|
88
|
-
canvasPath,
|
|
89
|
-
canvasName,
|
|
90
|
-
canvasesDir,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
async function loadRawTaskExecutorMap(dagPath) {
|
|
94
|
-
const raw = JSON.parse(await readFile(dagPath, "utf-8"));
|
|
95
|
-
const map = new Map();
|
|
96
|
-
for (const task of raw.tasks ?? []) {
|
|
97
|
-
if (typeof task.id === "string")
|
|
98
|
-
map.set(task.id, task.executor);
|
|
99
|
-
}
|
|
100
|
-
return map;
|
|
101
|
-
}
|
|
102
|
-
function assertNoCursorTasks(spec, rawTaskExecutors) {
|
|
103
|
-
const issues = collectForbiddenExecutorIssues(spec, ["cursor"]);
|
|
104
|
-
if (issues.length === 0)
|
|
105
|
-
return;
|
|
106
|
-
const details = issues
|
|
107
|
-
.map((issue) => {
|
|
108
|
-
const implicitDefault = rawTaskExecutors.get(issue.taskId) === undefined;
|
|
109
|
-
return `task=${issue.taskId}${implicitDefault ? " (implicit default)" : ""}`;
|
|
110
|
-
})
|
|
111
|
-
.join("; ");
|
|
112
|
-
throw new Error(`--no-cursor forbids cursor executor tasks: ${details}`);
|
|
113
|
-
}
|
|
114
|
-
function getCanvasFlushErrorMessage(error) {
|
|
115
|
-
return error instanceof Error ? error.message : String(error);
|
|
116
|
-
}
|
|
117
|
-
async function flushCanvasSafely(canvas) {
|
|
118
|
-
if (!canvas)
|
|
119
|
-
return undefined;
|
|
120
|
-
try {
|
|
121
|
-
await canvas.flush();
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
// Canvas is a derived view; failed writes must not decide canonical DAG success.
|
|
126
|
-
return getCanvasFlushErrorMessage(error);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
1
|
+
import { parseRunDagArgs } from "../application/dag/args.js";
|
|
2
|
+
import { runDagUseCase } from "../application/dag/run-dag.js";
|
|
3
|
+
export { parseRunDagArgs };
|
|
129
4
|
export async function runRunDag(repoRoot, rawArgs) {
|
|
130
5
|
const parsed = parseRunDagArgs(rawArgs, repoRoot);
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
assertNoCursorTasks(spec, rawTaskExecutors);
|
|
135
|
-
}
|
|
136
|
-
assertValidDagSpec(spec);
|
|
137
|
-
const resolvedCanvasPath = resolveCanvasPath({
|
|
138
|
-
canvasPath: parsed.canvasPath,
|
|
139
|
-
canvasName: parsed.canvasName,
|
|
140
|
-
canvasesDir: parsed.canvasesDir,
|
|
141
|
-
cwd: parsed.cwd,
|
|
142
|
-
});
|
|
143
|
-
const canvas = resolvedCanvasPath
|
|
144
|
-
? createDagCanvasObserver({ canvasPath: resolvedCanvasPath, spec })
|
|
145
|
-
: undefined;
|
|
146
|
-
if (parsed.dryRun) {
|
|
147
|
-
const summary = await runDag(spec, {
|
|
148
|
-
cwd: parsed.cwd,
|
|
149
|
-
dryRun: true,
|
|
150
|
-
runId: parsed.runId,
|
|
151
|
-
observer: canvas?.observer,
|
|
152
|
-
});
|
|
153
|
-
const canvasError = await flushCanvasSafely(canvas);
|
|
154
|
-
console.log(JSON.stringify({
|
|
155
|
-
mode: "dry-run",
|
|
156
|
-
title: spec.title,
|
|
157
|
-
taskCount: spec.tasks.length,
|
|
158
|
-
ranks: summary.ranks,
|
|
159
|
-
runId: summary.runId,
|
|
160
|
-
runDir: summary.runDir,
|
|
161
|
-
...(resolvedCanvasPath ? { canvasPath: resolvedCanvasPath } : {}),
|
|
162
|
-
...(canvasError ? { canvasError } : {}),
|
|
163
|
-
}, null, 2));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const maxConcurrent = parsed.maxConcurrent;
|
|
167
|
-
const summary = await runDag(spec, {
|
|
168
|
-
cwd: parsed.cwd,
|
|
169
|
-
initOnly: parsed.initOnly,
|
|
170
|
-
dryRun: false,
|
|
171
|
-
maxConcurrent,
|
|
172
|
-
runId: parsed.runId,
|
|
173
|
-
observer: canvas?.observer,
|
|
6
|
+
const result = await runDagUseCase({
|
|
7
|
+
repoRoot,
|
|
8
|
+
...parsed,
|
|
174
9
|
});
|
|
175
|
-
|
|
176
|
-
const payload = resolvedCanvasPath
|
|
177
|
-
? {
|
|
178
|
-
...summary,
|
|
179
|
-
canvasPath: resolvedCanvasPath,
|
|
180
|
-
...(canvasError ? { canvasError } : {}),
|
|
181
|
-
}
|
|
182
|
-
: summary;
|
|
183
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
10
|
+
console.log(JSON.stringify(result, null, 2));
|
|
184
11
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { writeTextArtifactFile } from "../infrastructure/harness/artifact-store.js";
|
|
3
3
|
import { redactPromptForLog, truncateOutput, } from "./cursor-executor.js";
|
|
4
4
|
import { formatCursorFailureMessage, } from "./cursor-execution-log.js";
|
|
5
5
|
const SUMMARY_STDOUT_MAX = 4_000;
|
|
@@ -126,10 +126,9 @@ export function buildResultSummaryMarkdown(input) {
|
|
|
126
126
|
return meta.join("\n");
|
|
127
127
|
}
|
|
128
128
|
export async function writeCursorExecutorArtifacts(artifactsDir, input) {
|
|
129
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
130
129
|
const promptPath = path.join(artifactsDir, "prompt.redacted.md");
|
|
131
130
|
const summaryPath = path.join(artifactsDir, "result.summary.md");
|
|
132
|
-
await
|
|
133
|
-
await
|
|
131
|
+
await writeTextArtifactFile(promptPath, buildPromptRedactedMarkdown(input.prompt));
|
|
132
|
+
await writeTextArtifactFile(summaryPath, buildResultSummaryMarkdown(input));
|
|
134
133
|
return { promptPath, summaryPath };
|
|
135
134
|
}
|
|
@@ -3,8 +3,9 @@ import { createInterface } from 'node:readline';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { DEFAULT_CURSOR_TIMEOUT_MS, } from './cursor-executor.js';
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const WORKER_MODULE_PATH = fileURLToPath(import.meta.url);
|
|
7
|
+
const WORKER_MODULE_DIR = path.dirname(WORKER_MODULE_PATH);
|
|
8
|
+
const WORKER_ENTRY = resolveCursorWorkerEntryPath(WORKER_MODULE_PATH);
|
|
8
9
|
let workerProcess = null;
|
|
9
10
|
let workerBoot = null;
|
|
10
11
|
const pending = new Map();
|
|
@@ -37,8 +38,17 @@ export function shouldUseCursorWorker(options) {
|
|
|
37
38
|
return true;
|
|
38
39
|
return isCursorWorkerEnabled();
|
|
39
40
|
}
|
|
41
|
+
export function resolveCursorWorkerEntryPath(modulePath) {
|
|
42
|
+
const extension = path.extname(modulePath) === '.ts' ? '.ts' : '.js';
|
|
43
|
+
return path.join(path.dirname(modulePath), `../cursor-worker-entry${extension}`);
|
|
44
|
+
}
|
|
45
|
+
export function buildCursorWorkerScriptArgs(entryPath) {
|
|
46
|
+
return path.extname(entryPath) === '.ts'
|
|
47
|
+
? ['--import', 'tsx/esm', entryPath]
|
|
48
|
+
: [entryPath];
|
|
49
|
+
}
|
|
40
50
|
function workerScriptArgs() {
|
|
41
|
-
return
|
|
51
|
+
return buildCursorWorkerScriptArgs(WORKER_ENTRY);
|
|
42
52
|
}
|
|
43
53
|
export function getCursorWorkerEntryPath() {
|
|
44
54
|
return WORKER_ENTRY;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { ensureDagNodeArtifactDir } from "../infrastructure/harness/artifact-store.js";
|
|
3
3
|
import { executeCursorTask } from "./cursor-executor.js";
|
|
4
4
|
import { writeCursorExecutorArtifacts } from "./cursor-executor-artifacts.js";
|
|
5
5
|
import { buildCursorExecutionLogRecord, persistCursorExecutionLog, } from "./cursor-execution-log.js";
|
|
@@ -14,8 +14,7 @@ export async function executeDagCursorNode(input, meta) {
|
|
|
14
14
|
// Non-git cwd: skip write guard (documented limitation).
|
|
15
15
|
beforeStatus = undefined;
|
|
16
16
|
}
|
|
17
|
-
const artifactDir =
|
|
18
|
-
await mkdir(artifactDir, { recursive: true });
|
|
17
|
+
const artifactDir = await ensureDagNodeArtifactDir(meta.runDir, input.task.id);
|
|
19
18
|
const result = await executeCursorTask({
|
|
20
19
|
task: input.prompt,
|
|
21
20
|
cwd: input.cwd,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { writeTextArtifactFile } from "../infrastructure/harness/artifact-store.js";
|
|
3
3
|
import { executePiStep, } from "./pi-executor.js";
|
|
4
4
|
import { redactPromptForLog, truncateOutput } from "./cursor-executor.js";
|
|
5
5
|
import { readGitStatusPorcelain, runPostRunWriteGuard, } from "./shell-write-guard.js";
|
|
@@ -211,11 +211,10 @@ export function buildPiResultSummaryMarkdown(input) {
|
|
|
211
211
|
return lines.join("\n");
|
|
212
212
|
}
|
|
213
213
|
export async function writePiExecutorArtifacts(artifactsDir, input) {
|
|
214
|
-
await mkdir(artifactsDir, { recursive: true });
|
|
215
214
|
const promptPath = path.join(artifactsDir, "prompt.redacted.md");
|
|
216
215
|
const summaryPath = path.join(artifactsDir, "result.summary.md");
|
|
217
|
-
await
|
|
218
|
-
await
|
|
216
|
+
await writeTextArtifactFile(promptPath, buildPiPromptRedactedMarkdown(input.prompt));
|
|
217
|
+
await writeTextArtifactFile(summaryPath, buildPiResultSummaryMarkdown(input));
|
|
219
218
|
return { promptPath, summaryPath };
|
|
220
219
|
}
|
|
221
220
|
export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
1
|
+
import { writeDagNodeTextArtifact } from "../infrastructure/harness/artifact-store.js";
|
|
3
2
|
export function buildStaticResultSummaryMarkdown(input) {
|
|
4
3
|
const lines = [
|
|
5
4
|
"# Static execution summary",
|
|
@@ -26,15 +25,13 @@ export async function executeDagStaticNode(input, meta) {
|
|
|
26
25
|
}
|
|
27
26
|
const status = staticConfig.status ?? "success";
|
|
28
27
|
const started = Date.now();
|
|
29
|
-
const nodeDir = path.join(meta.runDir, input.task.id);
|
|
30
|
-
await mkdir(nodeDir, { recursive: true });
|
|
31
28
|
const summary = buildStaticResultSummaryMarkdown({
|
|
32
29
|
nodeId: input.task.id,
|
|
33
30
|
runId: meta.runId,
|
|
34
31
|
resultMarkdown: staticConfig.resultMarkdown,
|
|
35
32
|
status,
|
|
36
33
|
});
|
|
37
|
-
await
|
|
34
|
+
await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", summary);
|
|
38
35
|
return {
|
|
39
36
|
ok: status === "success",
|
|
40
37
|
stdout: staticConfig.resultMarkdown,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { writeDagNodeTextArtifact } from "../infrastructure/harness/artifact-store.js";
|
|
5
5
|
import { truncateOutput } from "./cursor-executor.js";
|
|
6
6
|
import { expandShellPreset, buildVerdictGateShellCommand } from "./shell-presets.js";
|
|
7
7
|
import { pathsChangedDuringRun, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
|
|
@@ -12,23 +12,17 @@ const SUMMARY_STDERR_MAX = 2_000;
|
|
|
12
12
|
const NODE_STDOUT_MAX = 12_000;
|
|
13
13
|
const NODE_STDERR_MAX = 8_000;
|
|
14
14
|
function resolveBashExecutable() {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const envCandidate = process.env.LOOP_AGENT_BASH ?? process.env.GIT_BASH;
|
|
16
|
+
if (envCandidate && existsSync(envCandidate))
|
|
17
|
+
return envCandidate;
|
|
17
18
|
if (process.platform !== "win32")
|
|
18
|
-
return "bash";
|
|
19
|
+
return envCandidate || "bash";
|
|
19
20
|
const candidates = [
|
|
20
|
-
process.env.
|
|
21
|
-
process.env.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
? path.join(process.env["ProgramFiles(x86)"], "Git", "bin", "bash.exe")
|
|
26
|
-
: undefined,
|
|
27
|
-
process.env.LocalAppData
|
|
28
|
-
? path.join(process.env.LocalAppData, "Programs", "Git", "bin", "bash.exe")
|
|
29
|
-
: undefined,
|
|
30
|
-
];
|
|
31
|
-
return candidates.find((candidate) => candidate && existsSync(candidate)) ?? "bash";
|
|
21
|
+
path.join(process.env.ProgramFiles ?? "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
22
|
+
path.join(process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)", "Git", "bin", "bash.exe"),
|
|
23
|
+
path.join(process.env.LocalAppData ?? "", "Programs", "Git", "bin", "bash.exe"),
|
|
24
|
+
].filter(Boolean);
|
|
25
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? envCandidate ?? "bash";
|
|
32
26
|
}
|
|
33
27
|
function isWithinRoot(root, candidate) {
|
|
34
28
|
const relative = path.relative(root, candidate);
|
|
@@ -272,15 +266,13 @@ export async function executeDagShellNode(input, meta) {
|
|
|
272
266
|
const commandsOk = results.every((result) => result.ok);
|
|
273
267
|
const ok = commandsOk && writeGuardOk;
|
|
274
268
|
const firstFailure = results.find((result) => !result.ok);
|
|
275
|
-
|
|
276
|
-
await mkdir(nodeDir, { recursive: true });
|
|
277
|
-
await writeFile(path.join(nodeDir, "result.summary.md"), buildShellResultSummaryMarkdown({
|
|
269
|
+
await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", buildShellResultSummaryMarkdown({
|
|
278
270
|
nodeId: input.task.id,
|
|
279
271
|
runId: meta.runId,
|
|
280
272
|
rootCwd: input.cwd,
|
|
281
273
|
results,
|
|
282
274
|
writeGuardViolations: writeGuardOk ? undefined : writeGuardViolations,
|
|
283
|
-
})
|
|
275
|
+
}));
|
|
284
276
|
const aggregate = summarizeCommandResults(results);
|
|
285
277
|
const stderrParts = [aggregate.stderr];
|
|
286
278
|
if (!writeGuardOk) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export const DAG_ACTIVE_DIR = ".harness/dag-runs/active";
|
|
3
|
+
export const TOOL_ACTIVE_DIR = ".harness/runs/active";
|
|
4
|
+
function filterRuntimeEntries(entries) {
|
|
5
|
+
return entries
|
|
6
|
+
.map((entry) => entry.trim())
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.filter((entry) => path.basename(entry) !== ".gitkeep");
|
|
9
|
+
}
|
|
10
|
+
export function evaluateActiveResidue(input) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const dagEntries = filterRuntimeEntries(input.dagActiveEntries);
|
|
13
|
+
if (dagEntries.length > 0) {
|
|
14
|
+
findings.push({
|
|
15
|
+
allowEnv: "HARNESS_ALLOW_ACTIVE_DAG_RUNS",
|
|
16
|
+
dir: DAG_ACTIVE_DIR,
|
|
17
|
+
entries: dagEntries,
|
|
18
|
+
kind: "dag-active",
|
|
19
|
+
message: "[HARNESS RUNTIME DIRTY] .harness/dag-runs/active contains runtime entries:",
|
|
20
|
+
remediation: [
|
|
21
|
+
"",
|
|
22
|
+
".harness/dag-runs/active is a transient runtime area and should be empty at governance check time.",
|
|
23
|
+
"Completed DAG runs must be archived by run-dag to .harness/dag-runs/completed/<run-id>/.",
|
|
24
|
+
"Paused DAG runs (decision gate requiresHuman) live under .harness/dag-runs/paused/<run-id>/ and are gitignored runtime state.",
|
|
25
|
+
"Dry-run/init-only/stale active directories must be removed after review.",
|
|
26
|
+
"",
|
|
27
|
+
"If a live DAG is intentionally running while this check executes, rerun with:",
|
|
28
|
+
" HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-harness-runtime-clean.sh",
|
|
29
|
+
].join("\n"),
|
|
30
|
+
severity: "error",
|
|
31
|
+
strictEnv: null,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const toolEntries = filterRuntimeEntries(input.toolActiveEntries);
|
|
35
|
+
if (toolEntries.length > 0) {
|
|
36
|
+
findings.push({
|
|
37
|
+
allowEnv: "HARNESS_ALLOW_ACTIVE_TOOL_RUNS",
|
|
38
|
+
dir: TOOL_ACTIVE_DIR,
|
|
39
|
+
entries: toolEntries,
|
|
40
|
+
kind: "tool-active",
|
|
41
|
+
message: "[HARNESS RUNTIME WARNING] .harness/runs/active contains runtime entries:",
|
|
42
|
+
remediation: [
|
|
43
|
+
"",
|
|
44
|
+
".harness/runs/active is for live one-shot tool runs only.",
|
|
45
|
+
"Completed, failed, or stale one-shot run evidence must be moved or explicitly reconciled.",
|
|
46
|
+
"",
|
|
47
|
+
"If a live one-shot run is intentionally active while this check executes, rerun with:",
|
|
48
|
+
" HARNESS_ALLOW_ACTIVE_TOOL_RUNS=1 bash scripts/check-harness-runtime-clean.sh",
|
|
49
|
+
].join("\n"),
|
|
50
|
+
severity: "warning",
|
|
51
|
+
strictEnv: "HARNESS_STRICT_ACTIVE_TOOL_RUNS",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
}
|
|
56
|
+
export function shouldFailActiveResidue(findings, env) {
|
|
57
|
+
const blocking = findings.filter((finding) => {
|
|
58
|
+
if (env[finding.allowEnv] === "1")
|
|
59
|
+
return false;
|
|
60
|
+
if (finding.kind === "dag-active")
|
|
61
|
+
return true;
|
|
62
|
+
return finding.strictEnv ? env[finding.strictEnv] === "1" : false;
|
|
63
|
+
});
|
|
64
|
+
return { blocking, fail: blocking.length > 0 };
|
|
65
|
+
}
|
|
66
|
+
export function formatActiveResidueFinding(finding) {
|
|
67
|
+
const lines = [
|
|
68
|
+
finding.message,
|
|
69
|
+
...finding.entries.map((entry) => ` - ${entry}`),
|
|
70
|
+
finding.remediation,
|
|
71
|
+
];
|
|
72
|
+
return `${lines.join("\n")}\n`;
|
|
73
|
+
}
|