opencastle 0.10.7 → 0.12.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/README.md +4 -0
- package/bin/cli.mjs +4 -0
- package/dist/cli/convoy/events.d.ts +10 -0
- package/dist/cli/convoy/events.d.ts.map +1 -0
- package/dist/cli/convoy/events.js +27 -0
- package/dist/cli/convoy/events.js.map +1 -0
- package/dist/cli/convoy/events.test.d.ts +2 -0
- package/dist/cli/convoy/events.test.d.ts.map +1 -0
- package/dist/cli/convoy/events.test.js +94 -0
- package/dist/cli/convoy/events.test.js.map +1 -0
- package/dist/cli/convoy/store.d.ts +23 -0
- package/dist/cli/convoy/store.d.ts.map +1 -0
- package/dist/cli/convoy/store.js +210 -0
- package/dist/cli/convoy/store.js.map +1 -0
- package/dist/cli/convoy/store.test.d.ts +2 -0
- package/dist/cli/convoy/store.test.d.ts.map +1 -0
- package/dist/cli/convoy/store.test.js +387 -0
- package/dist/cli/convoy/store.test.js.map +1 -0
- package/dist/cli/convoy/types.d.ts +56 -0
- package/dist/cli/convoy/types.d.ts.map +1 -0
- package/dist/cli/convoy/types.js +2 -0
- package/dist/cli/convoy/types.js.map +1 -0
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +5 -1
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/init.test.js +1 -1
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/lesson.d.ts +17 -0
- package/dist/cli/lesson.d.ts.map +1 -0
- package/dist/cli/lesson.js +294 -0
- package/dist/cli/lesson.js.map +1 -0
- package/dist/cli/log.d.ts +7 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +131 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/executor.test.js +1 -0
- package/dist/cli/run/executor.test.js.map +1 -1
- package/dist/cli/run/loop-executor.d.ts +3 -0
- package/dist/cli/run/loop-executor.d.ts.map +1 -0
- package/dist/cli/run/loop-executor.js +155 -0
- package/dist/cli/run/loop-executor.js.map +1 -0
- package/dist/cli/run/loop-reporter.d.ts +6 -0
- package/dist/cli/run/loop-reporter.d.ts.map +1 -0
- package/dist/cli/run/loop-reporter.js +112 -0
- package/dist/cli/run/loop-reporter.js.map +1 -0
- package/dist/cli/run/reporter.d.ts.map +1 -1
- package/dist/cli/run/reporter.js +28 -1
- package/dist/cli/run/reporter.js.map +1 -1
- package/dist/cli/run/schema.d.ts +4 -0
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +178 -50
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +598 -1
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +84 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +78 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +54 -1
- package/dist/cli/update.js.map +1 -1
- package/package.json +3 -2
- package/src/cli/convoy/events.test.ts +118 -0
- package/src/cli/convoy/events.ts +41 -0
- package/src/cli/convoy/store.test.ts +446 -0
- package/src/cli/convoy/store.ts +308 -0
- package/src/cli/convoy/types.ts +68 -0
- package/src/cli/dashboard.ts +5 -1
- package/src/cli/init.test.ts +1 -1
- package/src/cli/lesson.ts +312 -0
- package/src/cli/log.ts +133 -0
- package/src/cli/run/executor.test.ts +1 -0
- package/src/cli/run/executor.ts +8 -8
- package/src/cli/run/loop-executor.ts +199 -0
- package/src/cli/run/loop-reporter.ts +125 -0
- package/src/cli/run/reporter.ts +30 -1
- package/src/cli/run/schema.test.ts +704 -3
- package/src/cli/run/schema.ts +206 -56
- package/src/cli/run.ts +82 -5
- package/src/cli/types.ts +87 -1
- package/src/cli/update.ts +62 -1
- package/src/dashboard/dist/index.html +14 -15
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/scripts/generate-seed-data.ts +23 -43
- package/src/dashboard/seed-data/events.ndjson +104 -0
- package/src/dashboard/src/pages/index.astro +14 -15
- package/src/orchestrator/agents/api-designer.agent.md +1 -1
- package/src/orchestrator/agents/architect.agent.md +1 -1
- package/src/orchestrator/agents/content-engineer.agent.md +1 -1
- package/src/orchestrator/agents/copywriter.agent.md +1 -1
- package/src/orchestrator/agents/data-expert.agent.md +1 -1
- package/src/orchestrator/agents/database-engineer.agent.md +1 -1
- package/src/orchestrator/agents/developer.agent.md +1 -1
- package/src/orchestrator/agents/devops-expert.agent.md +1 -1
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +1 -1
- package/src/orchestrator/agents/release-manager.agent.md +1 -1
- package/src/orchestrator/agents/security-expert.agent.md +1 -1
- package/src/orchestrator/agents/seo-specialist.agent.md +1 -1
- package/src/orchestrator/agents/session-guard.agent.md +9 -21
- package/src/orchestrator/agents/team-lead.agent.md +8 -34
- package/src/orchestrator/agents/testing-expert.agent.md +1 -1
- package/src/orchestrator/agents/ui-ux-expert.agent.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +11 -12
- package/src/orchestrator/customizations/DISPUTES.md +2 -2
- package/src/orchestrator/customizations/README.md +1 -3
- package/src/orchestrator/customizations/logs/README.md +66 -14
- package/src/orchestrator/instructions/ai-optimization.instructions.md +21 -132
- package/src/orchestrator/instructions/general.instructions.md +35 -181
- package/src/orchestrator/plugins/nx/SKILL.md +1 -1
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +4 -8
- package/src/orchestrator/prompts/bug-fix.prompt.md +4 -4
- package/src/orchestrator/prompts/implement-feature.prompt.md +3 -3
- package/src/orchestrator/prompts/quick-refinement.prompt.md +3 -3
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +11 -11
- package/src/orchestrator/skills/decomposition/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +4 -19
- package/src/orchestrator/skills/git-workflow/SKILL.md +72 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +1 -1
- package/src/orchestrator/skills/observability-logging/SKILL.md +129 -0
- package/src/orchestrator/skills/orchestration-protocols/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +4 -7
- package/src/orchestrator/skills/self-improvement/SKILL.md +13 -26
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +2 -2
- package/src/orchestrator/customizations/logs/delegations.ndjson +0 -1
- package/src/orchestrator/customizations/logs/panels.ndjson +0 -1
- package/src/orchestrator/customizations/logs/reviews.ndjson +0 -0
- package/src/orchestrator/customizations/logs/sessions.ndjson +0 -1
- /package/src/orchestrator/customizations/logs/{disputes.ndjson → events.ndjson} +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
}
|
package/src/cli/run/reporter.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { resolve } from 'node:path'
|
|
3
3
|
import { formatDuration } from './executor.js'
|
|
4
4
|
import { c } from '../prompt.js'
|
|
5
|
+
import { appendEvent } from '../log.js'
|
|
5
6
|
import type {
|
|
6
7
|
TaskSpec,
|
|
7
8
|
Task,
|
|
@@ -56,6 +57,17 @@ export function createReporter(spec: TaskSpec, options: ReporterOptions = {}): R
|
|
|
56
57
|
if (verbose && result.output && result.status === 'done') {
|
|
57
58
|
console.log(` Output: ${result.output.slice(0, 500)}`)
|
|
58
59
|
}
|
|
60
|
+
|
|
61
|
+
appendEvent({
|
|
62
|
+
type: 'delegation',
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
session_id: spec.name,
|
|
65
|
+
agent: task.agent,
|
|
66
|
+
task: `${task.id}: ${task.description}`,
|
|
67
|
+
mechanism: 'run-task',
|
|
68
|
+
outcome: result.status === 'done' ? 'success' : result.status,
|
|
69
|
+
duration_sec: Math.round(result.duration / 1000),
|
|
70
|
+
}).catch(() => {})
|
|
59
71
|
},
|
|
60
72
|
|
|
61
73
|
onTaskSkipped(task: Task, reason: string): void {
|
|
@@ -95,6 +107,23 @@ export function createReporter(spec: TaskSpec, options: ReporterOptions = {}): R
|
|
|
95
107
|
console.log(` ${ICONS.failed} Could not write report: ${(err as Error).message}`)
|
|
96
108
|
}
|
|
97
109
|
|
|
110
|
+
await appendEvent({
|
|
111
|
+
type: 'session',
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
agent: 'opencastle-run',
|
|
114
|
+
model: spec.adapter,
|
|
115
|
+
task: `Run: ${report.name}`,
|
|
116
|
+
outcome: report.summary.failed > 0 || report.summary['timed-out'] > 0 ? 'failure' : 'success',
|
|
117
|
+
duration_min: Math.round((new Date(report.completedAt).getTime() - new Date(report.startedAt).getTime()) / 60000),
|
|
118
|
+
files_changed: 0,
|
|
119
|
+
retries: 0,
|
|
120
|
+
mode: 'tasks',
|
|
121
|
+
tasks_total: report.summary.total,
|
|
122
|
+
tasks_done: report.summary.done,
|
|
123
|
+
tasks_failed: report.summary.failed,
|
|
124
|
+
tasks_skipped: report.summary.skipped,
|
|
125
|
+
}).catch(() => {})
|
|
126
|
+
|
|
98
127
|
console.log()
|
|
99
128
|
},
|
|
100
129
|
}
|
|
@@ -108,7 +137,7 @@ export function printExecutionPlan(spec: TaskSpec, phases: Task[][]): void {
|
|
|
108
137
|
console.log(` ${c.dim('Adapter:')} ${c.cyan(spec.adapter)}`)
|
|
109
138
|
console.log(` ${c.dim('Concurrency:')} ${c.yellow(String(spec.concurrency))}`)
|
|
110
139
|
console.log(` ${c.dim('On failure:')} ${c.yellow(spec.on_failure)}`)
|
|
111
|
-
console.log(` ${c.dim('Tasks:')} ${c.yellow(String(spec.tasks
|
|
140
|
+
console.log(` ${c.dim('Tasks:')} ${c.yellow(String(spec.tasks?.length ?? 0))}`)
|
|
112
141
|
console.log(` ${c.dim('──────────────────────────────────')}`)
|
|
113
142
|
|
|
114
143
|
for (let i = 0; i < phases.length; i++) {
|