@tea-agent/loop-agent 0.10.0 → 0.12.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 +10 -2
- package/CHANGELOG.md +91 -24
- package/README.md +84 -12
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +38 -2
- package/dist/application/dag/run-dag.js +11 -27
- package/dist/application/dag/validate-dag.js +13 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +547 -95
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +488 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +29 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +32 -3
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/read-model.js +338 -83
- package/dist/worker/observe/paths.js +17 -0
- package/dist/worker/observe/routes.js +165 -21
- package/dist/worker/observe/server.js +59 -1
- package/dist/worker/observe/static/api.js +27 -0
- package/dist/worker/observe/static/app.js +120 -2317
- package/dist/worker/observe/static/constants.js +148 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/dag-helpers.js +172 -0
- package/dist/worker/observe/static/dag-model.js +72 -0
- package/dist/worker/observe/static/dom.js +61 -0
- package/dist/worker/observe/static/format-pool.js +67 -0
- package/dist/worker/observe/static/format.js +292 -0
- package/dist/worker/observe/static/index.html +300 -82
- package/dist/worker/observe/static/kpi.js +94 -0
- package/dist/worker/observe/static/relations.js +128 -0
- package/dist/worker/observe/static/router.js +85 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +68 -0
- package/dist/worker/observe/static/state.js +253 -0
- package/dist/worker/observe/static/styles.css +1720 -495
- package/dist/worker/observe/static/views/batch.js +226 -0
- package/dist/worker/observe/static/views/dag-graph.js +172 -0
- package/dist/worker/observe/static/views/dag-inspector.js +477 -0
- package/dist/worker/observe/static/views/dag.js +362 -0
- package/dist/worker/observe/static/views/dashboard.js +442 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +453 -0
- package/dist/worker/observe/static/views/pool.js +347 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +205 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +260 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +797 -27
- package/dist/workflows/dag/node-execution.js +183 -35
- package/dist/workflows/dag/repair-artifact.js +91 -0
- package/dist/workflows/dag/report.js +50 -0
- package/dist/workflows/dag/retry-policy.js +138 -0
- package/dist/workflows/dag/runner.js +77 -17
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +529 -0
- package/dist/workflows/dag/types.js +86 -10
- package/dist/workflows/dag/validate.js +73 -12
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +28 -3
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +140 -0
- package/docs/architecture/evolution.md +53 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +43 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +1 -3
- package/docs/exec-plans/completed/README.md +23 -0
- package/docs/feature-workflow.md +78 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +50 -1
- package/docs/reports/README.md +90 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +23 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +70 -3
- package/docs/templates/agent-dag.supervised-implementation.json +9 -8
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +139 -0
- package/docs/templates/backend-test-dag.json +276 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -0
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +14 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +36 -20
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function updateStatePath(homeDir) {
|
|
4
|
+
return path.join(homeDir, ".loop-agent", "update-state.json");
|
|
5
|
+
}
|
|
6
|
+
export function updateLockPath(homeDir) {
|
|
7
|
+
return path.join(homeDir, ".loop-agent", "update.lock");
|
|
8
|
+
}
|
|
9
|
+
export async function readUpdateState(statePath) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await readFile(statePath, "utf-8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (parsed.schemaVersion !== 1)
|
|
14
|
+
return emptyState();
|
|
15
|
+
return {
|
|
16
|
+
schemaVersion: 1,
|
|
17
|
+
lastCheckedAt: stringOrUndefined(parsed.lastCheckedAt),
|
|
18
|
+
latestVersion: stringOrUndefined(parsed.latestVersion),
|
|
19
|
+
dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
|
|
20
|
+
retryAfter: stringOrUndefined(parsed.retryAfter),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return emptyState();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeUpdateState(statePath, state) {
|
|
28
|
+
await mkdir(path.dirname(statePath), { recursive: true });
|
|
29
|
+
const tempPath = path.join(path.dirname(statePath), `.${path.basename(statePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
30
|
+
await writeFile(tempPath, `${JSON.stringify({ ...state, schemaVersion: 1 }, null, 2)}\n`, "utf-8");
|
|
31
|
+
await rename(tempPath, statePath);
|
|
32
|
+
}
|
|
33
|
+
export async function acquireUpdateLock(lockPath, options = {}) {
|
|
34
|
+
const now = options.now ?? (() => new Date());
|
|
35
|
+
const staleMs = options.staleMs ?? 10 * 60 * 1000;
|
|
36
|
+
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
37
|
+
try {
|
|
38
|
+
await mkdir(lockPath);
|
|
39
|
+
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
40
|
+
return async () => {
|
|
41
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
try {
|
|
46
|
+
const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
|
|
47
|
+
const owner = JSON.parse(ownerRaw);
|
|
48
|
+
if (owner.at && now().getTime() - Date.parse(owner.at) > staleMs) {
|
|
49
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
50
|
+
await mkdir(lockPath);
|
|
51
|
+
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
52
|
+
return async () => {
|
|
53
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function emptyState() {
|
|
64
|
+
return { schemaVersion: 1 };
|
|
65
|
+
}
|
|
66
|
+
function stringOrUndefined(value) {
|
|
67
|
+
return typeof value === "string" ? value : undefined;
|
|
68
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
2
6
|
import { buildLoopAgentProgram } from "./cli/program.js";
|
|
7
|
+
import { createNpmUpdateClient } from "./cli/update/npm-client.js";
|
|
8
|
+
import { runSelfUpdateNotifier } from "./cli/update/notifier.js";
|
|
3
9
|
async function main() {
|
|
4
10
|
const program = buildLoopAgentProgram({
|
|
5
11
|
defaultRepoRoot: process.cwd(),
|
|
6
12
|
});
|
|
7
13
|
await program.parseAsync(process.argv);
|
|
14
|
+
if (process.exitCode && process.exitCode !== 0)
|
|
15
|
+
return;
|
|
16
|
+
const packageRoot = resolvePackageRoot();
|
|
17
|
+
const currentVersion = readPackageVersion(packageRoot);
|
|
18
|
+
await runSelfUpdateNotifier({
|
|
19
|
+
argv: process.argv.slice(2),
|
|
20
|
+
currentVersion,
|
|
21
|
+
env: process.env,
|
|
22
|
+
homeDir: os.homedir(),
|
|
23
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
24
|
+
stdoutIsTTY: process.stdout.isTTY === true,
|
|
25
|
+
stderrIsTTY: process.stderr.isTTY === true,
|
|
26
|
+
client: createNpmUpdateClient({
|
|
27
|
+
currentVersion,
|
|
28
|
+
packageRoot,
|
|
29
|
+
env: process.env,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
8
32
|
}
|
|
9
33
|
main().catch((error) => {
|
|
10
34
|
console.error(error instanceof Error ? error.message : String(error));
|
|
11
35
|
process.exit(1);
|
|
12
36
|
});
|
|
37
|
+
function resolvePackageRoot() {
|
|
38
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
|
+
}
|
|
40
|
+
function readPackageVersion(packageRoot) {
|
|
41
|
+
const pkg = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf-8"));
|
|
42
|
+
if (!pkg.version)
|
|
43
|
+
throw new Error("loop-agent package.json is missing version");
|
|
44
|
+
return pkg.version;
|
|
45
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cursor Prompt — one-shot CLI for Cursor SDK tasks.
|
|
2
|
+
* Cursor Prompt — one-shot CLI for Cursor SDK tasks (sidecar).
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* cursor-prompt "Fix lint errors" # one-shot
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* cursor-prompt --model gpt-5.5 "Deep analysis" # pick model
|
|
10
10
|
* cursor-prompt --cwd /path --timeout 120000 "task" # cwd + timeout
|
|
11
11
|
*/
|
|
12
|
-
import { executeCursorTask, DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS,
|
|
12
|
+
import { executeCursorTask, executeCursorPromptStream, DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, } from "../sidecars/cursor-prompt/index.js";
|
|
13
13
|
import { createOneShotPromptParseAccum, formatUnknownFlagError, parseOneShotCommonFlag, printOneShotHelpAndExit, resolveOneShotCwd, resolveOneShotTask, } from "../shared/one-shot-prompt-args.js";
|
|
14
14
|
import { persistCursorRunLog, computeRunDir } from "../records/one-shot-runs.js";
|
|
15
15
|
const CURSOR_PROMPT_USAGE = [
|
|
@@ -111,12 +111,17 @@ async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
|
|
|
111
111
|
const startedAt = Date.now();
|
|
112
112
|
process.stderr.write(`[cursor-prompt] running "${task.slice(0, 80)}${task.length > 80 ? "..." : ""}" (model=${model}, cwd=${cwd})\n`);
|
|
113
113
|
const runDir = (await computeRunDir(cwd, task)) ?? undefined;
|
|
114
|
-
const result = await executeCursorTask({
|
|
115
|
-
|
|
114
|
+
const result = await executeCursorTask({
|
|
115
|
+
task,
|
|
116
|
+
cwd,
|
|
117
|
+
model,
|
|
118
|
+
timeoutMs,
|
|
119
|
+
runDir,
|
|
120
|
+
});
|
|
116
121
|
await persistCursorRunLog(cwd, {
|
|
117
122
|
task,
|
|
118
123
|
model,
|
|
119
|
-
channel:
|
|
124
|
+
channel: "tool",
|
|
120
125
|
cwd,
|
|
121
126
|
result,
|
|
122
127
|
runDir,
|
|
@@ -135,87 +140,42 @@ async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
|
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
async function runCursorPromptStreaming(task, cwd, model, timeoutMs) {
|
|
138
|
-
const apiKey = process.env.CURSOR_API_KEY;
|
|
139
|
-
if (!apiKey?.trim()) {
|
|
140
|
-
process.stderr.write("[cursor-prompt] CURSOR_API_KEY not set\n");
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
const runDir = (await computeRunDir(cwd, task)) ?? undefined;
|
|
144
|
-
const prompt = buildCursorPrompt(task, { runDir });
|
|
145
143
|
const startedAt = Date.now();
|
|
146
|
-
process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
timeoutHandle = setTimeout(() => {
|
|
173
|
-
resolve("timeout");
|
|
174
|
-
}, timeoutMs);
|
|
175
|
-
});
|
|
176
|
-
const raced = await Promise.race([
|
|
177
|
-
runPromise.then((v) => ({ kind: "done", value: v })),
|
|
178
|
-
timeoutRace.then(() => ({ kind: "timeout" })),
|
|
179
|
-
]);
|
|
180
|
-
if (timeoutHandle)
|
|
181
|
-
clearTimeout(timeoutHandle);
|
|
182
|
-
if (raced.kind === "timeout") {
|
|
183
|
-
process.stderr.write(`\n[cursor-prompt] TIMEOUT after ${timeoutMs}ms\n`);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
187
|
-
const sdkResult = raced.value;
|
|
188
|
-
process.stderr.write(`\n[cursor-prompt] done in ${elapsed}s, status=${sdkResult.status}\n`);
|
|
189
|
-
const runStatus = sdkResult.status;
|
|
190
|
-
const status = mapRunStatusToExecutionStatus(runStatus, false);
|
|
191
|
-
const ok = status === 'completed';
|
|
192
|
-
// Persist human-readable run log under .harness/runs/
|
|
193
|
-
await persistCursorRunLog(cwd, {
|
|
194
|
-
task,
|
|
195
|
-
model,
|
|
196
|
-
channel: 'tool',
|
|
197
|
-
cwd,
|
|
198
|
-
result: {
|
|
199
|
-
ok,
|
|
200
|
-
status,
|
|
201
|
-
failureCategory: ok ? 'success' : classifyCursorFailure({ runStatus, stderr: '', stdout: '' }),
|
|
202
|
-
model,
|
|
203
|
-
durationMs: sdkResult.durationMs ?? Date.now() - startedAt,
|
|
204
|
-
stdout: '', // Streaming output went directly to stdout
|
|
205
|
-
stderr: '',
|
|
206
|
-
},
|
|
207
|
-
runDir,
|
|
208
|
-
});
|
|
144
|
+
process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})
|
|
145
|
+
`);
|
|
146
|
+
const runDir = (await computeRunDir(cwd, task)) ?? undefined;
|
|
147
|
+
const result = await executeCursorPromptStream({
|
|
148
|
+
task,
|
|
149
|
+
cwd,
|
|
150
|
+
model,
|
|
151
|
+
timeoutMs,
|
|
152
|
+
runDir,
|
|
153
|
+
callbacks: {
|
|
154
|
+
onAssistantText: (text) => process.stdout.write(text),
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
await persistCursorRunLog(cwd, {
|
|
158
|
+
task,
|
|
159
|
+
model,
|
|
160
|
+
channel: "tool",
|
|
161
|
+
cwd,
|
|
162
|
+
result,
|
|
163
|
+
runDir,
|
|
164
|
+
});
|
|
165
|
+
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
166
|
+
if (result.ok) {
|
|
167
|
+
process.stderr.write(`
|
|
168
|
+
[cursor-prompt] done in ${elapsed}s, status=${result.status}
|
|
169
|
+
`);
|
|
209
170
|
}
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
process.stderr.write(
|
|
171
|
+
else {
|
|
172
|
+
const stderr = result.stderr || "(no output)";
|
|
173
|
+
process.stderr.write(`
|
|
174
|
+
[cursor-prompt] FAILED in ${elapsed}s (${result.failureCategory}):
|
|
175
|
+
${stderr}
|
|
176
|
+
`);
|
|
213
177
|
process.exit(1);
|
|
214
178
|
}
|
|
215
|
-
finally {
|
|
216
|
-
if (timeoutHandle)
|
|
217
|
-
clearTimeout(timeoutHandle);
|
|
218
|
-
}
|
|
219
179
|
}
|
|
220
180
|
export function printCursorPromptUsage() {
|
|
221
181
|
console.log(CURSOR_PROMPT_USAGE);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
2
3
|
import { assertDagRunTransferTargetAvailable, getDagRunDir, readHumanEscalation, requirePausedDagRun, transferDagRunDir, writeDagRunState, writeHumanApprovalArtifact, } from "../workflows/dag/lifecycle.js";
|
|
4
|
+
import { assertSkillSnapshotCoversSpec, readSkillSnapshot, SKILL_SNAPSHOT_REL_PATH, } from "../workflows/dag/skill-snapshot.js";
|
|
3
5
|
export function parseDagApproveArgs(args) {
|
|
4
6
|
if (args.length === 0) {
|
|
5
7
|
throw new Error("usage: dag approve --run-id <id> --option <A|B|...> [--notes \"...\"]");
|
|
@@ -59,6 +61,34 @@ export async function runDagApprove(repoRoot, rawArgs) {
|
|
|
59
61
|
if (!optionIds.has(parsed.option)) {
|
|
60
62
|
throw new Error(`option ${parsed.option} is not valid; expected one of: ${[...optionIds].join(", ")}`);
|
|
61
63
|
}
|
|
64
|
+
// Fail closed before any approval/state/lifecycle mutation. Snapshot-backed
|
|
65
|
+
// runs must validate fully. A true pre-snapshot legacy run (no ref and no
|
|
66
|
+
// artifact) may still be approved so resume can perform the established
|
|
67
|
+
// legacy-resume-backfill. An artifact without a ref is an orphan and cannot
|
|
68
|
+
// be trusted or silently adopted.
|
|
69
|
+
if (state.skillSnapshotRef) {
|
|
70
|
+
try {
|
|
71
|
+
const snapshot = await readSkillSnapshot(runDir, state.skillSnapshotRef, {
|
|
72
|
+
expectedRunId: state.runId,
|
|
73
|
+
});
|
|
74
|
+
assertSkillSnapshotCoversSpec(snapshot, spec, state);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new Error(`skill snapshot pre-approval validation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const snapshotPath = path.join(runDir, ...SKILL_SNAPSHOT_REL_PATH.split("/"));
|
|
82
|
+
try {
|
|
83
|
+
await access(snapshotPath);
|
|
84
|
+
throw new Error("skill snapshot artifact exists without a state ref; refusing approval");
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (!isNotFound(error)) {
|
|
88
|
+
throw new Error(`skill snapshot pre-approval validation failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
62
92
|
const activeRunDir = getDagRunDir(repoRoot, "active", state.runId);
|
|
63
93
|
await assertDagRunTransferTargetAvailable(activeRunDir);
|
|
64
94
|
const approvedAt = new Date().toISOString();
|
|
@@ -100,3 +130,9 @@ export async function runDagApprove(repoRoot, rawArgs) {
|
|
|
100
130
|
nextRecommendedAction: "Run is approved; use dag resume to continue.",
|
|
101
131
|
}, null, 2));
|
|
102
132
|
}
|
|
133
|
+
function isNotFound(error) {
|
|
134
|
+
return Boolean(error
|
|
135
|
+
&& typeof error === "object"
|
|
136
|
+
&& "code" in error
|
|
137
|
+
&& error.code === "ENOENT");
|
|
138
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import os from
|
|
2
|
-
import path from
|
|
3
|
-
import { delegateTask } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
const DELEGATE_USAGE =
|
|
9
|
-
+ '[--no-symlink] [--auto-run] [--no-auto-run]';
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { delegateTask } from "../task/delegate.js";
|
|
4
|
+
import { loadHarnessManifest } from "../governance/harness.js";
|
|
5
|
+
import { generateTaskDagUseCase } from "../application/dag/generate-task-dag.js";
|
|
6
|
+
import { validateDagUseCase } from "../application/dag/validate-dag.js";
|
|
7
|
+
import { runDagUseCase } from "../application/dag/run-dag.js";
|
|
8
|
+
const DELEGATE_USAGE = "usage: delegate <taskId> [--base <branch>] [--branch <name>] [--no-symlink] [--auto-run]";
|
|
10
9
|
export function parseDelegateArgs(args) {
|
|
11
10
|
if (args.length === 0) {
|
|
12
11
|
throw new Error(DELEGATE_USAGE);
|
|
@@ -15,44 +14,37 @@ export function parseDelegateArgs(args) {
|
|
|
15
14
|
let base;
|
|
16
15
|
let branch;
|
|
17
16
|
let symlinkNodeModules = true;
|
|
18
|
-
let autoRun =
|
|
19
|
-
let executor;
|
|
17
|
+
let autoRun = false;
|
|
20
18
|
for (let i = 0; i < args.length; i += 1) {
|
|
21
19
|
const arg = args[i];
|
|
22
|
-
if (arg ===
|
|
20
|
+
if (arg === "--base") {
|
|
23
21
|
base = args[i + 1];
|
|
24
22
|
if (!base)
|
|
25
|
-
throw new Error(
|
|
23
|
+
throw new Error("--base requires a value");
|
|
26
24
|
i += 1;
|
|
27
25
|
}
|
|
28
|
-
else if (arg ===
|
|
26
|
+
else if (arg === "--branch") {
|
|
29
27
|
branch = args[i + 1];
|
|
30
28
|
if (!branch)
|
|
31
|
-
throw new Error(
|
|
29
|
+
throw new Error("--branch requires a value");
|
|
32
30
|
i += 1;
|
|
33
31
|
}
|
|
34
|
-
else if (arg ===
|
|
35
|
-
|
|
36
|
-
if (value !== 'pi' && value !== 'cursor') {
|
|
37
|
-
throw new Error('--executor must be "pi" or "cursor"');
|
|
38
|
-
}
|
|
39
|
-
executor = value;
|
|
40
|
-
i += 1;
|
|
32
|
+
else if (arg === "--executor") {
|
|
33
|
+
throw new Error("--executor was removed; delegate is worktree isolation only; use --auto-run for Pi DAG execution");
|
|
41
34
|
}
|
|
42
|
-
else if (arg ===
|
|
35
|
+
else if (arg === "--no-symlink") {
|
|
43
36
|
symlinkNodeModules = false;
|
|
44
37
|
}
|
|
45
|
-
else if (arg ===
|
|
38
|
+
else if (arg === "--auto-run") {
|
|
46
39
|
autoRun = true;
|
|
47
40
|
}
|
|
48
|
-
else if (arg ===
|
|
49
|
-
|
|
41
|
+
else if (arg === "--no-auto-run") {
|
|
42
|
+
throw new Error("--no-auto-run was removed; delegate defaults to worktree-only (omit --auto-run)");
|
|
50
43
|
}
|
|
51
|
-
else if (arg ===
|
|
52
|
-
throw new Error(
|
|
53
|
-
+ 'Use `delegate <taskId> --executor cursor --auto-run` for in-process Cursor SDK.');
|
|
44
|
+
else if (arg === "--supervised") {
|
|
45
|
+
throw new Error("--supervised was removed with the tmux white-box subagent lifecycle. Use `delegate <taskId> --auto-run` for Pi DAG execution in the worktree.");
|
|
54
46
|
}
|
|
55
|
-
else if (!arg.startsWith(
|
|
47
|
+
else if (!arg.startsWith("--")) {
|
|
56
48
|
if (taskId)
|
|
57
49
|
throw new Error(`unexpected positional argument: ${arg}`);
|
|
58
50
|
taskId = arg;
|
|
@@ -62,71 +54,77 @@ export function parseDelegateArgs(args) {
|
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
56
|
if (!taskId) {
|
|
65
|
-
throw new Error(
|
|
57
|
+
throw new Error("delegate requires <taskId> as the first positional argument");
|
|
66
58
|
}
|
|
67
|
-
return { taskId, base, branch, symlinkNodeModules, autoRun
|
|
59
|
+
return { taskId, base, branch, symlinkNodeModules, autoRun };
|
|
68
60
|
}
|
|
69
|
-
export function
|
|
70
|
-
|
|
71
|
-
console.error(` logs: .harness/tasks/${taskId}/logs/result.summary.md`);
|
|
72
|
-
for (const line of buildCursorRecoverySteps({
|
|
73
|
-
channel: 'delegate',
|
|
74
|
-
taskId,
|
|
75
|
-
sessionName: result.sessionName,
|
|
76
|
-
worktreePath: result.worktreePath,
|
|
77
|
-
failureCategory: cursorResult.failureCategory,
|
|
78
|
-
})) {
|
|
79
|
-
console.error(line);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
export function printCursorDelegateNextSteps(taskId, result) {
|
|
61
|
+
export function printDelegateNextSteps(taskId, result) {
|
|
62
|
+
const dagPath = path.join(os.tmpdir(), `${taskId}-dag.json`);
|
|
83
63
|
const lines = [
|
|
84
|
-
`[delegate]
|
|
64
|
+
`[delegate] worktree ready (Pi-only).`,
|
|
85
65
|
` worktree: ${result.worktreePath}`,
|
|
86
|
-
` Next — run
|
|
87
|
-
`
|
|
66
|
+
` Next — generate, validate, and run DAG in the worktree:`,
|
|
67
|
+
` loop-agent dag run-task ${taskId} --profile auto --strict-models --output ${JSON.stringify(dagPath)}`,
|
|
68
|
+
` loop-agent dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
|
|
69
|
+
` loop-agent run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(result.worktreePath)}`,
|
|
70
|
+
` Or: loop-agent delegate ${taskId} --auto-run`,
|
|
88
71
|
` After work completes:`,
|
|
89
|
-
`
|
|
72
|
+
` loop-agent harvest ${taskId}`,
|
|
90
73
|
];
|
|
91
74
|
for (const line of lines) {
|
|
92
75
|
console.error(line);
|
|
93
76
|
}
|
|
94
77
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
` 1. npm run dev -- dag run-task ${taskId} --profile auto --strict-models --output ${JSON.stringify(dagPath)}`,
|
|
102
|
-
` 2. npm run dev -- dag validate --dag ${JSON.stringify(dagPath)} --strict-models --strict-governance`,
|
|
103
|
-
` 3. npm run dev -- run-dag --dag ${JSON.stringify(dagPath)} --cwd ${JSON.stringify(cwd)}`,
|
|
104
|
-
].join('\n');
|
|
105
|
-
}
|
|
106
|
-
export async function runDelegate(repoRoot, rawArgs) {
|
|
78
|
+
const DEFAULT_RUN_DELEGATE_DEPENDENCIES = {
|
|
79
|
+
generateTaskDag: generateTaskDagUseCase,
|
|
80
|
+
validateDag: validateDagUseCase,
|
|
81
|
+
runDag: runDagUseCase,
|
|
82
|
+
};
|
|
83
|
+
export async function runDelegate(repoRoot, rawArgs, dependencies = DEFAULT_RUN_DELEGATE_DEPENDENCIES) {
|
|
107
84
|
const parsed = parseDelegateArgs(rawArgs);
|
|
108
85
|
const manifest = await loadHarnessManifest(repoRoot);
|
|
109
86
|
const result = await delegateTask(repoRoot, manifest, parsed.taskId, {
|
|
110
87
|
base: parsed.base,
|
|
111
88
|
branch: parsed.branch,
|
|
112
89
|
symlinkNodeModules: parsed.symlinkNodeModules,
|
|
113
|
-
executor: parsed.executor,
|
|
114
90
|
});
|
|
115
91
|
if (parsed.autoRun) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
92
|
+
const dagPath = path.join(os.tmpdir(), `${parsed.taskId}-delegate-dag.json`);
|
|
93
|
+
await dependencies.generateTaskDag({
|
|
94
|
+
repoRoot,
|
|
95
|
+
taskId: parsed.taskId,
|
|
96
|
+
outputPath: dagPath,
|
|
97
|
+
strictModels: true,
|
|
98
|
+
execute: false,
|
|
99
|
+
initOnly: false,
|
|
100
|
+
dryRun: false,
|
|
101
|
+
cwd: result.worktreePath,
|
|
102
|
+
profile: "auto",
|
|
103
|
+
profileExplicit: false,
|
|
104
|
+
});
|
|
105
|
+
await dependencies.validateDag({
|
|
106
|
+
repoRoot,
|
|
107
|
+
dagPath,
|
|
108
|
+
strictModelMatrix: true,
|
|
109
|
+
strictGovernance: true,
|
|
110
|
+
forbidExecutors: [],
|
|
111
|
+
});
|
|
112
|
+
const runSummary = await dependencies.runDag({
|
|
113
|
+
repoRoot,
|
|
114
|
+
dagPath,
|
|
115
|
+
cwd: result.worktreePath,
|
|
116
|
+
initOnly: false,
|
|
117
|
+
dryRun: false,
|
|
118
|
+
});
|
|
119
|
+
console.log(JSON.stringify({
|
|
120
|
+
...result,
|
|
121
|
+
autoRun: {
|
|
122
|
+
dagPath,
|
|
123
|
+
runSummary,
|
|
124
|
+
},
|
|
125
|
+
}, null, 2));
|
|
126
|
+
return;
|
|
130
127
|
}
|
|
128
|
+
printDelegateNextSteps(parsed.taskId, result);
|
|
131
129
|
console.log(JSON.stringify(result, null, 2));
|
|
132
130
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,14 +2,6 @@ import { access } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { checkPiAvailability, resolvePiBackend } from '../executors/pi-executor.js';
|
|
4
4
|
import { checkPiSdkAvailability } from '../executors/pi-sdk-executor.js';
|
|
5
|
-
import { resolveCursorExecutorConfig } from '../executors/config-core.js';
|
|
6
|
-
import { isCursorWorkerEnabled, isCursorWorkerRunning } from '../executors/cursor-worker-client.js';
|
|
7
|
-
function checkCursorAvailability() {
|
|
8
|
-
if (!process.env.CURSOR_API_KEY?.trim()) {
|
|
9
|
-
return { ok: false, detail: 'CURSOR_API_KEY not set' };
|
|
10
|
-
}
|
|
11
|
-
return { ok: true, detail: 'CURSOR_API_KEY configured' };
|
|
12
|
-
}
|
|
13
5
|
export async function runDoctor(adapter, repoRoot) {
|
|
14
6
|
const manifest = await adapter.loadHarnessManifest(repoRoot);
|
|
15
7
|
const requiredPaths = [];
|
|
@@ -36,15 +28,5 @@ export async function runDoctor(adapter, repoRoot) {
|
|
|
36
28
|
if (backendMode === 'sdk-first' && !sdk.ok) {
|
|
37
29
|
console.error(`doctor warning: CODE_AGENT_PI_BACKEND=sdk-first but pi SDK unavailable (${sdk.detail}); steps will fall back to CLI`);
|
|
38
30
|
}
|
|
39
|
-
const cursorCfg = resolveCursorExecutorConfig(manifest);
|
|
40
|
-
if (cursorCfg) {
|
|
41
|
-
const cursor = checkCursorAvailability();
|
|
42
|
-
if (!cursor.ok) {
|
|
43
|
-
console.error(`doctor warning: executors.cursor registered but ${cursor.detail}`);
|
|
44
|
-
}
|
|
45
|
-
if (isCursorWorkerEnabled()) {
|
|
46
|
-
console.log(`doctor: CURSOR_USE_WORKER=1 (running=${isCursorWorkerRunning()})`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
31
|
console.log('doctor ok');
|
|
50
32
|
}
|