@tea-agent/loop-agent 0.1.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 +121 -0
- package/CHANGELOG.md +37 -0
- package/README.md +144 -0
- package/bin/loop-agent.js +21 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/loop-agent.js +98 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli/catalog.js +259 -0
- package/dist/cli/help.js +55 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/program.js +505 -0
- package/dist/cli.js +12 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/cursor-prompt.js +222 -0
- package/dist/commands/cursor-worker.js +43 -0
- package/dist/commands/dag-approve.js +102 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +56 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +177 -0
- package/dist/commands/dag-resume.js +34 -0
- package/dist/commands/dag-run-task.js +470 -0
- package/dist/commands/dag-validate.js +186 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +132 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +5 -0
- package/dist/commands/doctor.js +50 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +195 -0
- package/dist/commands/knowledge.js +64 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +241 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/pi-prompt.js +181 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag.js +184 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +84 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/cursor-worker-entry.js +8 -0
- package/dist/executors/config-core.js +55 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/cursor-artifacts.js +33 -0
- package/dist/executors/cursor-execution-log.js +81 -0
- package/dist/executors/cursor-executor-artifacts.js +135 -0
- package/dist/executors/cursor-executor.js +468 -0
- package/dist/executors/cursor-run.js +115 -0
- package/dist/executors/cursor-tool.js +94 -0
- package/dist/executors/cursor-worker-client.js +213 -0
- package/dist/executors/cursor-worker-protocol.js +18 -0
- package/dist/executors/cursor-worker-server.js +54 -0
- package/dist/executors/cursor-worker.js +3 -0
- package/dist/executors/cursor.js +6 -0
- package/dist/executors/dag-cursor-executor.js +88 -0
- package/dist/executors/dag-pi-executor.js +322 -0
- package/dist/executors/dag-static-executor.js +45 -0
- package/dist/executors/dag.js +4 -0
- package/dist/executors/index.js +8 -0
- package/dist/executors/model-routing.js +60 -0
- package/dist/executors/pi-event-serializer.js +43 -0
- package/dist/executors/pi-executor.js +606 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +255 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/shell-executor.js +300 -0
- package/dist/executors/shell-presets.js +47 -0
- package/dist/executors/shell-verification.js +251 -0
- package/dist/executors/shell-write-guard.js +126 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +434 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +128 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +152 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +236 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +421 -0
- package/dist/records/promotion.js +199 -0
- package/dist/shared/artifacts-core.js +88 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +165 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +23 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +238 -0
- package/dist/shared/timeout-policy.js +19 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/task/config-types.js +97 -0
- package/dist/task/config.js +2 -0
- package/dist/task/delegate.js +220 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +9 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +149 -0
- package/dist/task/runtime.js +699 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +55 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +171 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/decision-envelope.js +502 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/executor-registry.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +111 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +405 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +855 -0
- package/dist/workflows/dag/knowledge-curator.js +162 -0
- package/dist/workflows/dag/lifecycle.js +484 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +130 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +226 -0
- package/dist/workflows/dag/repair-artifact.js +136 -0
- package/dist/workflows/dag/report.js +1019 -0
- package/dist/workflows/dag/runner.js +1677 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/skill-instructions.js +471 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +275 -0
- package/dist/workflows/dag/upstream-artifacts.js +95 -0
- package/dist/workflows/dag/validate.js +527 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +360 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +114 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions.js +1334 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +134 -0
- package/dist/workflows/loop/context.js +48 -0
- package/dist/workflows/loop/events.js +25 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +55 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +62 -0
- package/docs/agent-dag-recovery-playbook.md +158 -0
- package/docs/agent-dag-runner.md +40 -0
- package/docs/cursor-executor-usage.md +25 -0
- package/docs/decisions/README.md +3 -0
- package/docs/design/README.md +36 -0
- package/docs/development-principles.md +71 -0
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -0
- package/docs/exec-plans/README.md +6 -0
- package/docs/exec-plans/active/README.md +5 -0
- package/docs/exec-plans/completed/README.md +5 -0
- package/docs/feature-workflow.md +184 -0
- package/docs/harness-methodology-debugging.md +153 -0
- package/docs/harness-methodology-tdd.md +130 -0
- package/docs/harness-methodology-verification.md +27 -0
- package/docs/loop-agent-harness.md +42 -0
- package/docs/progress/README.md +3 -0
- package/docs/reports/README.md +3 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +423 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +195 -0
- package/docs/templates/agent-dag.final-verification.json +190 -0
- package/docs/templates/agent-dag.schema.json +316 -0
- package/docs/templates/agent-dag.supervised-implementation.json +500 -0
- package/docs/templates/exec-plan.md +64 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/hybrid-dag.json +193 -0
- package/docs/templates/progress-log.md +17 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +42 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/verification-matrix.md +30 -0
- package/examples/decision-gate-agent-dag.json +123 -0
- package/examples/example-dag.json +51 -0
- package/examples/hybrid-loop-agent-dag.json +194 -0
- package/harness.json +92 -0
- package/package.json +61 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/loop-agent/SKILL.md +260 -0
- package/skills/loop-agent/references/README.md +63 -0
- package/skills/loop-agent/references/command-reference.md +315 -0
- package/skills/loop-agent/references/harness-policy.md +258 -0
- package/skills/loop-agent/references/hybrid-dag.md +216 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/model-routing.md +36 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +85 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +83 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -0
- package/skills/loop-agent/references/task-workflow.md +84 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Prompt — one-shot CLI for Cursor SDK tasks.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* cursor-prompt "Fix lint errors" # one-shot
|
|
6
|
+
* cursor-prompt --stdin # read task from stdin
|
|
7
|
+
* cursor-prompt --file task.md # read task from file
|
|
8
|
+
* cursor-prompt --stream "Analyze this code" # stream output
|
|
9
|
+
* cursor-prompt --model gpt-5.5 "Deep analysis" # pick model
|
|
10
|
+
* cursor-prompt --cwd /path --timeout 120000 "task" # cwd + timeout
|
|
11
|
+
*/
|
|
12
|
+
import { executeCursorTask, DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, buildCursorPrompt, classifyCursorFailure, mapRunStatusToExecutionStatus, } from "../executors/cursor-executor.js";
|
|
13
|
+
import { createOneShotPromptParseAccum, formatUnknownFlagError, parseOneShotCommonFlag, printOneShotHelpAndExit, resolveOneShotCwd, resolveOneShotTask, } from "../shared/one-shot-prompt-args.js";
|
|
14
|
+
import { persistCursorRunLog, computeRunDir } from "../records/one-shot-runs.js";
|
|
15
|
+
const CURSOR_PROMPT_USAGE = [
|
|
16
|
+
"usage: cursor-prompt [options] [task]",
|
|
17
|
+
"",
|
|
18
|
+
' cursor-prompt "Fix lint errors in src/" One-shot task',
|
|
19
|
+
" cursor-prompt --stdin Read task from stdin",
|
|
20
|
+
" cursor-prompt --file task.md Read task from a file",
|
|
21
|
+
' cursor-prompt --stream "Analyze this" Stream output in real-time',
|
|
22
|
+
' cursor-prompt --model gpt-5.5 "Deep analysis" Use a specific model',
|
|
23
|
+
' cursor-prompt --cwd /path --timeout 60000 "task" Custom cwd + timeout',
|
|
24
|
+
" cursor-prompt --list-models List commonly used models",
|
|
25
|
+
"",
|
|
26
|
+
"options:",
|
|
27
|
+
" --stdin Read task from stdin (ignores positional task)",
|
|
28
|
+
" --file <path> Read task from a file",
|
|
29
|
+
" --stream Stream agent output in real-time",
|
|
30
|
+
" --model <id> Model id (default: account default)",
|
|
31
|
+
" --cwd <path> Working directory (default: cwd)",
|
|
32
|
+
" --timeout <ms> Timeout in ms (default: 1,800,000 = 30min)",
|
|
33
|
+
" --list-models List commonly used models",
|
|
34
|
+
" --no-stream Force non-streaming output (default for non-tty)",
|
|
35
|
+
].join("\n");
|
|
36
|
+
const COMMON_MODELS = {
|
|
37
|
+
"composer-2.5": "Cursor Composer 2.5 (fast, small tasks)",
|
|
38
|
+
"composer-3": "Cursor Composer 3",
|
|
39
|
+
default: "Account default",
|
|
40
|
+
"gpt-5.5": "GPT 5.5 (deep reasoning)",
|
|
41
|
+
"gpt-5": "GPT 5",
|
|
42
|
+
"claude-sonnet-4": "Claude Sonnet 4",
|
|
43
|
+
"claude-opus-4": "Claude Opus 4",
|
|
44
|
+
"gemini-3-flash": "Gemini 3 Flash (fast/cheap)",
|
|
45
|
+
"gemini-3-pro": "Gemini 3 Pro",
|
|
46
|
+
};
|
|
47
|
+
export function listModels() {
|
|
48
|
+
console.log("Common Cursor models:\n");
|
|
49
|
+
for (const [id, desc] of Object.entries(COMMON_MODELS)) {
|
|
50
|
+
console.log(` ${id.padEnd(22)} ${desc}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function parseCursorPromptArgs(args) {
|
|
54
|
+
const accum = createOneShotPromptParseAccum();
|
|
55
|
+
let model;
|
|
56
|
+
let stream;
|
|
57
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
58
|
+
const common = parseOneShotCommonFlag(args, i, accum);
|
|
59
|
+
if (common.kind === "help") {
|
|
60
|
+
printOneShotHelpAndExit(CURSOR_PROMPT_USAGE);
|
|
61
|
+
}
|
|
62
|
+
if (common.kind === "consumed") {
|
|
63
|
+
i = common.nextIndex;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const arg = args[i];
|
|
67
|
+
if (arg === "--stream") {
|
|
68
|
+
stream = true;
|
|
69
|
+
}
|
|
70
|
+
else if (arg === "--no-stream") {
|
|
71
|
+
stream = false;
|
|
72
|
+
}
|
|
73
|
+
else if (arg === "--list-models") {
|
|
74
|
+
return { listModels: true };
|
|
75
|
+
}
|
|
76
|
+
else if (arg === "--model") {
|
|
77
|
+
model = args[i + 1];
|
|
78
|
+
if (!model)
|
|
79
|
+
throw new Error("--model requires a model id");
|
|
80
|
+
i += 1;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw formatUnknownFlagError(arg, CURSOR_PROMPT_USAGE);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const task = resolveOneShotTask(accum, CURSOR_PROMPT_USAGE);
|
|
87
|
+
// Default: stream if stdout is a TTY
|
|
88
|
+
if (stream === undefined) {
|
|
89
|
+
stream = process.stdout.isTTY === true;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
task,
|
|
93
|
+
cwd: resolveOneShotCwd(accum.cwd),
|
|
94
|
+
model,
|
|
95
|
+
timeoutMs: accum.timeoutMs,
|
|
96
|
+
stream,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export async function runCursorPrompt(args) {
|
|
100
|
+
const effectiveCwd = args.cwd ?? process.cwd();
|
|
101
|
+
const model = args.model ?? DEFAULT_CURSOR_MODEL;
|
|
102
|
+
const timeoutMs = args.timeoutMs ?? DEFAULT_CURSOR_TIMEOUT_MS;
|
|
103
|
+
if (args.stream) {
|
|
104
|
+
await runCursorPromptStreaming(args.task, effectiveCwd, model, timeoutMs);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
await runCursorPromptBatch(args.task, effectiveCwd, model, timeoutMs);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function runCursorPromptBatch(task, cwd, model, timeoutMs) {
|
|
111
|
+
const startedAt = Date.now();
|
|
112
|
+
process.stderr.write(`[cursor-prompt] running "${task.slice(0, 80)}${task.length > 80 ? "..." : ""}" (model=${model}, cwd=${cwd})\n`);
|
|
113
|
+
const runDir = (await computeRunDir(cwd, task)) ?? undefined;
|
|
114
|
+
const result = await executeCursorTask({ task, cwd, model, timeoutMs, runDir });
|
|
115
|
+
// Persist human-readable run log under .harness/runs/
|
|
116
|
+
await persistCursorRunLog(cwd, {
|
|
117
|
+
task,
|
|
118
|
+
model,
|
|
119
|
+
channel: 'tool',
|
|
120
|
+
cwd,
|
|
121
|
+
result,
|
|
122
|
+
runDir,
|
|
123
|
+
});
|
|
124
|
+
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
125
|
+
if (result.ok) {
|
|
126
|
+
process.stderr.write(`[cursor-prompt] done in ${elapsed}s, status=${result.status}\n`);
|
|
127
|
+
process.stdout.write(result.stdout);
|
|
128
|
+
if (!result.stdout.endsWith("\n"))
|
|
129
|
+
process.stdout.write("\n");
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const stderr = result.stderr || result.stdout || "(no output)";
|
|
133
|
+
process.stderr.write(`[cursor-prompt] FAILED in ${elapsed}s (${result.failureCategory}):\n${stderr}\n`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
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
|
+
const startedAt = Date.now();
|
|
146
|
+
process.stderr.write(`[cursor-prompt] streaming (model=${model}, cwd=${cwd})\n`);
|
|
147
|
+
let timeoutHandle;
|
|
148
|
+
try {
|
|
149
|
+
const { Agent } = await import("@cursor/sdk");
|
|
150
|
+
const agent = await Agent.create({
|
|
151
|
+
apiKey,
|
|
152
|
+
name: "pi-cursor-prompt",
|
|
153
|
+
model: { id: model },
|
|
154
|
+
local: { cwd },
|
|
155
|
+
});
|
|
156
|
+
const runPromise = (async () => {
|
|
157
|
+
const run = await agent.send(prompt);
|
|
158
|
+
for await (const event of run.stream()) {
|
|
159
|
+
if (event.type === "assistant") {
|
|
160
|
+
for (const block of event.message.content) {
|
|
161
|
+
if (block.type === "text") {
|
|
162
|
+
process.stdout.write(block.text);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const result = await run.wait();
|
|
168
|
+
await agent[Symbol.asyncDispose]();
|
|
169
|
+
return result;
|
|
170
|
+
})();
|
|
171
|
+
const timeoutRace = new Promise((resolve) => {
|
|
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
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
212
|
+
process.stderr.write(`\n[cursor-prompt] ERROR: ${message}\n`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
if (timeoutHandle)
|
|
217
|
+
clearTimeout(timeoutHandle);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function printCursorPromptUsage() {
|
|
221
|
+
console.log(CURSOR_PROMPT_USAGE);
|
|
222
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { executeCursorTaskViaWorker, getCursorWorkerEntryPath, isCursorWorkerChild, isCursorWorkerEnabled, isCursorWorkerRunning, stopCursorWorker, } from '../executors/cursor-worker-client.js';
|
|
2
|
+
export function parseCursorWorkerArgs(args) {
|
|
3
|
+
const action = args[0];
|
|
4
|
+
if (action === 'status' || action === 'stop' || action === 'ping') {
|
|
5
|
+
return { action };
|
|
6
|
+
}
|
|
7
|
+
throw new Error('usage: cursor-worker <status|stop|ping>');
|
|
8
|
+
}
|
|
9
|
+
export async function runCursorWorker(repoRoot, rawArgs) {
|
|
10
|
+
const { action } = parseCursorWorkerArgs(rawArgs);
|
|
11
|
+
if (action === 'status') {
|
|
12
|
+
console.log(JSON.stringify({
|
|
13
|
+
enabled: isCursorWorkerEnabled(),
|
|
14
|
+
running: isCursorWorkerRunning(),
|
|
15
|
+
child: isCursorWorkerChild(),
|
|
16
|
+
entry: getCursorWorkerEntryPath(),
|
|
17
|
+
repoRoot,
|
|
18
|
+
}, null, 2));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (action === 'stop') {
|
|
22
|
+
await stopCursorWorker();
|
|
23
|
+
console.log('cursor worker stopped');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (action === 'ping') {
|
|
27
|
+
if (!isCursorWorkerEnabled()) {
|
|
28
|
+
console.error('Set CURSOR_USE_WORKER=1 to enable the worker path.');
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const result = await executeCursorTaskViaWorker({
|
|
32
|
+
task: 'Reply with exactly: WORKER_PING_OK',
|
|
33
|
+
cwd: repoRoot,
|
|
34
|
+
model: 'default',
|
|
35
|
+
timeoutMs: 120_000,
|
|
36
|
+
});
|
|
37
|
+
console.log(JSON.stringify({ ok: result.ok, stdout: result.stdout.trim() }, null, 2));
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await stopCursorWorker();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { assertDagRunTransferTargetAvailable, getDagRunDir, readHumanEscalation, requirePausedDagRun, transferDagRunDir, writeDagRunState, writeHumanApprovalArtifact, } from "../workflows/dag/lifecycle.js";
|
|
3
|
+
export function parseDagApproveArgs(args) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
throw new Error("usage: dag approve --run-id <id> --option <A|B|...> [--notes \"...\"]");
|
|
6
|
+
}
|
|
7
|
+
let runId;
|
|
8
|
+
let option;
|
|
9
|
+
let notes;
|
|
10
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
if (arg === "--run-id") {
|
|
13
|
+
runId = args[++i];
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (arg.startsWith("--run-id=")) {
|
|
17
|
+
runId = arg.slice("--run-id=".length);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (arg === "--option") {
|
|
21
|
+
option = args[++i];
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg.startsWith("--option=")) {
|
|
25
|
+
option = arg.slice("--option=".length);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--notes") {
|
|
29
|
+
notes = args[++i];
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg.startsWith("--notes=")) {
|
|
33
|
+
notes = arg.slice("--notes=".length);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg.startsWith("-")) {
|
|
37
|
+
throw new Error(`unknown dag approve flag: ${arg}`);
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
40
|
+
}
|
|
41
|
+
if (!runId) {
|
|
42
|
+
throw new Error("dag approve requires --run-id <id>");
|
|
43
|
+
}
|
|
44
|
+
if (!option) {
|
|
45
|
+
throw new Error("dag approve requires --option <id>");
|
|
46
|
+
}
|
|
47
|
+
return { runId, option, notes };
|
|
48
|
+
}
|
|
49
|
+
export async function runDagApprove(repoRoot, rawArgs) {
|
|
50
|
+
const parsed = parseDagApproveArgs(rawArgs);
|
|
51
|
+
const { runDir, state, spec } = await requirePausedDagRun(repoRoot, parsed.runId);
|
|
52
|
+
const nodeId = state.pausedByNodeId;
|
|
53
|
+
const node = state.nodes[nodeId];
|
|
54
|
+
if (!node) {
|
|
55
|
+
throw new Error(`paused node ${nodeId} not found in run state`);
|
|
56
|
+
}
|
|
57
|
+
const escalation = await readHumanEscalation(runDir, nodeId);
|
|
58
|
+
const optionIds = new Set(escalation.humanEscalation.options.map((entry) => entry.id));
|
|
59
|
+
if (!optionIds.has(parsed.option)) {
|
|
60
|
+
throw new Error(`option ${parsed.option} is not valid; expected one of: ${[...optionIds].join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
const activeRunDir = getDagRunDir(repoRoot, "active", state.runId);
|
|
63
|
+
await assertDagRunTransferTargetAvailable(activeRunDir);
|
|
64
|
+
const approvedAt = new Date().toISOString();
|
|
65
|
+
const artifact = {
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
runId: state.runId,
|
|
68
|
+
nodeId,
|
|
69
|
+
decision: "approved",
|
|
70
|
+
selectedOption: parsed.option,
|
|
71
|
+
approvedAt,
|
|
72
|
+
escalationArtifactPath: node.escalationArtifactPath,
|
|
73
|
+
};
|
|
74
|
+
if (parsed.notes) {
|
|
75
|
+
artifact.notes = parsed.notes;
|
|
76
|
+
}
|
|
77
|
+
const approvalPath = await writeHumanApprovalArtifact({
|
|
78
|
+
runDir,
|
|
79
|
+
nodeId,
|
|
80
|
+
artifact,
|
|
81
|
+
});
|
|
82
|
+
node.pauseReason = undefined;
|
|
83
|
+
node.humanApprovalArtifactPath = approvalPath;
|
|
84
|
+
state.status = "running";
|
|
85
|
+
state.humanDecisionNodeId = nodeId;
|
|
86
|
+
delete state.pausedAt;
|
|
87
|
+
delete state.pausedByNodeId;
|
|
88
|
+
delete state.pauseReason;
|
|
89
|
+
await writeDagRunState(runDir, state);
|
|
90
|
+
const nextRunDir = await transferDagRunDir(runDir, activeRunDir);
|
|
91
|
+
console.log(JSON.stringify({
|
|
92
|
+
action: "approve",
|
|
93
|
+
runId: state.runId,
|
|
94
|
+
nodeId,
|
|
95
|
+
selectedOption: parsed.option,
|
|
96
|
+
approvalPath: path.relative(repoRoot, approvalPath),
|
|
97
|
+
runDir: nextRunDir,
|
|
98
|
+
title: spec.title,
|
|
99
|
+
status: state.status,
|
|
100
|
+
nextRecommendedAction: "Run is approved; use dag resume to continue.",
|
|
101
|
+
}, null, 2));
|
|
102
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { defaultFinalVerificationDagOutputPath, initFinalVerificationDagFromTask, } from '../workflows/dag/final-verification.js';
|
|
2
|
+
import { resolveCliPath } from '../shared/path-refs.js';
|
|
3
|
+
export function parseDagFinalVerificationArgs(args) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
throw new Error('usage: dag final-verification <task-id> [--output <path>] [--task-glob <pattern>] [--closeout <path>]');
|
|
6
|
+
}
|
|
7
|
+
let taskId;
|
|
8
|
+
let outputPath;
|
|
9
|
+
let taskGlob;
|
|
10
|
+
let closeoutPath;
|
|
11
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg === '--output' || arg === '-o') {
|
|
14
|
+
outputPath = requiredValue(args[++i], '--output');
|
|
15
|
+
}
|
|
16
|
+
else if (arg.startsWith('--output=')) {
|
|
17
|
+
outputPath = arg.slice('--output='.length);
|
|
18
|
+
}
|
|
19
|
+
else if (arg === '--task-glob') {
|
|
20
|
+
taskGlob = requiredValue(args[++i], '--task-glob');
|
|
21
|
+
}
|
|
22
|
+
else if (arg.startsWith('--task-glob=')) {
|
|
23
|
+
taskGlob = arg.slice('--task-glob='.length);
|
|
24
|
+
}
|
|
25
|
+
else if (arg === '--closeout') {
|
|
26
|
+
closeoutPath = requiredValue(args[++i], '--closeout');
|
|
27
|
+
}
|
|
28
|
+
else if (arg.startsWith('--closeout=')) {
|
|
29
|
+
closeoutPath = arg.slice('--closeout='.length);
|
|
30
|
+
}
|
|
31
|
+
else if (arg.startsWith('-')) {
|
|
32
|
+
throw new Error(`unknown dag final-verification flag: ${arg}`);
|
|
33
|
+
}
|
|
34
|
+
else if (taskId) {
|
|
35
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
taskId = arg;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!taskId) {
|
|
42
|
+
throw new Error('dag final-verification requires <task-id>');
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
taskId,
|
|
46
|
+
outputPath: outputPath ? resolveCliPath(outputPath) : undefined,
|
|
47
|
+
taskGlob,
|
|
48
|
+
closeoutPath: closeoutPath ? resolveCliPath(closeoutPath) : undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function runDagFinalVerification(repoRoot, rawArgs) {
|
|
52
|
+
const parsed = parseDagFinalVerificationArgs(rawArgs);
|
|
53
|
+
const result = await initFinalVerificationDagFromTask(repoRoot, parsed.taskId, {
|
|
54
|
+
outputPath: parsed.outputPath,
|
|
55
|
+
taskGlob: parsed.taskGlob,
|
|
56
|
+
closeoutPath: parsed.closeoutPath,
|
|
57
|
+
});
|
|
58
|
+
console.log(JSON.stringify({
|
|
59
|
+
ok: true,
|
|
60
|
+
taskId: result.taskId,
|
|
61
|
+
outputPath: result.outputPath,
|
|
62
|
+
defaultOutputPath: defaultFinalVerificationDagOutputPath(parsed.taskId),
|
|
63
|
+
taskCount: result.taskCount,
|
|
64
|
+
nodeIds: result.nodeIds,
|
|
65
|
+
next: [
|
|
66
|
+
`Review ${result.outputPath}`,
|
|
67
|
+
`npm run dev -- dag validate --dag ${result.outputPath} --strict-models --strict-governance`,
|
|
68
|
+
`npm run dev -- run-dag --dag ${result.outputPath} --cwd ${repoRoot}`,
|
|
69
|
+
],
|
|
70
|
+
}, null, 2));
|
|
71
|
+
}
|
|
72
|
+
function requiredValue(value, flag) {
|
|
73
|
+
if (!value)
|
|
74
|
+
throw new Error(`dag final-verification requires a value for ${flag}`);
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { defaultHybridDagOutputPath, initHybridDagFromTask, } from "../workflows/dag/init-hybrid.js";
|
|
3
|
+
export function parseDagInitHybridArgs(args) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
throw new Error("usage: dag init-hybrid <task-id> [--output <path>]");
|
|
6
|
+
}
|
|
7
|
+
let taskId;
|
|
8
|
+
let outputPath;
|
|
9
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
if (arg === "--output" || arg === "-o") {
|
|
12
|
+
outputPath = args[++i];
|
|
13
|
+
if (!outputPath) {
|
|
14
|
+
throw new Error("dag init-hybrid --output requires a path");
|
|
15
|
+
}
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (arg.startsWith("--output=")) {
|
|
19
|
+
outputPath = arg.slice("--output=".length);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg.startsWith("-")) {
|
|
23
|
+
throw new Error(`unknown dag init-hybrid flag: ${arg}`);
|
|
24
|
+
}
|
|
25
|
+
if (taskId) {
|
|
26
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
27
|
+
}
|
|
28
|
+
taskId = arg;
|
|
29
|
+
}
|
|
30
|
+
if (!taskId) {
|
|
31
|
+
throw new Error("dag init-hybrid requires <task-id>");
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
taskId,
|
|
35
|
+
outputPath: outputPath ? path.resolve(outputPath) : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function runDagInitHybrid(repoRoot, rawArgs) {
|
|
39
|
+
const parsed = parseDagInitHybridArgs(rawArgs);
|
|
40
|
+
const result = await initHybridDagFromTask(repoRoot, parsed.taskId, {
|
|
41
|
+
outputPath: parsed.outputPath,
|
|
42
|
+
});
|
|
43
|
+
console.log(JSON.stringify({
|
|
44
|
+
ok: true,
|
|
45
|
+
taskId: result.taskId,
|
|
46
|
+
outputPath: result.outputPath,
|
|
47
|
+
defaultOutputPath: defaultHybridDagOutputPath(parsed.taskId),
|
|
48
|
+
taskCount: result.taskCount,
|
|
49
|
+
nodeIds: result.nodeIds,
|
|
50
|
+
next: [
|
|
51
|
+
`Review ${result.outputPath}`,
|
|
52
|
+
`npm run dev -- dag validate --dag ${result.outputPath}`,
|
|
53
|
+
"Use run-dag --dry-run only when you intentionally need an active run snapshot.",
|
|
54
|
+
],
|
|
55
|
+
}, null, 2));
|
|
56
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatTaskReconcileJson, formatTaskReconcileMarkdown, runTaskReconcile, } from '../workflows/dag/reconcile-tasks.js';
|
|
2
|
+
export async function runDagReconcileTasks(repoRoot, args) {
|
|
3
|
+
const options = parseDagReconcileTasksArgs(args);
|
|
4
|
+
const report = await runTaskReconcile(repoRoot, {
|
|
5
|
+
glob: options.glob,
|
|
6
|
+
patch: options.patch,
|
|
7
|
+
});
|
|
8
|
+
const output = options.markdown
|
|
9
|
+
? formatTaskReconcileMarkdown(report)
|
|
10
|
+
: formatTaskReconcileJson(report);
|
|
11
|
+
process.stdout.write(output);
|
|
12
|
+
if (!report.ok) {
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function parseDagReconcileTasksArgs(args) {
|
|
17
|
+
let glob = '*';
|
|
18
|
+
let patch = false;
|
|
19
|
+
let json = false;
|
|
20
|
+
let markdown = false;
|
|
21
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
if (arg === '--glob') {
|
|
24
|
+
glob = requiredValue(args[++i], '--glob');
|
|
25
|
+
}
|
|
26
|
+
else if (arg.startsWith('--glob=')) {
|
|
27
|
+
glob = arg.slice('--glob='.length);
|
|
28
|
+
}
|
|
29
|
+
else if (arg === '--patch') {
|
|
30
|
+
patch = true;
|
|
31
|
+
}
|
|
32
|
+
else if (arg === '--json') {
|
|
33
|
+
json = true;
|
|
34
|
+
}
|
|
35
|
+
else if (arg === '--markdown') {
|
|
36
|
+
markdown = true;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new Error(`unknown dag reconcile-tasks flag: ${arg}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (json && markdown) {
|
|
43
|
+
throw new Error('dag reconcile-tasks accepts only one of --json or --markdown');
|
|
44
|
+
}
|
|
45
|
+
return { glob, patch, json, markdown };
|
|
46
|
+
}
|
|
47
|
+
function requiredValue(value, flag) {
|
|
48
|
+
if (!value)
|
|
49
|
+
throw new Error(`dag reconcile-tasks requires a value for ${flag}`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { assertDagRunTransferTargetAvailable, getDagRunDir, requirePausedDagRun, transferDagRunDir, writeDagRunState, writeHumanRejectionArtifact, } from "../workflows/dag/lifecycle.js";
|
|
3
|
+
export function parseDagRejectArgs(args) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
throw new Error('usage: dag reject --run-id <id> --reason "..."');
|
|
6
|
+
}
|
|
7
|
+
let runId;
|
|
8
|
+
let reason;
|
|
9
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
if (arg === "--run-id") {
|
|
12
|
+
runId = args[++i];
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (arg.startsWith("--run-id=")) {
|
|
16
|
+
runId = arg.slice("--run-id=".length);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (arg === "--reason") {
|
|
20
|
+
reason = args[++i];
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (arg.startsWith("--reason=")) {
|
|
24
|
+
reason = arg.slice("--reason=".length);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg.startsWith("-")) {
|
|
28
|
+
throw new Error(`unknown dag reject flag: ${arg}`);
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`unexpected positional argument: ${arg}`);
|
|
31
|
+
}
|
|
32
|
+
if (!runId) {
|
|
33
|
+
throw new Error("dag reject requires --run-id <id>");
|
|
34
|
+
}
|
|
35
|
+
if (!reason?.trim()) {
|
|
36
|
+
throw new Error("dag reject requires --reason <text>");
|
|
37
|
+
}
|
|
38
|
+
return { runId, reason: reason.trim() };
|
|
39
|
+
}
|
|
40
|
+
export async function runDagReject(repoRoot, rawArgs) {
|
|
41
|
+
const parsed = parseDagRejectArgs(rawArgs);
|
|
42
|
+
const { runDir, state } = await requirePausedDagRun(repoRoot, parsed.runId);
|
|
43
|
+
const nodeId = state.pausedByNodeId;
|
|
44
|
+
const node = state.nodes[nodeId];
|
|
45
|
+
if (!node) {
|
|
46
|
+
throw new Error(`paused node ${nodeId} not found in run state`);
|
|
47
|
+
}
|
|
48
|
+
const completedRunDir = getDagRunDir(repoRoot, "completed", state.runId);
|
|
49
|
+
await assertDagRunTransferTargetAvailable(completedRunDir);
|
|
50
|
+
const rejectedAt = new Date().toISOString();
|
|
51
|
+
const artifact = {
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
runId: state.runId,
|
|
54
|
+
nodeId,
|
|
55
|
+
decision: "rejected",
|
|
56
|
+
reason: parsed.reason,
|
|
57
|
+
rejectedAt,
|
|
58
|
+
};
|
|
59
|
+
const rejectionPath = await writeHumanRejectionArtifact({
|
|
60
|
+
runDir,
|
|
61
|
+
nodeId,
|
|
62
|
+
artifact,
|
|
63
|
+
});
|
|
64
|
+
node.pauseReason = undefined;
|
|
65
|
+
node.humanRejectionArtifactPath = rejectionPath;
|
|
66
|
+
for (const pendingNode of Object.values(state.nodes)) {
|
|
67
|
+
if (pendingNode.status === "PENDING") {
|
|
68
|
+
pendingNode.status = "SKIPPED";
|
|
69
|
+
pendingNode.skippedReason = "human rejection";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
state.status = "failed";
|
|
73
|
+
state.failureCategory = "human-rejected";
|
|
74
|
+
state.humanDecisionNodeId = nodeId;
|
|
75
|
+
state.finishedAt = rejectedAt;
|
|
76
|
+
delete state.pausedAt;
|
|
77
|
+
delete state.pausedByNodeId;
|
|
78
|
+
delete state.pauseReason;
|
|
79
|
+
await writeDagRunState(runDir, state);
|
|
80
|
+
const nextRunDir = await transferDagRunDir(runDir, completedRunDir);
|
|
81
|
+
console.log(JSON.stringify({
|
|
82
|
+
action: "reject",
|
|
83
|
+
runId: state.runId,
|
|
84
|
+
nodeId,
|
|
85
|
+
reason: parsed.reason,
|
|
86
|
+
failureCategory: state.failureCategory,
|
|
87
|
+
rejectionPath: path.relative(repoRoot, rejectionPath),
|
|
88
|
+
runDir: nextRunDir,
|
|
89
|
+
status: state.status,
|
|
90
|
+
}, null, 2));
|
|
91
|
+
}
|