@tea-agent/loop-agent 0.8.0 → 0.10.0-alpha.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 -0
- package/CHANGELOG.md +101 -1
- package/README.md +69 -5
- package/dist/application/dag/args.js +13 -16
- package/dist/application/dag/generate-task-dag.js +32 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +7 -11
- package/dist/cli/program.js +9 -21
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/dag-reconcile-run.js +118 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +60 -40
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- 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 +75 -9
- package/dist/governance/checks.js +6 -3
- package/dist/governance/manifest-types.js +33 -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 +353 -0
- package/dist/shared/reference-context.js +48 -22
- 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 +30 -13
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +2 -3
- package/dist/worker/cli.js +243 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/delivery/final-verification.js +194 -0
- package/dist/worker/delivery/git-transaction.js +354 -0
- package/dist/worker/delivery/package.js +502 -0
- package/dist/worker/feature/decision-loader.js +68 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/next-action.js +74 -0
- package/dist/worker/feature/reducer.js +133 -0
- package/dist/worker/feature/review.js +502 -0
- package/dist/worker/feature/run.js +365 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +270 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/loop-agent-client.js +345 -101
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/read-model.js +282 -15
- package/dist/worker/observe/paths.js +17 -5
- package/dist/worker/observe/routes.js +78 -20
- package/dist/worker/observe/server.js +8 -6
- package/dist/worker/observe/static/app.js +1045 -177
- package/dist/worker/observe/static/index.html +70 -43
- package/dist/worker/observe/static/styles.css +553 -610
- package/dist/worker/pool/run-store.js +14 -2
- package/dist/worker/pool/validation.js +59 -0
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/report/morning-report.js +41 -6
- package/dist/worker/run-task/run-task.js +23 -13
- package/dist/worker/runner/run-ready.js +89 -11
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/governance-profile.js +10 -0
- package/dist/workflows/dag/init-hybrid.js +601 -26
- package/dist/workflows/dag/lifecycle.js +146 -0
- package/dist/workflows/dag/node-execution.js +64 -7
- package/dist/workflows/dag/prompt.js +16 -0
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/runner.js +176 -119
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +45 -9
- package/dist/workflows/dag/validate.js +5 -8
- 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 +35 -12
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +41 -15
- 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 +39 -13
- package/docs/development-principles.md +1 -1
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +21 -0
- package/docs/feature-workflow.md +44 -4
- package/docs/init-surface.manifest.json +63 -1
- package/docs/loop-agent-harness.md +65 -3
- package/docs/progress/README.md +27 -0
- package/docs/reports/README.md +74 -5
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +4 -2
- 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 +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
- package/docs/templates/backend-test-dag.json +213 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -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 +8 -5
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/init-evolution-review.md +4 -2
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +17 -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 +6 -11
- package/package.json +22 -44
- package/scripts/check-product-line-docs.sh +10 -3
- package/scripts/check-task-pool-root.sh +1 -1
- 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 +48 -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/init-capability-evolution/SKILL.md +1 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +28 -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 +13 -15
- 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/verification-and-failure-handling.md +8 -3
- 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
package/dist/cli/program.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
+
import { readPackageVersion } from "../shared/package-metadata.js";
|
|
4
5
|
import { resolveAdapter } from "../adapters/index.js";
|
|
5
6
|
import { runCloseout } from "../commands/closeout.js";
|
|
6
7
|
import { runCoverageAudit } from "../commands/coverage-audit.js";
|
|
7
|
-
import { runCursorWorker } from "../commands/cursor-worker.js";
|
|
8
8
|
import { parseCursorPromptArgs, printCursorPromptUsage, runCursorPrompt, } from "../commands/cursor-prompt.js";
|
|
9
9
|
import { runDagApprove } from "../commands/dag-approve.js";
|
|
10
10
|
import { runDagFinalVerification } from "../commands/dag-final-verification.js";
|
|
11
11
|
import { runDagInitHybrid } from "../commands/dag-init-hybrid.js";
|
|
12
12
|
import { runDagReconcileTasks } from "../commands/dag-reconcile-tasks.js";
|
|
13
|
+
import { runDagReconcileRun } from "../commands/dag-reconcile-run.js";
|
|
13
14
|
import { runDagReject } from "../commands/dag-reject.js";
|
|
14
15
|
import { runDagCloseoutDraft, runDagReport } from "../commands/dag-report.js";
|
|
15
16
|
import { runDagResume } from "../commands/dag-resume.js";
|
|
@@ -52,18 +53,6 @@ import { handleStandaloneOneShotCommandError } from "../shared/one-shot-prompt-a
|
|
|
52
53
|
import { runDagDecisionInspect, runDagDecisionValidate, } from "../workflows/dag/decision-envelope.js";
|
|
53
54
|
import { runDagDoctor, runDagStatus } from "../workflows/dag/lifecycle.js";
|
|
54
55
|
import { CODE_AGENT_COMMAND_MANIFEST } from "./catalog.js";
|
|
55
|
-
function readPackageVersion() {
|
|
56
|
-
try {
|
|
57
|
-
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf-8");
|
|
58
|
-
const parsed = JSON.parse(raw);
|
|
59
|
-
if (typeof parsed.version === "string")
|
|
60
|
-
return parsed.version;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Fall back only if the package metadata is unexpectedly unavailable.
|
|
64
|
-
}
|
|
65
|
-
return "0.0.0";
|
|
66
|
-
}
|
|
67
56
|
function resolveRepoRootOnly(command, defaultRepoRoot, behavior) {
|
|
68
57
|
const options = collectGlobalOptions(command);
|
|
69
58
|
const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot);
|
|
@@ -230,9 +219,6 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
230
219
|
case "harvest":
|
|
231
220
|
await runHarvest(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
232
221
|
return;
|
|
233
|
-
case "cursor-worker":
|
|
234
|
-
await runCursorWorker(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
235
|
-
return;
|
|
236
222
|
case "knowledge":
|
|
237
223
|
await runKnowledge(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
238
224
|
return;
|
|
@@ -300,6 +286,9 @@ async function runDagAction(repoRoot, subcommand, rest) {
|
|
|
300
286
|
case "closeout-draft":
|
|
301
287
|
await runDagCloseoutDraft(repoRoot, args);
|
|
302
288
|
return;
|
|
289
|
+
case "reconcile-run":
|
|
290
|
+
await runDagReconcileRun(repoRoot, args);
|
|
291
|
+
return;
|
|
303
292
|
case "reconcile-tasks":
|
|
304
293
|
await runDagReconcileTasks(repoRoot, args);
|
|
305
294
|
return;
|
|
@@ -319,7 +308,7 @@ async function runDagAction(repoRoot, subcommand, rest) {
|
|
|
319
308
|
throw new Error("usage: dag decision <inspect|validate> --run-id <id> [--node-id <node-id>]");
|
|
320
309
|
}
|
|
321
310
|
default:
|
|
322
|
-
throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-tasks|final-verification|decision> ...");
|
|
311
|
+
throw new Error("usage: dag <init-hybrid|run-task|validate|workflow-plan|workflow-validate|workflow-compile|approve|reject|resume|status|doctor|report|closeout-draft|reconcile-run|reconcile-tasks|final-verification|decision> ...");
|
|
323
312
|
}
|
|
324
313
|
}
|
|
325
314
|
function compactArgs(args) {
|
|
@@ -371,7 +360,6 @@ function configureRunDag(command) {
|
|
|
371
360
|
.option("--dry-run", "validate and plan without executing")
|
|
372
361
|
.option("--max-concurrent <n>", "maximum concurrent nodes")
|
|
373
362
|
.option("--run-id <id>", "run id")
|
|
374
|
-
.option("--no-cursor", "disable Cursor execution")
|
|
375
363
|
.option("--canvas-path <path>", "absolute canvas output path")
|
|
376
364
|
.option("--canvas <name>", "canvas name")
|
|
377
365
|
.option("--canvases-dir <dir>", "canvas output directory")
|
|
@@ -383,7 +371,6 @@ function configureDagRunTask(command) {
|
|
|
383
371
|
.option("-o, --output <path>", "output DAG path")
|
|
384
372
|
.option("--profile <profile>", "auto|minimal|standard|reviewed|supervised")
|
|
385
373
|
.option("--strict-models", "fail on model routing warnings")
|
|
386
|
-
.option("--no-cursor", "disable Cursor execution")
|
|
387
374
|
.option("--execute", "execute after creating the DAG")
|
|
388
375
|
.option("--init-only", "initialize run artifacts without executing")
|
|
389
376
|
.option("--dry-run", "validate and plan without executing")
|
|
@@ -450,6 +437,7 @@ function addDagCommand(program, defaultRepoRoot) {
|
|
|
450
437
|
"doctor",
|
|
451
438
|
"report",
|
|
452
439
|
"closeout-draft",
|
|
440
|
+
"reconcile-run",
|
|
453
441
|
"reconcile-tasks",
|
|
454
442
|
"final-verification",
|
|
455
443
|
], defaultRepoRoot);
|
|
@@ -502,7 +490,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
502
490
|
program
|
|
503
491
|
.name("loop-agent")
|
|
504
492
|
.description("Loop Agent task and DAG automation CLI")
|
|
505
|
-
.version(readPackageVersion(), "-V, --version", "display version")
|
|
493
|
+
.version(readPackageVersion(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
|
|
506
494
|
.showHelpAfterError()
|
|
507
495
|
.option("--repo-root <path>", "repository root")
|
|
508
496
|
.option("--adapter <name>", "harness adapter name")
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { writeJsonAtomic } from "../infrastructure/harness/atomic-write.js";
|
|
3
|
+
import { assessDagRunLiveness, assessDagRunRecoveryEligibility, assertDagRunTransferTargetAvailable, getDagRunDir, locateDagRun, readDagRunState, transferDagRunDir, writeDagRunState, } from "../workflows/dag/lifecycle.js";
|
|
4
|
+
export function parseDagReconcileRunArgs(args) {
|
|
5
|
+
let runId;
|
|
6
|
+
let action;
|
|
7
|
+
let reason;
|
|
8
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
9
|
+
const arg = args[index];
|
|
10
|
+
if (arg === "--run-id")
|
|
11
|
+
runId = args[++index];
|
|
12
|
+
else if (arg.startsWith("--run-id="))
|
|
13
|
+
runId = arg.slice("--run-id=".length);
|
|
14
|
+
else if (arg === "--action")
|
|
15
|
+
action = parseAction(args[++index]);
|
|
16
|
+
else if (arg.startsWith("--action="))
|
|
17
|
+
action = parseAction(arg.slice("--action=".length));
|
|
18
|
+
else if (arg === "--reason")
|
|
19
|
+
reason = args[++index];
|
|
20
|
+
else if (arg.startsWith("--reason="))
|
|
21
|
+
reason = arg.slice("--reason=".length);
|
|
22
|
+
else if (arg.startsWith("-"))
|
|
23
|
+
throw new Error(`unknown dag reconcile-run flag: ${arg}`);
|
|
24
|
+
else
|
|
25
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
26
|
+
}
|
|
27
|
+
if (!runId)
|
|
28
|
+
throw new Error("dag reconcile-run requires --run-id <id>");
|
|
29
|
+
if (action && !reason?.trim()) {
|
|
30
|
+
throw new Error("dag reconcile-run mutation requires --reason <text>");
|
|
31
|
+
}
|
|
32
|
+
return { runId, action, ...(reason?.trim() ? { reason: reason.trim() } : {}) };
|
|
33
|
+
}
|
|
34
|
+
function parseAction(value) {
|
|
35
|
+
if (value === "supersede" || value === "abandon")
|
|
36
|
+
return value;
|
|
37
|
+
throw new Error("dag reconcile-run --action must be supersede or abandon");
|
|
38
|
+
}
|
|
39
|
+
export async function runDagReconcileRun(repoRoot, rawArgs) {
|
|
40
|
+
const parsed = parseDagReconcileRunArgs(rawArgs);
|
|
41
|
+
const located = await locateDagRun(repoRoot, parsed.runId);
|
|
42
|
+
if (!located)
|
|
43
|
+
throw new Error(`dag run not found: ${parsed.runId}`);
|
|
44
|
+
const state = await readDagRunState(located.runDir);
|
|
45
|
+
const liveness = assessDagRunLiveness({ state });
|
|
46
|
+
const eligibility = assessDagRunRecoveryEligibility({
|
|
47
|
+
lifecycle: located.lifecycle,
|
|
48
|
+
state,
|
|
49
|
+
liveness: liveness.status,
|
|
50
|
+
});
|
|
51
|
+
if (!parsed.action) {
|
|
52
|
+
console.log(JSON.stringify({
|
|
53
|
+
action: "inspect",
|
|
54
|
+
runId: state.runId,
|
|
55
|
+
lifecycle: located.lifecycle,
|
|
56
|
+
status: state.status,
|
|
57
|
+
liveness: liveness.status,
|
|
58
|
+
eligibility,
|
|
59
|
+
nextRecommendedAction: eligibility.canReconcile
|
|
60
|
+
? "Re-run with --action supersede|abandon and --reason <text>."
|
|
61
|
+
: "Do not reconcile this run; inspect runner and lifecycle evidence first.",
|
|
62
|
+
}, null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!eligibility.canReconcile || !eligibility.allowedActions.includes(parsed.action)) {
|
|
66
|
+
throw new Error(`dag run ${state.runId} is not eligible for reconciliation: ${eligibility.reasons.join(", ") || "unknown reason"}`);
|
|
67
|
+
}
|
|
68
|
+
const reconciledAt = new Date().toISOString();
|
|
69
|
+
const completedRunDir = getDagRunDir(repoRoot, "completed", state.runId);
|
|
70
|
+
if (located.lifecycle !== "completed") {
|
|
71
|
+
await assertDagRunTransferTargetAvailable(completedRunDir);
|
|
72
|
+
}
|
|
73
|
+
const artifactRelativePath = path.join(".harness", "dag-runs", "completed", state.runId, "reconciliation.json").replace(/\\/g, "/");
|
|
74
|
+
await writeJsonAtomic(path.join(located.runDir, "reconciliation.json"), {
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
runId: state.runId,
|
|
77
|
+
action: parsed.action,
|
|
78
|
+
reason: parsed.reason,
|
|
79
|
+
reconciledAt,
|
|
80
|
+
previousLifecycle: located.lifecycle,
|
|
81
|
+
previousStatus: state.status,
|
|
82
|
+
liveness: liveness.status,
|
|
83
|
+
originalState: state,
|
|
84
|
+
});
|
|
85
|
+
const previousStatus = state.status;
|
|
86
|
+
for (const node of Object.values(state.nodes)) {
|
|
87
|
+
if (node.status !== "RUNNING")
|
|
88
|
+
continue;
|
|
89
|
+
node.status = "ERROR";
|
|
90
|
+
node.finishedAt = reconciledAt;
|
|
91
|
+
node.failureCategory = `operator-${parsed.action}`;
|
|
92
|
+
}
|
|
93
|
+
state.status = parsed.action === "supersede" ? "superseded" : "abandoned";
|
|
94
|
+
state.finishedAt = reconciledAt;
|
|
95
|
+
state.failureCategory = `operator-${parsed.action}`;
|
|
96
|
+
state.reconciliation = {
|
|
97
|
+
action: parsed.action,
|
|
98
|
+
reason: parsed.reason,
|
|
99
|
+
reconciledAt,
|
|
100
|
+
previousStatus,
|
|
101
|
+
previousLifecycle: located.lifecycle,
|
|
102
|
+
liveness: liveness.status,
|
|
103
|
+
artifactPath: artifactRelativePath,
|
|
104
|
+
};
|
|
105
|
+
await writeDagRunState(located.runDir, state);
|
|
106
|
+
const runDir = located.lifecycle === "completed"
|
|
107
|
+
? located.runDir
|
|
108
|
+
: await transferDagRunDir(located.runDir, completedRunDir);
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
action: parsed.action,
|
|
111
|
+
runId: state.runId,
|
|
112
|
+
status: state.status,
|
|
113
|
+
lifecycle: "completed",
|
|
114
|
+
reason: parsed.reason,
|
|
115
|
+
reconciliationArtifactPath: artifactRelativePath,
|
|
116
|
+
runDir,
|
|
117
|
+
}, null, 2));
|
|
118
|
+
}
|
|
@@ -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
|
}
|