@tea-agent/loop-agent 0.9.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.
Files changed (183) hide show
  1. package/AGENTS.md +8 -0
  2. package/CHANGELOG.md +92 -11
  3. package/README.md +69 -5
  4. package/dist/application/dag/args.js +4 -14
  5. package/dist/application/dag/generate-task-dag.js +32 -2
  6. package/dist/application/dag/run-dag.js +1 -27
  7. package/dist/application/dag/validate-dag.js +2 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +7 -11
  10. package/dist/cli/program.js +9 -21
  11. package/dist/commands/cursor-prompt.js +42 -82
  12. package/dist/commands/dag-approve.js +36 -0
  13. package/dist/commands/dag-reconcile-run.js +118 -0
  14. package/dist/commands/delegate.js +75 -77
  15. package/dist/commands/doctor.js +0 -18
  16. package/dist/commands/init.js +60 -40
  17. package/dist/commands/instructions.js +7 -10
  18. package/dist/commands/loop.js +4 -20
  19. package/dist/executors/config-core.js +0 -51
  20. package/dist/executors/dag-pi-executor.js +1 -1
  21. package/dist/executors/dag.js +0 -1
  22. package/dist/executors/index.js +0 -2
  23. package/dist/executors/model-routing.js +9 -9
  24. package/dist/executors/shell-executor.js +1 -1
  25. package/dist/governance/checks.js +6 -3
  26. package/dist/governance/manifest-types.js +33 -2
  27. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  28. package/dist/records/harvest.js +2 -23
  29. package/dist/records/one-shot-runs.js +1 -1
  30. package/dist/shared/artifacts-core.js +24 -5
  31. package/dist/shared/output-truncation.js +37 -0
  32. package/dist/shared/package-metadata.js +353 -0
  33. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  34. package/dist/sidecars/cursor-prompt/index.js +3 -0
  35. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  36. package/dist/task/config-types.js +29 -12
  37. package/dist/task/delegate.js +9 -21
  38. package/dist/task/runtime.js +1 -2
  39. package/dist/worker/cli.js +243 -0
  40. package/dist/worker/closeout/apply.js +73 -0
  41. package/dist/worker/closeout/preview.js +30 -0
  42. package/dist/worker/delivery/final-verification.js +194 -0
  43. package/dist/worker/delivery/git-transaction.js +354 -0
  44. package/dist/worker/delivery/package.js +502 -0
  45. package/dist/worker/feature/decision-loader.js +68 -0
  46. package/dist/worker/feature/discover.js +14 -0
  47. package/dist/worker/feature/next-action.js +74 -0
  48. package/dist/worker/feature/reducer.js +133 -0
  49. package/dist/worker/feature/review.js +502 -0
  50. package/dist/worker/feature/run.js +365 -0
  51. package/dist/worker/feature/types.js +1 -0
  52. package/dist/worker/follow-up/approve.js +270 -0
  53. package/dist/worker/follow-up/factory.js +234 -0
  54. package/dist/worker/follow-up/paths.js +25 -0
  55. package/dist/worker/follow-up/policy.js +26 -0
  56. package/dist/worker/follow-up/schema.js +93 -0
  57. package/dist/worker/follow-up/store.js +96 -0
  58. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  59. package/dist/worker/metrics/projector.js +139 -0
  60. package/dist/worker/observability/read-model.js +282 -15
  61. package/dist/worker/observe/paths.js +17 -5
  62. package/dist/worker/observe/static/app.js +443 -61
  63. package/dist/worker/observe/static/index.html +3 -1
  64. package/dist/worker/observe/static/styles.css +85 -18
  65. package/dist/worker/pool/run-store.js +14 -2
  66. package/dist/worker/pool/validation.js +59 -0
  67. package/dist/worker/preflight.js +49 -1
  68. package/dist/worker/report/morning-report.js +41 -6
  69. package/dist/worker/run-task/run-task.js +23 -13
  70. package/dist/worker/runner/run-ready.js +89 -11
  71. package/dist/worker/task-spec/schema.js +0 -1
  72. package/dist/workflows/dag/convergence/controller.js +1 -1
  73. package/dist/workflows/dag/executor-registry.js +0 -2
  74. package/dist/workflows/dag/governance-profile.js +10 -0
  75. package/dist/workflows/dag/init-hybrid.js +601 -26
  76. package/dist/workflows/dag/lifecycle.js +146 -0
  77. package/dist/workflows/dag/node-execution.js +64 -7
  78. package/dist/workflows/dag/prompt.js +16 -0
  79. package/dist/workflows/dag/report.js +2 -0
  80. package/dist/workflows/dag/runner.js +176 -119
  81. package/dist/workflows/dag/scheduler.js +7 -2
  82. package/dist/workflows/dag/skill-snapshot.js +527 -0
  83. package/dist/workflows/dag/types.js +45 -9
  84. package/dist/workflows/dag/validate.js +5 -8
  85. package/dist/workflows/loop/actions/dag-action.js +0 -2
  86. package/dist/workflows/loop/actions/shared.js +1 -1
  87. package/dist/workflows/loop/actions.js +14 -31
  88. package/dist/workflows/loop/benchmark.js +1 -1
  89. package/dist/workflows/loop/index.js +1 -1
  90. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  91. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  92. package/docs/README.md +35 -16
  93. package/docs/agent-dag-recovery-playbook.md +1 -1
  94. package/docs/architecture/README.md +26 -0
  95. package/docs/architecture/dag-execution.md +134 -0
  96. package/docs/architecture/evolution.md +52 -0
  97. package/docs/architecture/facts-and-state.md +58 -0
  98. package/docs/architecture/runtime-boundaries.md +41 -15
  99. package/docs/architecture/system-overview.md +93 -0
  100. package/docs/architecture/worker-and-feature.md +81 -0
  101. package/docs/cursor-prompt-sidecar.md +36 -0
  102. package/docs/decisions/README.md +13 -1
  103. package/docs/design/README.md +39 -13
  104. package/docs/development-principles.md +1 -1
  105. package/docs/exec-plans/active/README.md +2 -2
  106. package/docs/exec-plans/completed/README.md +14 -1
  107. package/docs/feature-workflow.md +44 -4
  108. package/docs/init-surface.manifest.json +63 -1
  109. package/docs/loop-agent-harness.md +65 -3
  110. package/docs/progress/README.md +27 -0
  111. package/docs/reports/README.md +74 -8
  112. package/docs/skills/README.md +2 -1
  113. package/docs/skills/vetted-skill-registry.md +2 -1
  114. package/docs/templates/agent-dag-report.schema.json +4 -2
  115. package/docs/templates/agent-dag.base.json +0 -5
  116. package/docs/templates/agent-dag.final-verification.json +0 -5
  117. package/docs/templates/agent-dag.schema.json +1 -2
  118. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  119. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +131 -0
  120. package/docs/templates/backend-test-dag.json +213 -0
  121. package/docs/templates/backend-test-dag.retrospect.prompt.md +128 -0
  122. package/docs/templates/backend-test-dag.review-cases.prompt.md +85 -0
  123. package/docs/templates/frontend-design-contract.md +33 -0
  124. package/docs/templates/frontend-task-constraints.md +25 -0
  125. package/docs/templates/frontend-task-requirement.md +61 -0
  126. package/docs/templates/harness.schema.json +8 -5
  127. package/docs/templates/hybrid-dag.json +1 -6
  128. package/docs/templates/init-evolution-review.md +4 -2
  129. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  130. package/docs/templates/product-line/task.yaml +0 -1
  131. package/docs/templates/worker-dogfood-evidence.md +28 -0
  132. package/docs/templates/worker-dogfood-setup.md +20 -0
  133. package/docs/verification-matrix.md +17 -0
  134. package/examples/decision-gate-agent-dag.json +87 -33
  135. package/examples/example-dag.json +0 -5
  136. package/examples/hybrid-loop-agent-dag.json +0 -5
  137. package/harness.json +6 -11
  138. package/package.json +22 -44
  139. package/scripts/check-product-line-docs.sh +10 -3
  140. package/skills/agent-worker/SKILL.md +37 -0
  141. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  142. package/skills/frontend-design-review/SKILL.md +59 -0
  143. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  144. package/skills/frontend-implementation/SKILL.md +48 -0
  145. package/skills/frontend-implementation/references/code-standards.md +34 -0
  146. package/skills/frontend-implementation/references/design-spec.md +46 -0
  147. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  148. package/skills/frontend-review/SKILL.md +53 -0
  149. package/skills/frontend-review/references/review-findings.md +42 -0
  150. package/skills/frontend-verification/SKILL.md +40 -0
  151. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  152. package/skills/grill-me/SKILL.md +10 -0
  153. package/skills/grill-with-docs/SKILL.md +88 -0
  154. package/skills/grill-with-docs/adr-format.md +47 -0
  155. package/skills/grill-with-docs/context-format.md +60 -0
  156. package/skills/init-capability-evolution/SKILL.md +1 -0
  157. package/skills/loop-agent/SKILL.md +11 -9
  158. package/skills/loop-agent/references/command-reference.md +28 -15
  159. package/skills/loop-agent/references/docs-converge.md +126 -0
  160. package/skills/loop-agent/references/harness-policy.md +7 -7
  161. package/skills/loop-agent/references/hybrid-dag.md +13 -15
  162. package/skills/loop-agent/references/long-running-loop.md +4 -6
  163. package/skills/loop-agent/references/multi-worktree.md +6 -6
  164. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  165. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  166. package/skills/using-git-worktrees/SKILL.md +215 -0
  167. package/dist/commands/cursor-worker.js +0 -43
  168. package/dist/cursor-worker-entry.js +0 -8
  169. package/dist/executors/cursor-artifacts.js +0 -33
  170. package/dist/executors/cursor-execution-log.js +0 -81
  171. package/dist/executors/cursor-executor-artifacts.js +0 -134
  172. package/dist/executors/cursor-run.js +0 -115
  173. package/dist/executors/cursor-tool.js +0 -94
  174. package/dist/executors/cursor-worker-client.js +0 -223
  175. package/dist/executors/cursor-worker-protocol.js +0 -18
  176. package/dist/executors/cursor-worker-server.js +0 -54
  177. package/dist/executors/cursor-worker.js +0 -3
  178. package/dist/executors/cursor.js +0 -6
  179. package/dist/executors/dag-cursor-executor.js +0 -87
  180. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  181. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  182. package/docs/cursor-executor-usage.md +0 -25
  183. package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
