opencastle 0.14.0 → 0.16.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 (34) hide show
  1. package/dist/cli/convoy/store.d.ts +1 -0
  2. package/dist/cli/convoy/store.d.ts.map +1 -1
  3. package/dist/cli/convoy/store.js +5 -0
  4. package/dist/cli/convoy/store.js.map +1 -1
  5. package/dist/cli/run/schema.d.ts +5 -0
  6. package/dist/cli/run/schema.d.ts.map +1 -1
  7. package/dist/cli/run/schema.js +98 -143
  8. package/dist/cli/run/schema.js.map +1 -1
  9. package/dist/cli/run/schema.test.js +53 -215
  10. package/dist/cli/run/schema.test.js.map +1 -1
  11. package/dist/cli/run.d.ts.map +1 -1
  12. package/dist/cli/run.js +202 -104
  13. package/dist/cli/run.js.map +1 -1
  14. package/dist/cli/types.d.ts +2 -58
  15. package/dist/cli/types.d.ts.map +1 -1
  16. package/package.json +1 -1
  17. package/src/cli/convoy/store.ts +7 -0
  18. package/src/cli/run/schema.test.ts +61 -241
  19. package/src/cli/run/schema.ts +105 -153
  20. package/src/cli/run.ts +216 -105
  21. package/src/cli/types.ts +2 -66
  22. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  23. package/src/orchestrator/agents/team-lead.agent.md +2 -2
  24. package/src/orchestrator/prompts/generate-task-spec.prompt.md +26 -5
  25. package/dist/cli/run/loop-executor.d.ts +0 -3
  26. package/dist/cli/run/loop-executor.d.ts.map +0 -1
  27. package/dist/cli/run/loop-executor.js +0 -155
  28. package/dist/cli/run/loop-executor.js.map +0 -1
  29. package/dist/cli/run/loop-reporter.d.ts +0 -6
  30. package/dist/cli/run/loop-reporter.d.ts.map +0 -1
  31. package/dist/cli/run/loop-reporter.js +0 -112
  32. package/dist/cli/run/loop-reporter.js.map +0 -1
  33. package/src/cli/run/loop-executor.ts +0 -199
  34. package/src/cli/run/loop-reporter.ts +0 -125
