@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,259 @@
|
|
|
1
|
+
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { compileWorkflowToDag } from "../workflows/dynamic/compile.js";
|
|
5
|
+
import { assertValidWorkflowSpec, parseWorkflowSpec, } from "../workflows/dynamic/validate.js";
|
|
6
|
+
import { assertValidDagSpec } from "../workflows/dag/validate.js";
|
|
7
|
+
import { locateDagRun, readDagRunState } from "../workflows/dag/lifecycle.js";
|
|
8
|
+
import { runDag } from "../workflows/dag/runner.js";
|
|
9
|
+
function repoWorkflowDir(repoRoot) {
|
|
10
|
+
return path.join(repoRoot, ".loop-agent", "workflows");
|
|
11
|
+
}
|
|
12
|
+
function personalWorkflowDir() {
|
|
13
|
+
return path.join(os.homedir(), ".loop-agent", "workflows");
|
|
14
|
+
}
|
|
15
|
+
function workflowFileName(name) {
|
|
16
|
+
return name.endsWith(".workflow.json") ? name : `${name}.workflow.json`;
|
|
17
|
+
}
|
|
18
|
+
async function exists(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
await access(filePath);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function listWorkflowDir(dir, location) {
|
|
28
|
+
if (!(await exists(dir)))
|
|
29
|
+
return [];
|
|
30
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
31
|
+
return entries
|
|
32
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".workflow.json"))
|
|
33
|
+
.map((entry) => ({
|
|
34
|
+
name: entry.name.slice(0, -".workflow.json".length),
|
|
35
|
+
location,
|
|
36
|
+
path: path.join(dir, entry.name),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
async function listTaskLocalWorkflows(repoRoot) {
|
|
40
|
+
const tasksDir = path.join(repoRoot, ".harness", "tasks");
|
|
41
|
+
if (!(await exists(tasksDir)))
|
|
42
|
+
return [];
|
|
43
|
+
const tasks = await readdir(tasksDir, { withFileTypes: true });
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const task of tasks) {
|
|
46
|
+
if (!task.isDirectory())
|
|
47
|
+
continue;
|
|
48
|
+
const plannedDir = path.join(tasksDir, task.name, "workflows", "planned");
|
|
49
|
+
const planned = await listWorkflowDir(plannedDir, "task-local");
|
|
50
|
+
results.push(...planned.map((entry) => ({
|
|
51
|
+
...entry,
|
|
52
|
+
name: `${task.name}/${entry.name}`,
|
|
53
|
+
})));
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
async function listSavedWorkflows(repoRoot) {
|
|
58
|
+
return [
|
|
59
|
+
...(await listWorkflowDir(repoWorkflowDir(repoRoot), "repo")),
|
|
60
|
+
...(await listWorkflowDir(personalWorkflowDir(), "personal")),
|
|
61
|
+
...(await listTaskLocalWorkflows(repoRoot)),
|
|
62
|
+
].sort((a, b) => `${a.location}:${a.name}`.localeCompare(`${b.location}:${b.name}`));
|
|
63
|
+
}
|
|
64
|
+
async function resolveWorkflowPath(repoRoot, nameOrPath) {
|
|
65
|
+
const directPath = path.resolve(repoRoot, nameOrPath);
|
|
66
|
+
if (await exists(directPath)) {
|
|
67
|
+
return {
|
|
68
|
+
name: path.basename(directPath).replace(/\.workflow\.json$/, ""),
|
|
69
|
+
location: "path",
|
|
70
|
+
path: directPath,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const entries = await listSavedWorkflows(repoRoot);
|
|
74
|
+
const found = entries.find((entry) => entry.name === nameOrPath || workflowFileName(entry.name) === nameOrPath);
|
|
75
|
+
if (found)
|
|
76
|
+
return found;
|
|
77
|
+
throw new Error(`workflow not found: ${nameOrPath}`);
|
|
78
|
+
}
|
|
79
|
+
async function readWorkflowSpec(workflowPath, args = {}) {
|
|
80
|
+
const raw = JSON.parse(await readFile(workflowPath, "utf-8"));
|
|
81
|
+
const inputs = {
|
|
82
|
+
...(raw.inputs ?? {}),
|
|
83
|
+
...args,
|
|
84
|
+
};
|
|
85
|
+
return parseWorkflowSpec({ ...raw, inputs });
|
|
86
|
+
}
|
|
87
|
+
function readFlag(args, flag) {
|
|
88
|
+
const index = args.indexOf(flag);
|
|
89
|
+
if (index < 0)
|
|
90
|
+
return undefined;
|
|
91
|
+
const value = args[index + 1];
|
|
92
|
+
if (!value)
|
|
93
|
+
throw new Error(`${flag} requires a value`);
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
function requireFlag(args, flag) {
|
|
97
|
+
const value = readFlag(args, flag);
|
|
98
|
+
if (!value)
|
|
99
|
+
throw new Error(`${flag} is required`);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function parseArgsJson(raw) {
|
|
103
|
+
if (!raw)
|
|
104
|
+
return {};
|
|
105
|
+
const parsed = JSON.parse(raw);
|
|
106
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
107
|
+
throw new Error("--args must be a JSON object");
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
function stableJson(value) {
|
|
112
|
+
return JSON.stringify(sortJson(value), null, 2);
|
|
113
|
+
}
|
|
114
|
+
function sortJson(value) {
|
|
115
|
+
if (Array.isArray(value))
|
|
116
|
+
return value.map(sortJson);
|
|
117
|
+
if (typeof value !== "object" || value === null)
|
|
118
|
+
return value;
|
|
119
|
+
return Object.fromEntries(Object.entries(value)
|
|
120
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
121
|
+
.map(([key, entry]) => [key, sortJson(entry)]));
|
|
122
|
+
}
|
|
123
|
+
async function runWorkflowList(repoRoot) {
|
|
124
|
+
console.log(JSON.stringify({
|
|
125
|
+
mode: "workflow-list",
|
|
126
|
+
workflows: (await listSavedWorkflows(repoRoot)).map((entry) => ({
|
|
127
|
+
...entry,
|
|
128
|
+
path: path.relative(repoRoot, entry.path),
|
|
129
|
+
})),
|
|
130
|
+
}, null, 2));
|
|
131
|
+
}
|
|
132
|
+
async function runWorkflowInspect(repoRoot, name) {
|
|
133
|
+
if (!name)
|
|
134
|
+
throw new Error("usage: workflow inspect <name>");
|
|
135
|
+
const entry = await resolveWorkflowPath(repoRoot, name);
|
|
136
|
+
const spec = await readWorkflowSpec(entry.path);
|
|
137
|
+
console.log(JSON.stringify({
|
|
138
|
+
mode: "workflow-inspect",
|
|
139
|
+
name: spec.meta.name,
|
|
140
|
+
description: spec.meta.description ?? "",
|
|
141
|
+
riskLevel: spec.meta.riskLevel,
|
|
142
|
+
location: entry.location,
|
|
143
|
+
path: path.relative(repoRoot, entry.path),
|
|
144
|
+
nodes: spec.nodes.map((node) => ({
|
|
145
|
+
id: node.id,
|
|
146
|
+
type: node.type,
|
|
147
|
+
dependsOn: node.dependsOn,
|
|
148
|
+
})),
|
|
149
|
+
final: spec.final.from,
|
|
150
|
+
}, null, 2));
|
|
151
|
+
}
|
|
152
|
+
async function runWorkflowSave(repoRoot, args) {
|
|
153
|
+
const from = path.resolve(repoRoot, requireFlag(args, "--from"));
|
|
154
|
+
const name = requireFlag(args, "--name");
|
|
155
|
+
const scope = readFlag(args, "--scope") ?? "repo";
|
|
156
|
+
if (scope !== "repo" && scope !== "personal") {
|
|
157
|
+
throw new Error("workflow save --scope must be repo or personal");
|
|
158
|
+
}
|
|
159
|
+
const spec = await readWorkflowSpec(from);
|
|
160
|
+
assertValidWorkflowSpec(spec);
|
|
161
|
+
const dir = scope === "repo" ? repoWorkflowDir(repoRoot) : personalWorkflowDir();
|
|
162
|
+
const savedPath = path.join(dir, workflowFileName(name));
|
|
163
|
+
await mkdir(dir, { recursive: true });
|
|
164
|
+
await writeFile(savedPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
|
|
165
|
+
console.log(JSON.stringify({
|
|
166
|
+
mode: "workflow-save",
|
|
167
|
+
name,
|
|
168
|
+
scope,
|
|
169
|
+
sourcePath: from,
|
|
170
|
+
savedPath,
|
|
171
|
+
}, null, 2));
|
|
172
|
+
}
|
|
173
|
+
async function runWorkflowRun(repoRoot, name, args) {
|
|
174
|
+
if (!name)
|
|
175
|
+
throw new Error("usage: workflow run <name> [--args <json>]");
|
|
176
|
+
const entry = await resolveWorkflowPath(repoRoot, name);
|
|
177
|
+
const workflowArgs = parseArgsJson(readFlag(args, "--args"));
|
|
178
|
+
const spec = await readWorkflowSpec(entry.path, workflowArgs);
|
|
179
|
+
const compiled = compileWorkflowToDag(spec, { sourcePath: entry.path });
|
|
180
|
+
assertValidDagSpec(compiled.dag, { strictGovernance: true });
|
|
181
|
+
const summary = await runDag(compiled.dag, {
|
|
182
|
+
cwd: path.resolve(repoRoot, readFlag(args, "--cwd") ?? "."),
|
|
183
|
+
runId: readFlag(args, "--run-id"),
|
|
184
|
+
});
|
|
185
|
+
console.log(JSON.stringify({
|
|
186
|
+
mode: "workflow-run",
|
|
187
|
+
name: spec.meta.name,
|
|
188
|
+
sourcePath: entry.path,
|
|
189
|
+
runId: summary.runId,
|
|
190
|
+
status: summary.status,
|
|
191
|
+
runDir: summary.runDir,
|
|
192
|
+
replayed: false,
|
|
193
|
+
}, null, 2));
|
|
194
|
+
}
|
|
195
|
+
async function runWorkflowDiff(repoRoot, name, args) {
|
|
196
|
+
if (!name)
|
|
197
|
+
throw new Error("usage: workflow diff <name> --against <path>");
|
|
198
|
+
const against = path.resolve(repoRoot, requireFlag(args, "--against"));
|
|
199
|
+
const entry = await resolveWorkflowPath(repoRoot, name);
|
|
200
|
+
const left = await readWorkflowSpec(entry.path);
|
|
201
|
+
const right = await readWorkflowSpec(against);
|
|
202
|
+
const leftJson = stableJson(left);
|
|
203
|
+
const rightJson = stableJson(right);
|
|
204
|
+
console.log(JSON.stringify({
|
|
205
|
+
mode: "workflow-diff",
|
|
206
|
+
name,
|
|
207
|
+
savedPath: entry.path,
|
|
208
|
+
againstPath: against,
|
|
209
|
+
different: leftJson !== rightJson,
|
|
210
|
+
leftNodeCount: left.nodes.length,
|
|
211
|
+
rightNodeCount: right.nodes.length,
|
|
212
|
+
}, null, 2));
|
|
213
|
+
}
|
|
214
|
+
async function runWorkflowReplay(repoRoot, runId) {
|
|
215
|
+
if (!runId)
|
|
216
|
+
throw new Error("usage: workflow replay <runId>");
|
|
217
|
+
const located = await locateDagRun(repoRoot, runId);
|
|
218
|
+
if (!located)
|
|
219
|
+
throw new Error(`dag run not found: ${runId}`);
|
|
220
|
+
const state = await readDagRunState(located.runDir);
|
|
221
|
+
console.log(JSON.stringify({
|
|
222
|
+
mode: "workflow-replay",
|
|
223
|
+
runId,
|
|
224
|
+
lifecycle: located.lifecycle,
|
|
225
|
+
status: state.status,
|
|
226
|
+
runDir: located.runDir,
|
|
227
|
+
replayMode: "inspect-only",
|
|
228
|
+
reexecutedNodes: [],
|
|
229
|
+
note: "Replay is inspect-only in v0; completed artifacts are not re-executed.",
|
|
230
|
+
}, null, 2));
|
|
231
|
+
}
|
|
232
|
+
export async function runWorkflowCommand(repoRoot, args) {
|
|
233
|
+
const [subcommand, name, ...rest] = args;
|
|
234
|
+
if (subcommand === "list") {
|
|
235
|
+
await runWorkflowList(repoRoot);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (subcommand === "inspect") {
|
|
239
|
+
await runWorkflowInspect(repoRoot, name);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (subcommand === "save") {
|
|
243
|
+
await runWorkflowSave(repoRoot, [name, ...rest].filter(Boolean));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (subcommand === "run") {
|
|
247
|
+
await runWorkflowRun(repoRoot, name, rest);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (subcommand === "diff") {
|
|
251
|
+
await runWorkflowDiff(repoRoot, name, rest);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (subcommand === "replay") {
|
|
255
|
+
await runWorkflowReplay(repoRoot, name);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
throw new Error("usage: workflow <list|inspect|save|run|diff|replay> [name] [options]");
|
|
259
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { loadHarnessManifest } from '../governance/harness.js';
|
|
2
|
+
import { createWorktree } from '../task/worktree.js';
|
|
3
|
+
export async function runWorktreeCreate(repoRoot, args) {
|
|
4
|
+
// Extract optional flags
|
|
5
|
+
let base;
|
|
6
|
+
let branch;
|
|
7
|
+
const positionalArgs = [];
|
|
8
|
+
for (let i = 0; i < args.length; i++) {
|
|
9
|
+
const token = args[i];
|
|
10
|
+
if (token === '--base') {
|
|
11
|
+
base = args[++i];
|
|
12
|
+
if (!base)
|
|
13
|
+
throw new Error('--base requires a value');
|
|
14
|
+
}
|
|
15
|
+
else if (token === '--branch') {
|
|
16
|
+
branch = args[++i];
|
|
17
|
+
if (!branch)
|
|
18
|
+
throw new Error('--branch requires a value');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
positionalArgs.push(token);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const [taskId] = positionalArgs;
|
|
25
|
+
if (!taskId) {
|
|
26
|
+
throw new Error('usage: worktree create <task-id> [--base <branch>] [--branch <name>]');
|
|
27
|
+
}
|
|
28
|
+
const manifest = await loadHarnessManifest(repoRoot);
|
|
29
|
+
const result = await createWorktree(repoRoot, manifest, { taskId, branch, base });
|
|
30
|
+
console.log(JSON.stringify(result, null, 2));
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { loadHarnessManifest } from '../governance/harness.js';
|
|
2
|
+
import { removeWorktree } from '../task/worktree.js';
|
|
3
|
+
export async function runWorktreeRemove(repoRoot, args) {
|
|
4
|
+
let force = false;
|
|
5
|
+
let deleteBranch = false;
|
|
6
|
+
const positionalArgs = [];
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const token = args[i];
|
|
9
|
+
if (token === '--force') {
|
|
10
|
+
force = true;
|
|
11
|
+
}
|
|
12
|
+
else if (token === '--delete-branch') {
|
|
13
|
+
deleteBranch = true;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
positionalArgs.push(token);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const [taskId] = positionalArgs;
|
|
20
|
+
if (!taskId) {
|
|
21
|
+
throw new Error('usage: worktree remove <task-id> [--force] [--delete-branch]');
|
|
22
|
+
}
|
|
23
|
+
const manifest = await loadHarnessManifest(repoRoot);
|
|
24
|
+
await removeWorktree(repoRoot, manifest, { taskId, force, deleteBranch });
|
|
25
|
+
console.log(JSON.stringify({ ok: true, taskId, force, deleteBranch }, null, 2));
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Long-lived Cursor SDK worker (NDJSON on stdin/stdout).
|
|
4
|
+
* Spawned by cursor-worker-client when CURSOR_USE_WORKER=1.
|
|
5
|
+
*/
|
|
6
|
+
import { runCursorWorkerOnStreams } from './executors/cursor-worker-server.js';
|
|
7
|
+
process.env.CURSOR_WORKER_CHILD = '1';
|
|
8
|
+
await runCursorWorkerOnStreams(process.stdin, process.stdout);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DEFAULT_CURSOR_MODEL } from './cursor-executor.js';
|
|
2
|
+
import { DEFAULT_DAG_MODELS } from './model-routing.js';
|
|
3
|
+
export const TASK_COMPLEXITY_TO_DAG = {
|
|
4
|
+
small: 'LOW',
|
|
5
|
+
medium: 'MED',
|
|
6
|
+
large: 'HIGH',
|
|
7
|
+
};
|
|
8
|
+
export function resolveCursorModel(overrides = {}, cursorConfig) {
|
|
9
|
+
return overrides.taskModel ?? cursorConfig?.defaultModel ?? DEFAULT_CURSOR_MODEL;
|
|
10
|
+
}
|
|
11
|
+
export function resolveCursorModelForTaskConfig(taskConfig, cursorConfig) {
|
|
12
|
+
if (taskConfig.cursorModel?.trim()) {
|
|
13
|
+
return taskConfig.cursorModel.trim();
|
|
14
|
+
}
|
|
15
|
+
const dagLevel = TASK_COMPLEXITY_TO_DAG[taskConfig.complexity ?? 'medium'];
|
|
16
|
+
return DEFAULT_DAG_MODELS[dagLevel] ?? resolveCursorModel({}, cursorConfig);
|
|
17
|
+
}
|
|
18
|
+
export function resolveTaskExecutor(taskConfig, override) {
|
|
19
|
+
if (override)
|
|
20
|
+
return override;
|
|
21
|
+
return taskConfig.executor ?? 'pi';
|
|
22
|
+
}
|
|
23
|
+
export function resolveCursorExecutorConfig(manifest) {
|
|
24
|
+
const config = manifest.executors?.cursor;
|
|
25
|
+
if (config?.enabled === false)
|
|
26
|
+
return undefined;
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
export function assertCursorExecutorAvailable(manifest) {
|
|
30
|
+
const config = resolveCursorExecutorConfig(manifest);
|
|
31
|
+
if (!config) {
|
|
32
|
+
throw new Error('cursor executor is not registered in harness.json executors.cursor');
|
|
33
|
+
}
|
|
34
|
+
return config;
|
|
35
|
+
}
|
|
36
|
+
export function validateCursorTaskPreflight(taskConfig) {
|
|
37
|
+
if (taskConfig.executor !== 'cursor')
|
|
38
|
+
return;
|
|
39
|
+
const allowed = taskConfig.allowedPaths ?? [];
|
|
40
|
+
if (allowed.length === 0) {
|
|
41
|
+
// Read-only analysis tasks may omit allowedPaths; write tasks must declare scope explicitly.
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function assertCursorWriteTaskScope(taskConfig, changedPaths) {
|
|
46
|
+
if (taskConfig.executor !== 'cursor')
|
|
47
|
+
return;
|
|
48
|
+
if ((taskConfig.allowedPaths ?? []).length > 0)
|
|
49
|
+
return;
|
|
50
|
+
const codeChanges = changedPaths.filter((filePath) => !filePath.startsWith('.harness/tasks/'));
|
|
51
|
+
if (codeChanges.length > 0) {
|
|
52
|
+
throw new Error('cursor write task refused: allowedPaths is empty but git diff contains code changes. ' +
|
|
53
|
+
'Declare allowedPaths explicitly or keep the task read-only.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const MODIFY_LOG_MARKERS = ['修改摘要', '修改文件', '验证', '风险'];
|
|
2
|
+
const VERIFY_RESULT_MARKERS = ['命令', 'exit', 'pass', 'fail', '未运行'];
|
|
3
|
+
export function validateCursorModifyLog(content) {
|
|
4
|
+
const trimmed = content.trim();
|
|
5
|
+
if (!trimmed)
|
|
6
|
+
return ['修改记录.md is empty'];
|
|
7
|
+
if (!trimmed.includes('# 修改记录'))
|
|
8
|
+
return ['修改记录.md missing heading: # 修改记录'];
|
|
9
|
+
const missing = MODIFY_LOG_MARKERS.filter((marker) => !trimmed.includes(marker));
|
|
10
|
+
return missing.map((marker) => `修改记录.md missing section marker: ${marker}`);
|
|
11
|
+
}
|
|
12
|
+
export function validateCursorVerifyResult(content) {
|
|
13
|
+
const trimmed = content.trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
return ['验证结果.md is empty'];
|
|
16
|
+
if (!trimmed.includes('# 验证结果'))
|
|
17
|
+
return ['验证结果.md missing heading: # 验证结果'];
|
|
18
|
+
const hasCommandMarker = VERIFY_RESULT_MARKERS.some((marker) => trimmed.toLowerCase().includes(marker.toLowerCase()));
|
|
19
|
+
if (!hasCommandMarker) {
|
|
20
|
+
return ['验证结果.md missing verification command / exit / pass-fail evidence'];
|
|
21
|
+
}
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
export function validateCursorArtifacts(modifyLog, verifyResult) {
|
|
25
|
+
return [
|
|
26
|
+
...validateCursorModifyLog(modifyLog),
|
|
27
|
+
...validateCursorVerifyResult(verifyResult),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
export function isEmptyArtifactTemplate(content, templateMarker) {
|
|
31
|
+
const trimmed = content.trim();
|
|
32
|
+
return trimmed.includes(templateMarker) && trimmed.split('\n').filter((line) => line.trim().length > 0).length <= 3;
|
|
33
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { access, appendFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { buildCursorPrompt, buildRedactedExecutorRecord, } from './cursor-executor.js';
|
|
4
|
+
import { appendExecutorRecord } from '../shared/logger.js';
|
|
5
|
+
export function resolveDelegateExecutionMode(executor) {
|
|
6
|
+
if (executor === 'pi')
|
|
7
|
+
return 'pi-worktree';
|
|
8
|
+
if (executor === 'cursor')
|
|
9
|
+
return 'direct';
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
export function buildCursorExecutionLogRecord(input) {
|
|
13
|
+
const base = buildRedactedExecutorRecord({
|
|
14
|
+
executor: 'cursor',
|
|
15
|
+
model: input.model,
|
|
16
|
+
durationMs: input.result.durationMs,
|
|
17
|
+
failureCategory: input.result.failureCategory,
|
|
18
|
+
ok: input.result.ok,
|
|
19
|
+
status: input.result.status,
|
|
20
|
+
prompt: input.prompt,
|
|
21
|
+
result: input.result,
|
|
22
|
+
apiKey: input.apiKey,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
...base,
|
|
26
|
+
channel: input.context.channel,
|
|
27
|
+
executionMode: input.context.executionMode,
|
|
28
|
+
cwd: input.context.cwd,
|
|
29
|
+
taskId: input.context.taskId,
|
|
30
|
+
dagRunId: input.context.dagRunId,
|
|
31
|
+
dagNodeId: input.context.dagNodeId,
|
|
32
|
+
recordedAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function persistCursorExecutionLog(logsDir, record, fileName = 'executor.jsonl') {
|
|
36
|
+
await mkdir(logsDir, { recursive: true });
|
|
37
|
+
await appendFile(path.join(logsDir, fileName), `${JSON.stringify(record)}\n`, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
/** Append a unified record to `<taskDir>/logs/executor.jsonl`. */
|
|
40
|
+
export async function appendCursorExecutionLog(taskDir, record) {
|
|
41
|
+
await appendExecutorRecord(taskDir, record);
|
|
42
|
+
}
|
|
43
|
+
export async function persistCursorToolExecution(cwd, input) {
|
|
44
|
+
try {
|
|
45
|
+
await access(path.join(cwd, '.harness'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const cacheDir = path.join(cwd, '.harness', 'cache');
|
|
51
|
+
const record = buildCursorExecutionLogRecord({
|
|
52
|
+
context: { channel: 'tool', cwd },
|
|
53
|
+
model: input.model,
|
|
54
|
+
prompt: buildCursorPrompt(input.task),
|
|
55
|
+
result: input.result,
|
|
56
|
+
apiKey: input.apiKey,
|
|
57
|
+
});
|
|
58
|
+
await persistCursorExecutionLog(cacheDir, record, 'cursor-executions.jsonl');
|
|
59
|
+
}
|
|
60
|
+
export function formatCursorFailureMessage(category) {
|
|
61
|
+
switch (category) {
|
|
62
|
+
case 'missing-api-key':
|
|
63
|
+
return 'CURSOR_API_KEY is not set.';
|
|
64
|
+
case 'auth':
|
|
65
|
+
return 'Cursor API authentication failed.';
|
|
66
|
+
case 'network':
|
|
67
|
+
return 'Cursor API network error.';
|
|
68
|
+
case 'timeout':
|
|
69
|
+
return 'Cursor execution timed out.';
|
|
70
|
+
case 'invalid-output':
|
|
71
|
+
return 'Cursor output or artifacts are invalid.';
|
|
72
|
+
case 'path-violation':
|
|
73
|
+
return 'Cursor task violated path boundaries.';
|
|
74
|
+
case 'nonzero-exit':
|
|
75
|
+
return 'Cursor agent did not complete successfully.';
|
|
76
|
+
case 'success':
|
|
77
|
+
return 'OK';
|
|
78
|
+
default:
|
|
79
|
+
return 'Cursor execution failed.';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { redactPromptForLog, truncateOutput, } from "./cursor-executor.js";
|
|
4
|
+
import { formatCursorFailureMessage, } from "./cursor-execution-log.js";
|
|
5
|
+
const SUMMARY_STDOUT_MAX = 4_000;
|
|
6
|
+
const SUMMARY_STDERR_MAX = 2_000;
|
|
7
|
+
export function buildPromptRedactedMarkdown(prompt) {
|
|
8
|
+
const redacted = redactPromptForLog(prompt);
|
|
9
|
+
return [
|
|
10
|
+
"# Cursor prompt (redacted)",
|
|
11
|
+
"",
|
|
12
|
+
`- ${redacted}`,
|
|
13
|
+
"",
|
|
14
|
+
"Full prompt text is not stored. See `logs/executor.jsonl` for the same hash reference.",
|
|
15
|
+
"",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
export function buildCursorRecoverySteps(input) {
|
|
19
|
+
const taskId = input.taskId ?? "<task-id>";
|
|
20
|
+
const session = input.sessionName ?? `task-${taskId}`;
|
|
21
|
+
const lines = ["## Recovery steps", ""];
|
|
22
|
+
if (input.failureCategory) {
|
|
23
|
+
lines.push(`- **Failure**: ${formatCursorFailureMessage(input.failureCategory)} (\`${input.failureCategory}\`)`);
|
|
24
|
+
lines.push("");
|
|
25
|
+
}
|
|
26
|
+
if (input.channel === "delegate") {
|
|
27
|
+
lines.push("### Retry in-process (direct SDK)");
|
|
28
|
+
lines.push("```bash");
|
|
29
|
+
lines.push("cd .");
|
|
30
|
+
lines.push(`npm run dev -- delegate ${taskId} --executor cursor --auto-run`);
|
|
31
|
+
lines.push("```");
|
|
32
|
+
if (input.worktreePath) {
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push(`- Worktree: \`${input.worktreePath}\``);
|
|
35
|
+
}
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push("### Promote evidence");
|
|
38
|
+
lines.push("```bash");
|
|
39
|
+
lines.push(`npm run dev -- promote-run ${taskId} --run-id <run-id>`);
|
|
40
|
+
lines.push(`npm run dev -- closeout task ${taskId}`);
|
|
41
|
+
lines.push("```");
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push("### After closeout");
|
|
44
|
+
lines.push("```bash");
|
|
45
|
+
lines.push(`npm run dev -- harvest ${taskId}`);
|
|
46
|
+
lines.push("```");
|
|
47
|
+
}
|
|
48
|
+
else if (input.channel === "dag") {
|
|
49
|
+
lines.push("### Re-run DAG node");
|
|
50
|
+
lines.push("- Fix upstream outputs or prompts in the DAG JSON, then re-run `run-dag` with a new `--run-id`.");
|
|
51
|
+
lines.push("- Inspect `executor.jsonl` and per-node `result.summary.md` under `.harness/dag-runs/completed/<run-id>/` (or `active/<run-id>/` while the run is still in progress).");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
lines.push("### Pi cursor tool");
|
|
55
|
+
lines.push("- Retry with a clearer `task` and correct `cwd`.");
|
|
56
|
+
lines.push("- Check `.harness/cache/cursor-executions.jsonl` when cwd contains `.harness`.");
|
|
57
|
+
}
|
|
58
|
+
if (input.failureCategory === "missing-api-key") {
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push("- Export `CURSOR_API_KEY` before retrying.");
|
|
61
|
+
}
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
|
64
|
+
function formatTokenUsage(result) {
|
|
65
|
+
const rows = [];
|
|
66
|
+
if (result.tokensUsed !== undefined) {
|
|
67
|
+
rows.push(`| tokensUsed | ${result.tokensUsed} |`);
|
|
68
|
+
}
|
|
69
|
+
const usage = result.details?.usage;
|
|
70
|
+
if (typeof usage?.inputTokens === "number") {
|
|
71
|
+
rows.push(`| inputTokens | ${usage.inputTokens} |`);
|
|
72
|
+
}
|
|
73
|
+
if (typeof usage?.outputTokens === "number") {
|
|
74
|
+
rows.push(`| outputTokens | ${usage.outputTokens} |`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof usage?.totalTokens === "number" &&
|
|
77
|
+
result.tokensUsed === undefined) {
|
|
78
|
+
rows.push(`| totalTokens | ${usage.totalTokens} |`);
|
|
79
|
+
}
|
|
80
|
+
return rows;
|
|
81
|
+
}
|
|
82
|
+
export function buildResultSummaryMarkdown(input) {
|
|
83
|
+
const stdout = truncateOutput(input.result.stdout, SUMMARY_STDOUT_MAX);
|
|
84
|
+
const stderr = truncateOutput(input.result.stderr || "", SUMMARY_STDERR_MAX);
|
|
85
|
+
const recovery = input.recoveryLines ??
|
|
86
|
+
buildCursorRecoverySteps({
|
|
87
|
+
channel: input.context.channel,
|
|
88
|
+
taskId: input.context.taskId,
|
|
89
|
+
sessionName: input.context.taskId
|
|
90
|
+
? `task-${input.context.taskId}`
|
|
91
|
+
: undefined,
|
|
92
|
+
worktreePath: input.context.cwd,
|
|
93
|
+
failureCategory: input.result.ok
|
|
94
|
+
? undefined
|
|
95
|
+
: input.result.failureCategory,
|
|
96
|
+
});
|
|
97
|
+
const meta = [
|
|
98
|
+
"# Cursor execution summary",
|
|
99
|
+
"",
|
|
100
|
+
"## Run metadata",
|
|
101
|
+
"",
|
|
102
|
+
`| Field | Value |`,
|
|
103
|
+
`|-------|-------|`,
|
|
104
|
+
`| channel | ${input.context.channel} |`,
|
|
105
|
+
];
|
|
106
|
+
if (input.context.executionMode)
|
|
107
|
+
meta.push(`| executionMode | ${input.context.executionMode} |`);
|
|
108
|
+
if (input.context.taskId)
|
|
109
|
+
meta.push(`| taskId | ${input.context.taskId} |`);
|
|
110
|
+
if (input.context.dagRunId)
|
|
111
|
+
meta.push(`| dagRunId | ${input.context.dagRunId} |`);
|
|
112
|
+
if (input.context.dagNodeId)
|
|
113
|
+
meta.push(`| dagNodeId | ${input.context.dagNodeId} |`);
|
|
114
|
+
meta.push(`| cwd | ${input.context.cwd} |`, `| model | ${input.model} |`, `| ok | ${input.result.ok} |`, `| status | ${input.result.status} |`, `| failureCategory | ${input.result.failureCategory} |`, `| durationMs | ${input.result.durationMs} |`, ...formatTokenUsage(input.result), "", "## Prompt reference", "", `- ${redactPromptForLog(input.prompt)}`, "", "## Output", "");
|
|
115
|
+
if (stdout.text.trim()) {
|
|
116
|
+
meta.push("### stdout", "", "```text", stdout.text, "```", "");
|
|
117
|
+
if (stdout.truncated)
|
|
118
|
+
meta.push("_(stdout truncated)_", "");
|
|
119
|
+
}
|
|
120
|
+
if (stderr.text.trim()) {
|
|
121
|
+
meta.push("### stderr", "", "```text", stderr.text, "```", "");
|
|
122
|
+
if (stderr.truncated)
|
|
123
|
+
meta.push("_(stderr truncated)_", "");
|
|
124
|
+
}
|
|
125
|
+
meta.push(...recovery, "");
|
|
126
|
+
return meta.join("\n");
|
|
127
|
+
}
|
|
128
|
+
export async function writeCursorExecutorArtifacts(artifactsDir, input) {
|
|
129
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
130
|
+
const promptPath = path.join(artifactsDir, "prompt.redacted.md");
|
|
131
|
+
const summaryPath = path.join(artifactsDir, "result.summary.md");
|
|
132
|
+
await writeFile(promptPath, buildPromptRedactedMarkdown(input.prompt), "utf-8");
|
|
133
|
+
await writeFile(summaryPath, buildResultSummaryMarkdown(input), "utf-8");
|
|
134
|
+
return { promptPath, summaryPath };
|
|
135
|
+
}
|