@@ -1,94 +0,0 @@
1
- import { access } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
3
- import { executeCursorTask, truncateOutput, DEFAULT_CURSOR_MODEL, } from './cursor-executor.js';
4
- import { formatCursorFailureMessage, persistCursorToolExecution, } from './cursor-execution-log.js';
5
- import { persistCursorRunLog, computeRunDir } from '../records/one-shot-runs.js';
6
- export const DEFAULT_CURSOR_TOOL_MODEL = DEFAULT_CURSOR_MODEL;
7
- export const MAX_CURSOR_TOOL_OUTPUT_CHARS = 4_000;
8
- export async function validateCursorToolCwd(cwd) {
9
- const resolvedCwd = resolve(cwd);
10
- try {
11
- await access(resolvedCwd);
12
- }
13
- catch {
14
- return { ok: false, error: `cwd does not exist: ${resolvedCwd}` };
15
- }
16
- return { ok: true, resolvedCwd };
17
- }
18
- export async function runCursorTool(params, options = {}) {
19
- const apiKey = options.apiKey ?? process.env.CURSOR_API_KEY;
20
- if (!apiKey?.trim()) {
21
- return {
22
- content: [{ type: 'text', text: `Error: ${formatCursorFailureMessage('missing-api-key')}` }],
23
- details: { error: 'missing-api-key', failureCategory: 'missing-api-key' },
24
- };
25
- }
26
- const cwdCheck = await validateCursorToolCwd(params.cwd ?? process.cwd());
27
- if (!cwdCheck.ok) {
28
- return {
29
- content: [{ type: 'text', text: `Error: ${cwdCheck.error}` }],
30
- details: { error: 'invalid_cwd' },
31
- };
32
- }
33
- const model = params.model ?? DEFAULT_CURSOR_TOOL_MODEL;
34
- const runDir = options.skipRunLog ? undefined : (await computeRunDir(cwdCheck.resolvedCwd, params.task) ?? undefined);
35
- const execution = await executeCursorTask({
36
- task: params.task,
37
- cwd: cwdCheck.resolvedCwd,
38
- model,
39
- apiKey,
40
- runDir: runDir ?? undefined,
41
- runHook: options.runHook,
42
- });
43
- await persistCursorToolExecution(cwdCheck.resolvedCwd, {
44
- task: params.task,
45
- model,
46
- result: execution,
47
- apiKey,
48
- });
49
- // Persist human-readable run log under .harness/runs/
50
- if (!options.skipRunLog) {
51
- await persistCursorRunLog(cwdCheck.resolvedCwd, {
52
- task: params.task,
53
- model,
54
- channel: 'tool',
55
- cwd: cwdCheck.resolvedCwd,
56
- result: execution,
57
- runDir,
58
- });
59
- }
60
- const truncated = truncateOutput(execution.stdout || execution.stderr, MAX_CURSOR_TOOL_OUTPUT_CHARS);
61
- const summary = truncated.text || `(No text output. Status: ${execution.status})`;
62
- if (!execution.ok) {
63
- return {
64
- content: [{
65
- type: 'text',
66
- text: summary || `Error: ${formatCursorFailureMessage(execution.failureCategory)}`,
67
- }],
68
- details: {
69
- status: execution.status,
70
- durationMs: execution.durationMs,
71
- model,
72
- truncated: truncated.truncated,
73
- error: execution.failureCategory,
74
- failureCategory: execution.failureCategory,
75
- },
76
- };
77
- }
78
- return {
79
- content: [{ type: 'text', text: summary }],
80
- details: {
81
- status: execution.status,
82
- durationMs: execution.durationMs,
83
- model,
84
- truncated: truncated.truncated,
85
- failureCategory: execution.failureCategory,
86
- inputTokens: readTokenField(execution.details, 'inputTokens'),
87
- outputTokens: readTokenField(execution.details, 'outputTokens'),
88
- },
89
- };
90
- }
91
- function readTokenField(details, key) {
92
- const value = details?.[key];
93
- return typeof value === 'number' ? value : undefined;
94
- }
@@ -1,223 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { createInterface } from 'node:readline';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { DEFAULT_CURSOR_TIMEOUT_MS, } from './cursor-executor.js';
6
- const WORKER_MODULE_PATH = fileURLToPath(import.meta.url);
7
- const WORKER_MODULE_DIR = path.dirname(WORKER_MODULE_PATH);
8
- const WORKER_ENTRY = resolveCursorWorkerEntryPath(WORKER_MODULE_PATH);
9
- let workerProcess = null;
10
- let workerBoot = null;
11
- const pending = new Map();
12
- let nextId = 1;
13
- const DEFAULT_RPC_RESPONSE_GRACE_MS = 5_000;
14
- const CURSOR_WORKER_STOP_SIGNAL = 'SIGTERM';
15
- function getRpcResponseGraceMs() {
16
- const raw = process.env.CURSOR_WORKER_RPC_GRACE_MS?.trim();
17
- if (raw) {
18
- const parsed = Number(raw);
19
- if (Number.isFinite(parsed) && parsed >= 0)
20
- return parsed;
21
- }
22
- return DEFAULT_RPC_RESPONSE_GRACE_MS;
23
- }
24
- export function isCursorWorkerEnabled() {
25
- const raw = process.env.CURSOR_USE_WORKER?.trim().toLowerCase();
26
- return raw === '1' || raw === 'true' || raw === 'yes';
27
- }
28
- /** True when this process should accept worker RPC (child), not delegate to a worker. */
29
- export function isCursorWorkerChild() {
30
- return process.env.CURSOR_WORKER_CHILD === '1';
31
- }
32
- export function shouldUseCursorWorker(options) {
33
- if (options?.runHook)
34
- return false;
35
- if (isCursorWorkerChild())
36
- return false;
37
- if (options?.useWorker === true)
38
- return true;
39
- return isCursorWorkerEnabled();
40
- }
41
- export function resolveCursorWorkerEntryPath(modulePath) {
42
- const extension = path.extname(modulePath) === '.ts' ? '.ts' : '.js';
43
- return path.join(path.dirname(modulePath), `../cursor-worker-entry${extension}`);
44
- }
45
- export function buildCursorWorkerScriptArgs(entryPath) {
46
- return path.extname(entryPath) === '.ts'
47
- ? ['--import', 'tsx/esm', entryPath]
48
- : [entryPath];
49
- }
50
- function workerScriptArgs() {
51
- return buildCursorWorkerScriptArgs(WORKER_ENTRY);
52
- }
53
- export function getCursorWorkerEntryPath() {
54
- return WORKER_ENTRY;
55
- }
56
- function dispatchResponse(line) {
57
- let parsed;
58
- try {
59
- parsed = JSON.parse(line);
60
- }
61
- catch {
62
- return;
63
- }
64
- const waiter = pending.get(parsed.id);
65
- if (!waiter)
66
- return;
67
- pending.delete(parsed.id);
68
- waiter.resolve(parsed);
69
- }
70
- async function ensureWorkerProcess() {
71
- if (workerProcess && !workerProcess.killed && workerProcess.exitCode === null) {
72
- return workerProcess;
73
- }
74
- if (!workerBoot) {
75
- workerBoot = new Promise((resolve, reject) => {
76
- const child = spawn(process.execPath, workerScriptArgs(), {
77
- cwd: process.cwd(),
78
- env: { ...process.env, CURSOR_WORKER_CHILD: '1' },
79
- stdio: ['pipe', 'pipe', 'pipe'],
80
- });
81
- workerProcess = child;
82
- const rl = createInterface({ input: child.stdout });
83
- rl.on('line', dispatchResponse);
84
- child.stderr?.on('data', (chunk) => {
85
- const text = chunk.toString('utf-8').trim();
86
- if (text)
87
- process.stderr.write(`[cursor-worker] ${text}\n`);
88
- });
89
- child.on('error', (err) => {
90
- workerBoot = null;
91
- reject(err);
92
- });
93
- child.on('exit', (code) => {
94
- if (workerProcess !== child)
95
- return;
96
- workerProcess = null;
97
- workerBoot = null;
98
- for (const [, waiter] of pending) {
99
- waiter.reject(new Error(`cursor worker exited with code ${code ?? 'unknown'}`));
100
- }
101
- pending.clear();
102
- });
103
- // ping to confirm ready
104
- const pingId = '__boot__';
105
- const bootWaiter = new Promise((res, rej) => {
106
- const timeout = setTimeout(() => rej(new Error('cursor worker boot timeout')), 30_000);
107
- pending.set(pingId, {
108
- resolve: (response) => {
109
- clearTimeout(timeout);
110
- if (!response.ok) {
111
- rej(new Error('cursor worker ping failed'));
112
- return;
113
- }
114
- res();
115
- },
116
- reject: (err) => {
117
- clearTimeout(timeout);
118
- rej(err);
119
- },
120
- });
121
- });
122
- child.stdin.write(`${JSON.stringify({ id: pingId, type: 'ping' })}\n`);
123
- bootWaiter.then(() => resolve()).catch(reject);
124
- });
125
- }
126
- await workerBoot;
127
- if (!workerProcess)
128
- throw new Error('cursor worker failed to start');
129
- return workerProcess;
130
- }
131
- export async function stopCursorWorker(signal = CURSOR_WORKER_STOP_SIGNAL) {
132
- const pid = workerProcess?.pid;
133
- const hadLiveWorker = Boolean(workerProcess && workerProcess.exitCode === null && !workerProcess.killed);
134
- if (hadLiveWorker && workerProcess) {
135
- try {
136
- workerProcess.kill(signal);
137
- }
138
- catch {
139
- /* ignore ESRCH / race with natural exit */
140
- }
141
- }
142
- workerProcess = null;
143
- workerBoot = null;
144
- for (const [, waiter] of pending) {
145
- waiter.reject(new Error('cursor worker stopped'));
146
- }
147
- pending.clear();
148
- return {
149
- workerPid: pid,
150
- workerTerminationAttempted: hadLiveWorker,
151
- workerStopSignal: hadLiveWorker ? signal : undefined,
152
- };
153
- }
154
- /** Test-only: reset module singletons between cases. */
155
- export async function resetCursorWorkerStateForTests() {
156
- await stopCursorWorker();
157
- nextId = 1;
158
- }
159
- export function isCursorWorkerRunning() {
160
- return Boolean(workerProcess && workerProcess.exitCode === null && !workerProcess.killed);
161
- }
162
- export async function executeCursorTaskViaWorker(options) {
163
- const child = await ensureWorkerProcess();
164
- const id = `req-${nextId++}`;
165
- const request = {
166
- id,
167
- type: 'execute',
168
- task: options.task,
169
- cwd: options.cwd,
170
- model: options.model,
171
- timeoutMs: options.timeoutMs,
172
- runDir: options.runDir,
173
- artifactDir: options.artifactDir,
174
- };
175
- const taskTimeoutMs = options.timeoutMs ?? DEFAULT_CURSOR_TIMEOUT_MS;
176
- const rpcGraceMs = getRpcResponseGraceMs();
177
- const responsePromise = new Promise((resolve, reject) => {
178
- const timer = setTimeout(() => {
179
- pending.delete(id);
180
- void stopCursorWorker().then((stopResult) => {
181
- resolve({
182
- id,
183
- ok: true,
184
- result: {
185
- ok: false,
186
- status: 'timeout',
187
- failureCategory: 'timeout',
188
- model: options.model ?? 'default',
189
- durationMs: taskTimeoutMs,
190
- stdout: '',
191
- stderr: `cursor worker request timeout after ${taskTimeoutMs}ms`,
192
- details: {
193
- timedOut: true,
194
- timeoutKind: 'rpc',
195
- cancelDeferred: true,
196
- cancelAttempted: false,
197
- workerPid: stopResult.workerPid,
198
- workerTerminationAttempted: stopResult.workerTerminationAttempted,
199
- workerStopSignal: stopResult.workerStopSignal,
200
- workerRestartDeferred: true,
201
- },
202
- },
203
- });
204
- });
205
- }, taskTimeoutMs + rpcGraceMs);
206
- pending.set(id, {
207
- resolve: (value) => {
208
- clearTimeout(timer);
209
- resolve(value);
210
- },
211
- reject: (err) => {
212
- clearTimeout(timer);
213
- reject(err);
214
- },
215
- });
216
- });
217
- child.stdin.write(`${JSON.stringify(request)}\n`);
218
- const response = await responsePromise;
219
- if (!response.ok) {
220
- throw new Error(response.error);
221
- }
222
- return response.result;
223
- }
@@ -1,18 +0,0 @@
1
- export function parseWorkerRequestLine(line) {
2
- const parsed = JSON.parse(line);
3
- if (!parsed || typeof parsed !== 'object' || !('id' in parsed) || !('type' in parsed)) {
4
- throw new Error('invalid worker request');
5
- }
6
- if (parsed.type === 'ping')
7
- return parsed;
8
- if (parsed.type === 'execute') {
9
- if (typeof parsed.task !== 'string' || typeof parsed.cwd !== 'string') {
10
- throw new Error('execute request requires task and cwd');
11
- }
12
- return parsed;
13
- }
14
- throw new Error(`unknown worker request type: ${parsed.type}`);
15
- }
16
- export function serializeWorkerResponse(response) {
17
- return `${JSON.stringify(response)}\n`;
18
- }
@@ -1,54 +0,0 @@
1
- import { createInterface } from 'node:readline';
2
- import { executeCursorTask } from './cursor-executor.js';
3
- import { parseWorkerRequestLine, serializeWorkerResponse, } from './cursor-worker-protocol.js';
4
- export async function handleCursorWorkerRequest(request) {
5
- if (request.type === 'ping') {
6
- return {
7
- id: request.id,
8
- ok: true,
9
- result: {
10
- ok: true,
11
- status: 'completed',
12
- failureCategory: 'success',
13
- model: 'worker',
14
- durationMs: 0,
15
- stdout: 'pong',
16
- stderr: '',
17
- details: { worker: true },
18
- },
19
- };
20
- }
21
- try {
22
- const result = await executeCursorTask({
23
- task: request.task,
24
- cwd: request.cwd,
25
- model: request.model,
26
- timeoutMs: request.timeoutMs,
27
- runDir: request.runDir,
28
- artifactDir: request.artifactDir,
29
- });
30
- return { id: request.id, ok: true, result };
31
- }
32
- catch (error) {
33
- const message = error instanceof Error ? error.message : String(error);
34
- return { id: request.id, ok: false, error: message };
35
- }
36
- }
37
- export async function runCursorWorkerOnStreams(input, output) {
38
- const rl = createInterface({ input, crlfDelay: Infinity });
39
- for await (const line of rl) {
40
- const trimmed = line.trim();
41
- if (!trimmed)
42
- continue;
43
- let response;
44
- try {
45
- const request = parseWorkerRequestLine(trimmed);
46
- response = await handleCursorWorkerRequest(request);
47
- }
48
- catch (error) {
49
- const message = error instanceof Error ? error.message : String(error);
50
- response = { id: 'unknown', ok: false, error: message };
51
- }
52
- output.write(serializeWorkerResponse(response));
53
- }
54
- }
@@ -1,3 +0,0 @@
1
- export * from "./cursor-worker-client.js";
2
- export * from "./cursor-worker-protocol.js";
3
- export * from "./cursor-worker-server.js";
@@ -1,6 +0,0 @@
1
- export * from "./cursor-artifacts.js";
2
- export * from "./cursor-execution-log.js";
3
- export * from "./cursor-executor-artifacts.js";
4
- export * from "./cursor-executor.js";
5
- export * from "./cursor-run.js";
6
- export * from "./cursor-tool.js";
@@ -1,87 +0,0 @@
1
- import path from "node:path";
2
- import { ensureDagNodeArtifactDir } from "../infrastructure/harness/artifact-store.js";
3
- import { executeCursorTask } from "./cursor-executor.js";
4
- import { writeCursorExecutorArtifacts } from "./cursor-executor-artifacts.js";
5
- import { buildCursorExecutionLogRecord, persistCursorExecutionLog, } from "./cursor-execution-log.js";
6
- import { readGitStatusPorcelain, runPostRunWriteGuard, } from "./shell-write-guard.js";
7
- export async function executeDagCursorNode(input, meta) {
8
- const started = Date.now();
9
- let beforeStatus;
10
- try {
11
- beforeStatus = await readGitStatusPorcelain(input.cwd);
12
- }
13
- catch {
14
- // Non-git cwd: skip write guard (documented limitation).
15
- beforeStatus = undefined;
16
- }
17
- const artifactDir = await ensureDagNodeArtifactDir(meta.runDir, input.task.id);
18
- const result = await executeCursorTask({
19
- task: input.prompt,
20
- cwd: input.cwd,
21
- model: input.model,
22
- useWorker: true,
23
- artifactDir,
24
- });
25
- const executionContext = {
26
- channel: "dag",
27
- cwd: input.cwd,
28
- dagRunId: meta.runId,
29
- dagNodeId: input.task.id,
30
- };
31
- await persistCursorExecutionLog(meta.runDir, buildCursorExecutionLogRecord({
32
- context: executionContext,
33
- model: input.model,
34
- prompt: input.prompt,
35
- result,
36
- }));
37
- await writeCursorExecutorArtifacts(path.join(meta.runDir, input.task.id), {
38
- prompt: input.prompt,
39
- model: input.model,
40
- context: executionContext,
41
- result,
42
- });
43
- let writeGuardOk = true;
44
- let writeGuardViolations = [];
45
- if (beforeStatus !== undefined) {
46
- try {
47
- const guard = await runPostRunWriteGuard({
48
- rootCwd: input.cwd,
49
- beforeStatus,
50
- writePolicy: input.task.writePolicy,
51
- writeSet: input.task.writeSet,
52
- allowedPaths: input.task.allowedPaths,
53
- forbiddenPaths: input.task.forbiddenPaths,
54
- concurrentSiblingWriteSets: meta.concurrentSiblingWriteSets,
55
- });
56
- writeGuardOk = guard.ok;
57
- writeGuardViolations = guard.violations;
58
- }
59
- catch {
60
- writeGuardOk = false;
61
- writeGuardViolations = ["write guard failed: git status unavailable"];
62
- }
63
- }
64
- const ok = result.ok && writeGuardOk;
65
- const stderrParts = [result.stderr];
66
- if (!writeGuardOk) {
67
- stderrParts.push(`write guard failed: ${writeGuardViolations.join(", ")}`);
68
- if (meta.writeGuardAttribution === "best-effort") {
69
- stderrParts.push("write guard note: concurrent rank writers use best-effort per-node attribution; keep same-rank writeSet entries disjoint");
70
- }
71
- }
72
- const executionResult = {
73
- ok,
74
- stdout: result.stdout,
75
- stderr: stderrParts.filter(Boolean).join("\n\n"),
76
- failureCategory: !result.ok
77
- ? result.failureCategory
78
- : !writeGuardOk
79
- ? "write-guard"
80
- : result.failureCategory,
81
- durationMs: result.durationMs ?? Date.now() - started,
82
- };
83
- if (result.tokensUsed !== undefined) {
84
- executionResult.tokensUsed = result.tokensUsed;
85
- }
86
- return executionResult;
87
- }
@@ -1,191 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { DEFAULT_CURSOR_MODEL, DEFAULT_CURSOR_TIMEOUT_MS, executeCursorTask, } from "../../../executors/cursor-executor.js";
4
- import { pathsChangedDuringRun, readGitStatusPorcelain, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "../../../executors/shell-write-guard.js";
5
- import { computeRunDir, persistCursorRunLog } from "../../../records/one-shot-runs.js";
6
- import { writeLoopCursorFixRecord } from "../../../infrastructure/harness/loop-action-store.js";
7
- import { loadTaskConfig } from "../../../task/runtime.js";
8
- import { getLoopPaths } from "../paths.js";
9
- import { appendLoopRound } from "../rounds.js";
10
- import { rewriteLoopContext } from "../context.js";
11
- import { readLoopRounds } from "../rounds.js";
12
- import { validateLoopCursorFixPolicy } from "../policy/cursor-fix-policy.js";
13
- import { filterLoopOwnedChangedPaths, hasDagRound, relativeRef } from "./shared.js";
14
- import { readLatestPiReviewRecord } from "./pi-review.js";
15
- function extractChangedPaths(result) {
16
- const raw = result.details?.changedPaths;
17
- if (!Array.isArray(raw))
18
- return undefined;
19
- return raw.filter((value) => typeof value === "string");
20
- }
21
- export async function buildLoopCursorFixPrompt(repoRoot, taskId) {
22
- const paths = getLoopPaths(repoRoot, taskId);
23
- const [objective, context] = await Promise.all([
24
- readFile(paths.objectivePath, "utf-8"),
25
- readFile(paths.contextPath, "utf-8"),
26
- ]);
27
- const config = await loadTaskConfig(repoRoot, taskId);
28
- const rounds = await readLoopRounds(repoRoot, taskId);
29
- const latestReview = await readLatestPiReviewRecord(repoRoot, taskId);
30
- validateLoopCursorFixPolicy({
31
- allowedPaths: config.allowedPaths,
32
- forbiddenPaths: config.forbiddenPaths,
33
- complexity: config.complexity,
34
- dagFallbackReason: config.dagFallbackReason ?? "",
35
- hasDagRound: hasDagRound(rounds),
36
- });
37
- return [
38
- `Task id: ${taskId}`,
39
- "Exact objective: implement the next bounded fix described by the loop context.",
40
- "",
41
- "Allowed paths:",
42
- ...config.allowedPaths.map((entry) => `- ${entry}`),
43
- "",
44
- "Forbidden paths:",
45
- ...(config.forbiddenPaths.length > 0
46
- ? config.forbiddenPaths.map((entry) => `- ${entry}`)
47
- : ["- none"]),
48
- "",
49
- "Hard constraints:",
50
- "- Preserve unrelated files and user work.",
51
- "- Do not touch forbidden paths.",
52
- "- Do not mark the loop complete; shell verification or review must follow.",
53
- "",
54
- "Expected verification:",
55
- "- Run the verification commands listed in the task constraints after the fix, or explain why they were not run.",
56
- "",
57
- "<latest_pi_review>",
58
- latestReview
59
- ? [
60
- `recommendedAction: ${latestReview.structured.recommendedAction}`,
61
- `rootCause: ${latestReview.structured.rootCause}`,
62
- `fixScope: ${latestReview.structured.fixScope.join(", ")}`,
63
- `invariant: ${latestReview.structured.invariant ?? "unknown"}`,
64
- ].join("\n")
65
- : "No structured Pi review record found.",
66
- "</latest_pi_review>",
67
- "",
68
- "<objective>",
69
- objective.trim(),
70
- "</objective>",
71
- "",
72
- "<context>",
73
- context.trim(),
74
- "</context>",
75
- ].join("\n");
76
- }
77
- async function defaultCursorFixExecutor(input) {
78
- return executeCursorTask({
79
- task: input.prompt,
80
- cwd: input.cwd,
81
- model: input.model,
82
- timeoutMs: input.timeoutMs,
83
- runDir: input.runDir,
84
- });
85
- }
86
- export async function runLoopCursorFix(repoRoot, taskId, options = {}) {
87
- const config = await loadTaskConfig(repoRoot, taskId);
88
- const rounds = await readLoopRounds(repoRoot, taskId);
89
- validateLoopCursorFixPolicy({
90
- allowedPaths: config.allowedPaths,
91
- forbiddenPaths: config.forbiddenPaths,
92
- complexity: config.complexity,
93
- dagFallbackReason: config.dagFallbackReason ?? "",
94
- hasDagRound: hasDagRound(rounds),
95
- });
96
- const model = options.model ?? config.cursorModel ?? DEFAULT_CURSOR_MODEL;
97
- const timeoutMs = options.timeoutMs ?? DEFAULT_CURSOR_TIMEOUT_MS;
98
- const cwd = options.cwd ? path.resolve(repoRoot, options.cwd) : repoRoot;
99
- const executor = options.executor ?? defaultCursorFixExecutor;
100
- const round = rounds.length + 1;
101
- const latestReview = await readLatestPiReviewRecord(repoRoot, taskId);
102
- const prompt = await buildLoopCursorFixPrompt(repoRoot, taskId);
103
- const runDir = (await computeRunDir(repoRoot, `${taskId} cursor-fix r${round}`)) ?? undefined;
104
- const beforeStatus = await readGitStatusPorcelain(repoRoot).catch(() => "");
105
- const result = await executor({ prompt, cwd, model, timeoutMs, runDir });
106
- const explicitChangedPaths = extractChangedPaths(result);
107
- let changedPaths = explicitChangedPaths
108
- ? filterLoopOwnedChangedPaths(explicitChangedPaths, repoRoot, runDir)
109
- : undefined;
110
- const guard = explicitChangedPaths
111
- ? validateShellWriteGuard({
112
- changedPaths: changedPaths ?? [],
113
- writePolicy: "exclusive",
114
- writeSet: config.allowedPaths,
115
- forbiddenPaths: config.forbiddenPaths,
116
- })
117
- : await (async () => {
118
- const afterStatus = await readGitStatusPorcelain(repoRoot).catch(() => "");
119
- changedPaths = filterLoopOwnedChangedPaths(pathsChangedDuringRun(snapshotGitStatusPorcelain(beforeStatus), snapshotGitStatusPorcelain(afterStatus)), repoRoot, runDir);
120
- return validateShellWriteGuard({
121
- changedPaths,
122
- writePolicy: "exclusive",
123
- writeSet: config.allowedPaths,
124
- forbiddenPaths: config.forbiddenPaths,
125
- });
126
- })();
127
- const finalResult = guard.ok
128
- ? result
129
- : {
130
- ...result,
131
- ok: false,
132
- status: "failed",
133
- failureCategory: "path-violation",
134
- stderr: [
135
- result.stderr,
136
- `loop cursor-fix path violations: ${guard.violations.join(", ")}`,
137
- ]
138
- .filter(Boolean)
139
- .join("\n"),
140
- };
141
- const persisted = await persistCursorRunLog(repoRoot, {
142
- task: prompt,
143
- model,
144
- channel: "tool",
145
- cwd,
146
- result: finalResult,
147
- taskId,
148
- runDir,
149
- });
150
- const runRef = persisted?.logPath
151
- ? relativeRef(repoRoot, persisted.logPath)
152
- : "";
153
- const paths = getLoopPaths(repoRoot, taskId);
154
- const record = {
155
- schemaVersion: 1,
156
- taskId,
157
- round,
158
- action: "cursor-fix",
159
- ok: finalResult.ok,
160
- model,
161
- runRef,
162
- allowedPaths: config.allowedPaths,
163
- forbiddenPaths: config.forbiddenPaths,
164
- changedPaths: changedPaths ?? [],
165
- writeGuardViolations: guard.violations,
166
- fixScope: latestReview?.structured.fixScope ?? [],
167
- rootCause: latestReview?.structured.rootCause ?? "",
168
- invariant: latestReview?.structured.invariant ?? "",
169
- expectedNextVerification: "loop shell-verify or required task verification commands",
170
- recordedAt: new Date().toISOString(),
171
- };
172
- const recordPath = path.join(paths.reviewDir, `round-${round}-cursor-fix.json`);
173
- await writeLoopCursorFixRecord(repoRoot, taskId, round, record);
174
- await appendLoopRound(repoRoot, taskId, {
175
- action: "cursor-fix",
176
- refs: [runRef, relativeRef(repoRoot, recordPath)].filter(Boolean),
177
- result: finalResult.ok ? "cursor fix completed" : "cursor fix failed",
178
- verification: finalResult.ok
179
- ? "cursor-fix completed; shell verification still required"
180
- : `cursor-fix failed: ${finalResult.failureCategory}`,
181
- lesson: finalResult.ok
182
- ? "cursor-fix changed only allowed paths; completion remains gated by verification"
183
- : finalResult.stderr || finalResult.stdout || "cursor-fix failed",
184
- next: "run shell verification for the cursor fix",
185
- decision: "continue",
186
- failureCategory: finalResult.ok ? undefined : finalResult.failureCategory,
187
- completedCriteria: finalResult.ok ? ["cursor bounded fix action executed"] : [],
188
- });
189
- await rewriteLoopContext(repoRoot, taskId);
190
- return record;
191
- }