@@ -1,155 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { spawn } from 'node:child_process';
3
- import { resolve } from 'node:path';
4
- import { formatDuration } from './executor.js';
5
- import { parseTimeout } from './schema.js';
6
- async function runBackpressureCommand(command, active, timeoutMs) {
7
- return new Promise((res) => {
8
- const child = spawn('sh', ['-c', command]);
9
- active.bpChild = child;
10
- let output = '';
11
- let killed = false;
12
- const timer = setTimeout(() => {
13
- killed = true;
14
- child.kill('SIGTERM');
15
- }, timeoutMs);
16
- child.stdout.on('data', (data) => { output += data.toString(); });
17
- child.stderr.on('data', (data) => { output += data.toString(); });
18
- child.on('close', (code) => {
19
- clearTimeout(timer);
20
- active.bpChild = null;
21
- const exitCode = code ?? 1;
22
- if (output.length > 5000)
23
- output = output.slice(0, 5000);
24
- const timedOut = killed;
25
- res({
26
- command,
27
- exitCode: timedOut ? -1 : exitCode,
28
- output: timedOut ? `Command timed out after ${timeoutMs}ms` : output,
29
- passed: !timedOut && exitCode === 0,
30
- });
31
- });
32
- });
33
- }
34
- async function runBackpressure(commands, reporter, active, timeoutMs) {
35
- const results = [];
36
- for (const command of commands) {
37
- reporter.onBackpressureStart(command);
38
- const result = await runBackpressureCommand(command, active, timeoutMs);
39
- reporter.onBackpressureResult(result);
40
- results.push(result);
41
- if (!result.passed)
42
- return { passed: false, results };
43
- }
44
- return { passed: true, results };
45
- }
46
- export function createLoopExecutor(spec, adapter, reporter) {
47
- return {
48
- async run() {
49
- const loop = spec.loop;
50
- const startedAt = new Date();
51
- const iterations = [];
52
- let aborted = false;
53
- const active = { task: null, bpChild: null };
54
- const timeoutMs = parseTimeout(loop.timeout);
55
- const sigintHandler = () => {
56
- aborted = true;
57
- if (active.task && typeof adapter.kill === 'function') {
58
- adapter.kill(active.task);
59
- }
60
- if (active.bpChild && !active.bpChild.killed) {
61
- active.bpChild.kill('SIGTERM');
62
- }
63
- };
64
- process.on('SIGINT', sigintHandler);
65
- let stoppedReason = 'max-iterations';
66
- try {
67
- for (let i = 1; i <= loop.max_iterations; i++) {
68
- if (aborted) {
69
- stoppedReason = 'user-abort';
70
- break;
71
- }
72
- reporter.onIterationStart(i, loop.max_iterations);
73
- // Re-read prompt from disk each iteration for latest content
74
- const promptContent = await readFile(resolve(process.cwd(), loop.prompt), 'utf8');
75
- const syntheticTask = {
76
- id: `loop-${i}`,
77
- prompt: promptContent,
78
- agent: 'autonomous',
79
- timeout: loop.timeout,
80
- depends_on: [],
81
- files: [],
82
- description: `Loop iteration ${i}`,
83
- max_retries: 1,
84
- };
85
- const iterStart = Date.now();
86
- active.task = syntheticTask;
87
- const adapterResult = await adapter.execute(syntheticTask, { verbose: spec._verbose });
88
- active.task = null;
89
- if (!adapterResult.success) {
90
- const duration = Date.now() - iterStart;
91
- const iterResult = {
92
- iteration: i,
93
- status: 'failed',
94
- duration,
95
- output: adapterResult.output,
96
- backpressureResults: [],
97
- };
98
- iterations.push(iterResult);
99
- reporter.onIterationDone(i, iterResult);
100
- stoppedReason = 'error';
101
- break;
102
- }
103
- let backpressureResults = [];
104
- if (loop.backpressure && loop.backpressure.length > 0) {
105
- const bp = await runBackpressure(loop.backpressure, reporter, active, timeoutMs);
106
- backpressureResults = bp.results;
107
- if (!bp.passed) {
108
- const duration = Date.now() - iterStart;
109
- const iterResult = {
110
- iteration: i,
111
- status: 'backpressure-fail',
112
- duration,
113
- output: adapterResult.output,
114
- backpressureResults,
115
- };
116
- iterations.push(iterResult);
117
- reporter.onIterationDone(i, iterResult);
118
- stoppedReason = 'backpressure-fail';
119
- break;
120
- }
121
- }
122
- const duration = Date.now() - iterStart;
123
- const iterResult = {
124
- iteration: i,
125
- status: 'done',
126
- duration,
127
- output: adapterResult.output,
128
- backpressureResults,
129
- };
130
- iterations.push(iterResult);
131
- reporter.onIterationDone(i, iterResult);
132
- }
133
- }
134
- finally {
135
- process.off('SIGINT', sigintHandler);
136
- }
137
- const completedAt = new Date();
138
- const completedIterations = iterations.filter((it) => it.status === 'done').length;
139
- const report = {
140
- name: spec.name,
141
- mode: 'loop',
142
- startedAt: startedAt.toISOString(),
143
- completedAt: completedAt.toISOString(),
144
- duration: formatDuration(completedAt.getTime() - startedAt.getTime()),
145
- totalIterations: iterations.length,
146
- completedIterations,
147
- stoppedReason,
148
- iterations,
149
- };
150
- await reporter.onComplete(report);
151
- return report;
152
- },
153
- };
154
- }
155
- //# sourceMappingURL=loop-executor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-executor.js","sourceRoot":"","sources":["../../../src/cli/run/loop-executor.ts"],"names":[],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- import type { LoopReporter } from '../types.js';
2
- export declare function createLoopReporter(specName: string, options?: {
3
- reportDir?: string;
4
- verbose?: boolean;
5
- }): LoopReporter;
6
- //# sourceMappingURL=loop-reporter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-reporter.d.ts","sourceRoot":"","sources":["../../../src/cli/run/loop-reporter.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,aAAa,CAAA;AAUpB,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GACtD,YAAY,CAoGd"}
@@ -1,112 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
3
- import { formatDuration } from './executor.js';
4
- import { c } from '../prompt.js';
5
- import { appendEvent } from '../log.js';
6
- import { appendLesson } from '../lesson.js';
7
- const STOPPED_LABELS = {
8
- 'max-iterations': 'reached max iterations',
9
- 'plan-empty': 'plan exhausted',
10
- 'backpressure-fail': 'backpressure check failed',
11
- 'user-abort': 'aborted by user',
12
- error: 'agent error',
13
- };
14
- export function createLoopReporter(specName, options = {}) {
15
- const reportDir = options.reportDir ?? resolve(process.cwd(), '.opencastle', 'runs');
16
- const verbose = options.verbose ?? false;
17
- return {
18
- onIterationStart(i, max) {
19
- console.log(`\n \u21bb Iteration ${i}/${max}`);
20
- },
21
- onIterationDone(_i, result) {
22
- const dur = formatDuration(result.duration);
23
- if (result.status === 'done') {
24
- console.log(` ${c.green('\u2713')} Completed (${dur})`);
25
- }
26
- else if (result.status === 'backpressure-fail') {
27
- console.log(` ${c.yellow('\u26a0')} Backpressure failed (${dur})`);
28
- }
29
- else {
30
- console.log(` ${c.red('\u2717')} Failed (${dur})`);
31
- if (result.output) {
32
- const lines = result.output.split('\n').slice(0, 5);
33
- for (const line of lines)
34
- console.log(` ${line}`);
35
- if (result.output.split('\n').length > 5)
36
- console.log(` ... (truncated)`);
37
- }
38
- }
39
- if (verbose && result.output && result.status === 'done') {
40
- console.log(` Output: ${result.output.slice(0, 500)}`);
41
- }
42
- appendEvent({
43
- type: 'delegation',
44
- timestamp: new Date().toISOString(),
45
- session_id: specName,
46
- agent: 'autonomous',
47
- task: `Loop iteration ${_i}`,
48
- mechanism: 'run-loop',
49
- outcome: result.status === 'done' ? 'success' : result.status,
50
- duration_sec: Math.round(result.duration / 1000),
51
- }).catch(() => { });
52
- },
53
- onBackpressureStart(cmd) {
54
- console.log(` \u23ce Running: ${cmd}`);
55
- },
56
- onBackpressureResult(result) {
57
- if (result.passed) {
58
- console.log(` ${c.green('\u2713')} Exit ${result.exitCode}`);
59
- }
60
- else {
61
- console.log(` ${c.red('\u2717')} Exit ${result.exitCode}`);
62
- if (result.output) {
63
- const lines = result.output.split('\n').slice(0, 5);
64
- for (const line of lines)
65
- console.log(` ${line}`);
66
- }
67
- }
68
- },
69
- async onComplete(report) {
70
- console.log(`\n ${c.dim('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500')}`);
71
- console.log(` ${c.bold('Loop complete:')} ${report.name}`);
72
- console.log(` Duration: ${report.duration}`);
73
- console.log();
74
- console.log(` Iterations: ${report.totalIterations} total \u2014 ` +
75
- `${c.green(String(report.completedIterations))} completed`);
76
- console.log(` Stopped: ${STOPPED_LABELS[report.stoppedReason]}`);
77
- try {
78
- await mkdir(reportDir, { recursive: true });
79
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
80
- const reportPath = resolve(reportDir, `loop-${specName}-${timestamp}.json`);
81
- await writeFile(reportPath, JSON.stringify(report, null, 2), 'utf8');
82
- console.log(` Report: ${reportPath}`);
83
- }
84
- catch (err) {
85
- console.log(` \u2717 Could not write report: ${err.message}`);
86
- }
87
- await appendEvent({
88
- type: 'session',
89
- timestamp: new Date().toISOString(),
90
- agent: 'opencastle-run',
91
- task: `Loop: ${report.name}`,
92
- outcome: report.stoppedReason === 'max-iterations' || report.stoppedReason === 'plan-empty' ? 'success' : 'failure',
93
- duration_min: Math.round((new Date(report.completedAt).getTime() - new Date(report.startedAt).getTime()) / 60000),
94
- mode: 'loop',
95
- total_iterations: report.totalIterations,
96
- completed_iterations: report.completedIterations,
97
- stopped_reason: report.stoppedReason,
98
- }).catch(() => { });
99
- if (report.stoppedReason === 'error' || report.stoppedReason === 'backpressure-fail') {
100
- const lastIter = report.iterations[report.iterations.length - 1];
101
- await appendLesson({
102
- title: `Run loop "${report.name}" stopped: ${report.stoppedReason}`,
103
- category: 'general',
104
- severity: 'medium',
105
- problem: `Loop stopped after ${report.totalIterations} iterations due to ${report.stoppedReason}.${lastIter?.output ? ` Last output: ${lastIter.output.slice(0, 200)}` : ''}`,
106
- }).catch(() => { });
107
- }
108
- console.log();
109
- },
110
- };
111
- }
112
- //# sourceMappingURL=loop-reporter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"loop-reporter.js","sourceRoot":"","sources":["../../../src/cli/run/loop-reporter.ts"],"names":[],"mappings":"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"}
@@ -1,199 +0,0 @@
1
- import { readFile } from 'node:fs/promises'
2
- import { spawn } from 'node:child_process'
3
- import type { ChildProcess } from 'node:child_process'
4
- import { resolve } from 'node:path'
5
- import { formatDuration } from './executor.js'
6
- import { parseTimeout } from './schema.js'
7
- import type {
8
- TaskSpec,
9
- LoopRunReport,
10
- LoopIterationResult,
11
- BackpressureResult,
12
- AgentAdapter,
13
- LoopReporter,
14
- LoopExecutor,
15
- Task,
16
- } from '../types.js'
17
-
18
- interface ActiveState {
19
- task: Task | null
20
- bpChild: ChildProcess | null
21
- }
22
-
23
- async function runBackpressureCommand(
24
- command: string,
25
- active: ActiveState,
26
- timeoutMs: number,
27
- ): Promise<BackpressureResult> {
28
- return new Promise((res) => {
29
- const child = spawn('sh', ['-c', command])
30
- active.bpChild = child
31
- let output = ''
32
- let killed = false
33
-
34
- const timer = setTimeout(() => {
35
- killed = true
36
- child.kill('SIGTERM')
37
- }, timeoutMs)
38
-
39
- child.stdout.on('data', (data: Buffer) => { output += data.toString() })
40
- child.stderr.on('data', (data: Buffer) => { output += data.toString() })
41
-
42
- child.on('close', (code: number | null) => {
43
- clearTimeout(timer)
44
- active.bpChild = null
45
- const exitCode = code ?? 1
46
- if (output.length > 5000) output = output.slice(0, 5000)
47
- const timedOut = killed
48
- res({
49
- command,
50
- exitCode: timedOut ? -1 : exitCode,
51
- output: timedOut ? `Command timed out after ${timeoutMs}ms` : output,
52
- passed: !timedOut && exitCode === 0,
53
- })
54
- })
55
- })
56
- }
57
-
58
- async function runBackpressure(
59
- commands: string[],
60
- reporter: LoopReporter,
61
- active: ActiveState,
62
- timeoutMs: number,
63
- ): Promise<{ passed: boolean; results: BackpressureResult[] }> {
64
- const results: BackpressureResult[] = []
65
- for (const command of commands) {
66
- reporter.onBackpressureStart(command)
67
- const result = await runBackpressureCommand(command, active, timeoutMs)
68
- reporter.onBackpressureResult(result)
69
- results.push(result)
70
- if (!result.passed) return { passed: false, results }
71
- }
72
- return { passed: true, results }
73
- }
74
-
75
- export function createLoopExecutor(
76
- spec: TaskSpec,
77
- adapter: AgentAdapter,
78
- reporter: LoopReporter,
79
- ): LoopExecutor {
80
- return {
81
- async run(): Promise<LoopRunReport> {
82
- const loop = spec.loop!
83
- const startedAt = new Date()
84
- const iterations: LoopIterationResult[] = []
85
- let aborted = false
86
- const active: ActiveState = { task: null, bpChild: null }
87
- const timeoutMs = parseTimeout(loop.timeout)
88
-
89
- const sigintHandler = () => {
90
- aborted = true
91
- if (active.task && typeof adapter.kill === 'function') {
92
- adapter.kill(active.task)
93
- }
94
- if (active.bpChild && !active.bpChild.killed) {
95
- active.bpChild.kill('SIGTERM')
96
- }
97
- }
98
- process.on('SIGINT', sigintHandler)
99
-
100
- let stoppedReason: LoopRunReport['stoppedReason'] = 'max-iterations'
101
-
102
- try {
103
- for (let i = 1; i <= loop.max_iterations; i++) {
104
- if (aborted) {
105
- stoppedReason = 'user-abort'
106
- break
107
- }
108
-
109
- reporter.onIterationStart(i, loop.max_iterations)
110
-
111
- // Re-read prompt from disk each iteration for latest content
112
- const promptContent = await readFile(resolve(process.cwd(), loop.prompt), 'utf8')
113
-
114
- const syntheticTask: Task = {
115
- id: `loop-${i}`,
116
- prompt: promptContent,
117
- agent: 'autonomous',
118
- timeout: loop.timeout,
119
- depends_on: [],
120
- files: [],
121
- description: `Loop iteration ${i}`,
122
- max_retries: 1,
123
- }
124
-
125
- const iterStart = Date.now()
126
- active.task = syntheticTask
127
- const adapterResult = await adapter.execute(syntheticTask, { verbose: spec._verbose })
128
- active.task = null
129
-
130
- if (!adapterResult.success) {
131
- const duration = Date.now() - iterStart
132
- const iterResult: LoopIterationResult = {
133
- iteration: i,
134
- status: 'failed',
135
- duration,
136
- output: adapterResult.output,
137
- backpressureResults: [],
138
- }
139
- iterations.push(iterResult)
140
- reporter.onIterationDone(i, iterResult)
141
- stoppedReason = 'error'
142
- break
143
- }
144
-
145
- let backpressureResults: BackpressureResult[] = []
146
- if (loop.backpressure && loop.backpressure.length > 0) {
147
- const bp = await runBackpressure(loop.backpressure, reporter, active, timeoutMs)
148
- backpressureResults = bp.results
149
- if (!bp.passed) {
150
- const duration = Date.now() - iterStart
151
- const iterResult: LoopIterationResult = {
152
- iteration: i,
153
- status: 'backpressure-fail',
154
- duration,
155
- output: adapterResult.output,
156
- backpressureResults,
157
- }
158
- iterations.push(iterResult)
159
- reporter.onIterationDone(i, iterResult)
160
- stoppedReason = 'backpressure-fail'
161
- break
162
- }
163
- }
164
-
165
- const duration = Date.now() - iterStart
166
- const iterResult: LoopIterationResult = {
167
- iteration: i,
168
- status: 'done',
169
- duration,
170
- output: adapterResult.output,
171
- backpressureResults,
172
- }
173
- iterations.push(iterResult)
174
- reporter.onIterationDone(i, iterResult)
175
- }
176
- } finally {
177
- process.off('SIGINT', sigintHandler)
178
- }
179
-
180
- const completedAt = new Date()
181
- const completedIterations = iterations.filter((it) => it.status === 'done').length
182
-
183
- const report: LoopRunReport = {
184
- name: spec.name,
185
- mode: 'loop',
186
- startedAt: startedAt.toISOString(),
187
- completedAt: completedAt.toISOString(),
188
- duration: formatDuration(completedAt.getTime() - startedAt.getTime()),
189
- totalIterations: iterations.length,
190
- completedIterations,
191
- stoppedReason,
192
- iterations,
193
- }
194
-
195
- await reporter.onComplete(report)
196
- return report
197
- },
198
- }
199
- }
@@ -1,125 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises'
2
- import { resolve } from 'node:path'
3
- import { formatDuration } from './executor.js'
4
- import { c } from '../prompt.js'
5
- import { appendEvent } from '../log.js'
6
- import { appendLesson } from '../lesson.js'
7
- import type {
8
- LoopRunReport,
9
- LoopIterationResult,
10
- BackpressureResult,
11
- LoopReporter,
12
- } from '../types.js'
13
-
14
- const STOPPED_LABELS: Record<LoopRunReport['stoppedReason'], string> = {
15
- 'max-iterations': 'reached max iterations',
16
- 'plan-empty': 'plan exhausted',
17
- 'backpressure-fail': 'backpressure check failed',
18
- 'user-abort': 'aborted by user',
19
- error: 'agent error',
20
- }
21
-
22
- export function createLoopReporter(
23
- specName: string,
24
- options: { reportDir?: string; verbose?: boolean } = {},
25
- ): LoopReporter {
26
- const reportDir = options.reportDir ?? resolve(process.cwd(), '.opencastle', 'runs')
27
- const verbose = options.verbose ?? false
28
-
29
- return {
30
- onIterationStart(i: number, max: number): void {
31
- console.log(`\n \u21bb Iteration ${i}/${max}`)
32
- },
33
-
34
- onIterationDone(_i: number, result: LoopIterationResult): void {
35
- const dur = formatDuration(result.duration)
36
- if (result.status === 'done') {
37
- console.log(` ${c.green('\u2713')} Completed (${dur})`)
38
- } else if (result.status === 'backpressure-fail') {
39
- console.log(` ${c.yellow('\u26a0')} Backpressure failed (${dur})`)
40
- } else {
41
- console.log(` ${c.red('\u2717')} Failed (${dur})`)
42
- if (result.output) {
43
- const lines = result.output.split('\n').slice(0, 5)
44
- for (const line of lines) console.log(` ${line}`)
45
- if (result.output.split('\n').length > 5) console.log(` ... (truncated)`)
46
- }
47
- }
48
- if (verbose && result.output && result.status === 'done') {
49
- console.log(` Output: ${result.output.slice(0, 500)}`)
50
- }
51
-
52
- appendEvent({
53
- type: 'delegation',
54
- timestamp: new Date().toISOString(),
55
- session_id: specName,
56
- agent: 'autonomous',
57
- task: `Loop iteration ${_i}`,
58
- mechanism: 'run-loop',
59
- outcome: result.status === 'done' ? 'success' : result.status,
60
- duration_sec: Math.round(result.duration / 1000),
61
- }).catch(() => {})
62
- },
63
-
64
- onBackpressureStart(cmd: string): void {
65
- console.log(` \u23ce Running: ${cmd}`)
66
- },
67
-
68
- onBackpressureResult(result: BackpressureResult): void {
69
- if (result.passed) {
70
- console.log(` ${c.green('\u2713')} Exit ${result.exitCode}`)
71
- } else {
72
- console.log(` ${c.red('\u2717')} Exit ${result.exitCode}`)
73
- if (result.output) {
74
- const lines = result.output.split('\n').slice(0, 5)
75
- for (const line of lines) console.log(` ${line}`)
76
- }
77
- }
78
- },
79
-
80
- async onComplete(report: LoopRunReport): Promise<void> {
81
- console.log(`\n ${c.dim('\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500')}`)
82
- console.log(` ${c.bold('Loop complete:')} ${report.name}`)
83
- console.log(` Duration: ${report.duration}`)
84
- console.log()
85
- console.log(
86
- ` Iterations: ${report.totalIterations} total \u2014 ` +
87
- `${c.green(String(report.completedIterations))} completed`,
88
- )
89
- console.log(` Stopped: ${STOPPED_LABELS[report.stoppedReason]}`)
90
-
91
- try {
92
- await mkdir(reportDir, { recursive: true })
93
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
94
- const reportPath = resolve(reportDir, `loop-${specName}-${timestamp}.json`)
95
- await writeFile(reportPath, JSON.stringify(report, null, 2), 'utf8')
96
- console.log(` Report: ${reportPath}`)
97
- } catch (err: unknown) {
98
- console.log(` \u2717 Could not write report: ${(err as Error).message}`)
99
- }
100
- await appendEvent({
101
- type: 'session',
102
- timestamp: new Date().toISOString(),
103
- agent: 'opencastle-run',
104
- task: `Loop: ${report.name}`,
105
- outcome: report.stoppedReason === 'max-iterations' || report.stoppedReason === 'plan-empty' ? 'success' : 'failure',
106
- duration_min: Math.round((new Date(report.completedAt).getTime() - new Date(report.startedAt).getTime()) / 60000),
107
- mode: 'loop',
108
- total_iterations: report.totalIterations,
109
- completed_iterations: report.completedIterations,
110
- stopped_reason: report.stoppedReason,
111
- }).catch(() => {})
112
-
113
- if (report.stoppedReason === 'error' || report.stoppedReason === 'backpressure-fail') {
114
- const lastIter = report.iterations[report.iterations.length - 1]
115
- await appendLesson({
116
- title: `Run loop "${report.name}" stopped: ${report.stoppedReason}`,
117
- category: 'general',
118
- severity: 'medium',
119
- problem: `Loop stopped after ${report.totalIterations} iterations due to ${report.stoppedReason}.${lastIter?.output ? ` Last output: ${lastIter.output.slice(0, 200)}` : ''}`,
120
- }).catch(() => {})
121
- }
122
- console.log()
123
- },
124
- }
125
- }