opencastle 0.13.0 → 0.15.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/dist/cli/convoy/engine.d.ts +38 -0
- package/dist/cli/convoy/engine.d.ts.map +1 -0
- package/dist/cli/convoy/engine.js +416 -0
- package/dist/cli/convoy/engine.js.map +1 -0
- package/dist/cli/convoy/engine.test.d.ts +2 -0
- package/dist/cli/convoy/engine.test.d.ts.map +1 -0
- package/dist/cli/convoy/engine.test.js +1140 -0
- package/dist/cli/convoy/engine.test.js.map +1 -0
- package/dist/cli/convoy/health.d.ts +23 -0
- package/dist/cli/convoy/health.d.ts.map +1 -0
- package/dist/cli/convoy/health.js +69 -0
- package/dist/cli/convoy/health.js.map +1 -0
- package/dist/cli/convoy/health.test.d.ts +2 -0
- package/dist/cli/convoy/health.test.d.ts.map +1 -0
- package/dist/cli/convoy/health.test.js +392 -0
- package/dist/cli/convoy/health.test.js.map +1 -0
- package/dist/cli/convoy/store.d.ts +1 -0
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +5 -0
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/run/schema.d.ts +5 -0
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +98 -143
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +53 -215
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +202 -104
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +2 -58
- package/dist/cli/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/convoy/engine.test.ts +1349 -0
- package/src/cli/convoy/engine.ts +521 -0
- package/src/cli/convoy/health.test.ts +456 -0
- package/src/cli/convoy/health.ts +111 -0
- package/src/cli/convoy/store.ts +7 -0
- package/src/cli/run/schema.test.ts +61 -241
- package/src/cli/run/schema.ts +105 -153
- package/src/cli/run.ts +216 -105
- package/src/cli/types.ts +2 -66
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/dist/cli/run/loop-executor.d.ts +0 -3
- package/dist/cli/run/loop-executor.d.ts.map +0 -1
- package/dist/cli/run/loop-executor.js +0 -155
- package/dist/cli/run/loop-executor.js.map +0 -1
- package/dist/cli/run/loop-reporter.d.ts +0 -6
- package/dist/cli/run/loop-reporter.d.ts.map +0 -1
- package/dist/cli/run/loop-reporter.js +0 -112
- package/dist/cli/run/loop-reporter.js.map +0 -1
- package/src/cli/run/loop-executor.ts +0 -199
- package/src/cli/run/loop-reporter.ts +0 -125
package/src/cli/run.ts
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
2
3
|
import { resolve } from 'node:path'
|
|
3
|
-
import {
|
|
4
|
+
import { parseTaskSpecText, isConvoySpec } from './run/schema.js'
|
|
4
5
|
import { createExecutor, buildPhases } from './run/executor.js'
|
|
5
6
|
import { getAdapter, detectAdapter } from './run/adapters/index.js'
|
|
6
7
|
import { createReporter, printExecutionPlan } from './run/reporter.js'
|
|
7
8
|
import type { CliContext, RunOptions } from './types.js'
|
|
9
|
+
import type { ConvoyResult } from './convoy/engine.js'
|
|
8
10
|
|
|
9
11
|
const HELP = `
|
|
10
12
|
opencastle run [options]
|
|
11
13
|
|
|
12
14
|
Process a task queue from a spec file, delegating to AI agents autonomously.
|
|
13
|
-
|
|
15
|
+
Version 1 specs use the Convoy Engine; legacy specs use the standard executor.
|
|
14
16
|
|
|
15
17
|
Options:
|
|
16
18
|
--file, -f <path> Task spec file (default: opencastle.tasks.yml)
|
|
17
19
|
--dry-run Show execution plan without running
|
|
18
|
-
--concurrency, -c <n> Override max parallel tasks
|
|
20
|
+
--concurrency, -c <n> Override max parallel tasks
|
|
19
21
|
--adapter, -a <name> Override agent runtime adapter
|
|
20
22
|
--report-dir <path> Where to write run reports (default: .opencastle/runs)
|
|
21
23
|
--verbose Show full agent output
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
+
--resume Resume the last interrupted convoy from .opencastle/convoy.db
|
|
25
|
+
--status Print the current convoy state from .opencastle/convoy.db
|
|
24
26
|
--help, -h Show this help
|
|
25
27
|
`
|
|
26
28
|
|
|
@@ -36,8 +38,8 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
36
38
|
reportDir: null,
|
|
37
39
|
verbose: false,
|
|
38
40
|
help: false,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
resume: false,
|
|
42
|
+
status: false,
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -79,26 +81,12 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
79
81
|
case '--verbose':
|
|
80
82
|
opts.verbose = true
|
|
81
83
|
break
|
|
82
|
-
case '--
|
|
83
|
-
|
|
84
|
-
const val = parseInt(args[++i], 10)
|
|
85
|
-
if (!Number.isFinite(val) || val < 1) {
|
|
86
|
-
console.error(` \u2717 --max-iterations must be an integer >= 1`)
|
|
87
|
-
process.exit(1)
|
|
88
|
-
}
|
|
89
|
-
opts.maxIterations = val
|
|
84
|
+
case '--resume':
|
|
85
|
+
opts.resume = true
|
|
90
86
|
break
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (i + 1 >= args.length) { console.error(' \u2717 --mode requires a name'); process.exit(1) }
|
|
94
|
-
const modeVal = args[++i]
|
|
95
|
-
if (modeVal !== 'tasks' && modeVal !== 'loop') {
|
|
96
|
-
console.error(` \u2717 --mode must be one of: tasks, loop`)
|
|
97
|
-
process.exit(1)
|
|
98
|
-
}
|
|
99
|
-
opts.mode = modeVal
|
|
87
|
+
case '--status':
|
|
88
|
+
opts.status = true
|
|
100
89
|
break
|
|
101
|
-
}
|
|
102
90
|
default:
|
|
103
91
|
console.error(` ✗ Unknown option: ${arg}`)
|
|
104
92
|
console.log(HELP)
|
|
@@ -109,6 +97,61 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
109
97
|
return opts
|
|
110
98
|
}
|
|
111
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Print a user-friendly adapter unavailable error.
|
|
102
|
+
*/
|
|
103
|
+
function printAdapterError(detectionFailed: boolean, adapterName: string): void {
|
|
104
|
+
if (detectionFailed) {
|
|
105
|
+
console.error(
|
|
106
|
+
` ✗ No agent CLI found on your PATH.\n` +
|
|
107
|
+
` Install one of the following adapters:\n` +
|
|
108
|
+
` • copilot — https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli\n` +
|
|
109
|
+
` • claude — npm install -g @anthropic-ai/claude-code\n` +
|
|
110
|
+
` • cursor — https://cursor.com (Cursor > Install CLI)\n` +
|
|
111
|
+
`\n` +
|
|
112
|
+
` Or specify an adapter explicitly: opencastle run --adapter <name>`
|
|
113
|
+
)
|
|
114
|
+
} else {
|
|
115
|
+
const hints: Record<string, string> = {
|
|
116
|
+
'claude-code':
|
|
117
|
+
' Install: npm install -g @anthropic-ai/claude-code\n' +
|
|
118
|
+
' Docs: https://docs.anthropic.com/en/docs/claude-code',
|
|
119
|
+
copilot:
|
|
120
|
+
' Requires the Copilot CLI installed and authenticated:\n' +
|
|
121
|
+
' https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli\n' +
|
|
122
|
+
' Docs: https://docs.github.com/en/copilot',
|
|
123
|
+
cursor:
|
|
124
|
+
' The Cursor agent CLI ships with the Cursor editor.\n' +
|
|
125
|
+
' Install Cursor from https://cursor.com and ensure the\n' +
|
|
126
|
+
' "agent" command is on your PATH (Cursor > Install CLI).',
|
|
127
|
+
}
|
|
128
|
+
const cliName = adapterName === 'claude-code' ? 'claude' : adapterName
|
|
129
|
+
const hint = hints[adapterName] ?? ''
|
|
130
|
+
console.error(
|
|
131
|
+
` ✗ Adapter "${adapterName}" is not available.\n` +
|
|
132
|
+
` Make sure the "${cliName}" CLI is installed and on your PATH.\n` +
|
|
133
|
+
hint
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Print a convoy result summary.
|
|
140
|
+
*/
|
|
141
|
+
function printConvoyResult(result: ConvoyResult): void {
|
|
142
|
+
console.log(`\n ──────────────────────────────────────`)
|
|
143
|
+
console.log(` Convoy ${result.status}: ${result.duration}`)
|
|
144
|
+
console.log(
|
|
145
|
+
` Done: ${result.summary.done} | Failed: ${result.summary.failed} | Skipped: ${result.summary.skipped} | Timed out: ${result.summary.timedOut}`
|
|
146
|
+
)
|
|
147
|
+
if (result.gateResults) {
|
|
148
|
+
console.log(` Gates:`)
|
|
149
|
+
for (const g of result.gateResults) {
|
|
150
|
+
console.log(` ${g.passed ? '✓' : '✗'} ${g.command}`)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
112
155
|
/**
|
|
113
156
|
* CLI entry point for the `run` command.
|
|
114
157
|
*/
|
|
@@ -120,15 +163,133 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
120
163
|
return
|
|
121
164
|
}
|
|
122
165
|
|
|
166
|
+
const dbPath = resolve(process.cwd(), '.opencastle', 'convoy.db')
|
|
167
|
+
|
|
168
|
+
// ── --status flag ─────────────────────────────────────────────
|
|
169
|
+
if (opts.status) {
|
|
170
|
+
if (!existsSync(dbPath)) {
|
|
171
|
+
console.log(' No convoy database found at .opencastle/convoy.db')
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
175
|
+
const store = createConvoyStore(dbPath)
|
|
176
|
+
try {
|
|
177
|
+
const convoy = store.getLatestConvoy()
|
|
178
|
+
if (!convoy) {
|
|
179
|
+
console.log(' No convoy records found.')
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const tasks = store.getTasksByConvoy(convoy.id)
|
|
183
|
+
const byStatus = tasks.reduce((acc, t) => {
|
|
184
|
+
acc[t.status] = (acc[t.status] ?? 0) + 1
|
|
185
|
+
return acc
|
|
186
|
+
}, {} as Record<string, number>)
|
|
187
|
+
console.log(`\n Convoy: ${convoy.name}`)
|
|
188
|
+
console.log(` ID: ${convoy.id}`)
|
|
189
|
+
console.log(` Status: ${convoy.status}`)
|
|
190
|
+
console.log(` Branch: ${convoy.branch ?? '(none)'}`)
|
|
191
|
+
console.log(` Created: ${convoy.created_at}`)
|
|
192
|
+
if (convoy.started_at) console.log(` Started: ${convoy.started_at}`)
|
|
193
|
+
if (convoy.finished_at) console.log(` Finished: ${convoy.finished_at}`)
|
|
194
|
+
console.log(`\n Tasks:`)
|
|
195
|
+
for (const [status, count] of Object.entries(byStatus)) {
|
|
196
|
+
console.log(` ${status}: ${count}`)
|
|
197
|
+
}
|
|
198
|
+
console.log(` total: ${tasks.length}`)
|
|
199
|
+
} finally {
|
|
200
|
+
store.close()
|
|
201
|
+
}
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── --resume flag ─────────────────────────────────────────────
|
|
206
|
+
if (opts.resume) {
|
|
207
|
+
if (!existsSync(dbPath)) {
|
|
208
|
+
console.error(' ✗ No convoy database found at .opencastle/convoy.db')
|
|
209
|
+
console.error(' Run a convoy spec first: opencastle run convoy.yml')
|
|
210
|
+
process.exit(1)
|
|
211
|
+
}
|
|
212
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
213
|
+
const store = createConvoyStore(dbPath)
|
|
214
|
+
const convoy = store.getLatestConvoy()
|
|
215
|
+
store.close()
|
|
216
|
+
if (!convoy) {
|
|
217
|
+
console.error(' ✗ No convoy records found in .opencastle/convoy.db')
|
|
218
|
+
process.exit(1)
|
|
219
|
+
}
|
|
220
|
+
if (convoy.status === 'done' || convoy.status === 'failed') {
|
|
221
|
+
console.error(
|
|
222
|
+
` ✗ Last convoy "${convoy.name}" already finished with status: ${convoy.status}`
|
|
223
|
+
)
|
|
224
|
+
console.error(` Only interrupted (running/pending) convoys can be resumed.`)
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const resumeSpec = parseTaskSpecText(convoy.spec_yaml)
|
|
229
|
+
if (opts.concurrency !== null) resumeSpec.concurrency = opts.concurrency
|
|
230
|
+
if (opts.adapter !== null) resumeSpec.adapter = opts.adapter
|
|
231
|
+
if (opts.verbose) resumeSpec._verbose = true
|
|
232
|
+
|
|
233
|
+
let resumeDetectionFailed = false
|
|
234
|
+
if (!resumeSpec.adapter) {
|
|
235
|
+
const detected = await detectAdapter()
|
|
236
|
+
if (detected) {
|
|
237
|
+
resumeSpec.adapter = detected
|
|
238
|
+
console.log(` ℹ Auto-detected adapter: ${detected}`)
|
|
239
|
+
} else {
|
|
240
|
+
resumeDetectionFailed = true
|
|
241
|
+
resumeSpec.adapter = 'claude-code'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const resumeAdapter = await getAdapter(resumeSpec.adapter)
|
|
246
|
+
const resumeAvailable = await resumeAdapter.isAvailable()
|
|
247
|
+
if (!resumeAvailable) {
|
|
248
|
+
printAdapterError(resumeDetectionFailed, resumeSpec.adapter)
|
|
249
|
+
process.exit(1)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(`\n \uD83C\uDFF0 OpenCastle Convoy (Resume): ${convoy.name}`)
|
|
253
|
+
console.log(` Convoy ID: ${convoy.id}`)
|
|
254
|
+
const { createConvoyEngine } = await import('./convoy/engine.js')
|
|
255
|
+
const resumeEngine = createConvoyEngine({
|
|
256
|
+
spec: resumeSpec,
|
|
257
|
+
specYaml: convoy.spec_yaml,
|
|
258
|
+
adapter: resumeAdapter,
|
|
259
|
+
verbose: opts.verbose,
|
|
260
|
+
})
|
|
261
|
+
const resumeResult = await resumeEngine.resume(convoy.id)
|
|
262
|
+
printConvoyResult(resumeResult)
|
|
263
|
+
process.exit(resumeResult.status !== 'done' ? 1 : 0)
|
|
264
|
+
}
|
|
265
|
+
|
|
123
266
|
// ── Read and validate spec ────────────────────────────────────
|
|
124
267
|
const specPath = resolve(process.cwd(), opts.file)
|
|
125
|
-
|
|
268
|
+
let specText = ''
|
|
269
|
+
try {
|
|
270
|
+
specText = await readFile(specPath, 'utf8')
|
|
271
|
+
} catch (err: unknown) {
|
|
272
|
+
const e = err as Error & { code?: string }
|
|
273
|
+
if (e.code === 'ENOENT') {
|
|
274
|
+
console.error(` ✗ Task spec file not found: ${opts.file}`)
|
|
275
|
+
} else {
|
|
276
|
+
console.error(` ✗ Cannot read task spec file: ${e.message}`)
|
|
277
|
+
}
|
|
278
|
+
process.exit(1)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let spec
|
|
282
|
+
try {
|
|
283
|
+
spec = parseTaskSpecText(specText)
|
|
284
|
+
} catch (err: unknown) {
|
|
285
|
+
console.error(` ✗ ${(err as Error).message}`)
|
|
286
|
+
process.exit(1)
|
|
287
|
+
}
|
|
126
288
|
|
|
127
289
|
// Apply CLI overrides
|
|
128
290
|
if (opts.concurrency !== null) spec.concurrency = opts.concurrency
|
|
129
291
|
if (opts.adapter !== null) spec.adapter = opts.adapter
|
|
130
292
|
if (opts.verbose) spec._verbose = true
|
|
131
|
-
if (opts.mode !== null) spec.mode = opts.mode as 'tasks' | 'loop'
|
|
132
293
|
|
|
133
294
|
// ── Auto-detect adapter if not specified ─────────────────────
|
|
134
295
|
let detectionFailed = false
|
|
@@ -145,22 +306,13 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
145
306
|
|
|
146
307
|
// ── Dry run ──────────────────────────────────────────────────
|
|
147
308
|
if (opts.dryRun) {
|
|
148
|
-
if (spec
|
|
149
|
-
|
|
150
|
-
console.log(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
console.log(`
|
|
154
|
-
console.log(`
|
|
155
|
-
if (loop.plan_file) console.log(` Plan file: ${loop.plan_file}`)
|
|
156
|
-
if (loop.model) console.log(` Model: ${loop.model}`)
|
|
157
|
-
if (loop.backpressure?.length) {
|
|
158
|
-
console.log(` Backpressure:`)
|
|
159
|
-
for (const cmd of loop.backpressure) {
|
|
160
|
-
console.log(` - ${cmd}`)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return
|
|
309
|
+
if (isConvoySpec(spec)) {
|
|
310
|
+
console.log(`\n \uD83C\uDFF0 Convoy Plan: ${spec.name}`)
|
|
311
|
+
console.log(
|
|
312
|
+
` Adapter: ${spec.adapter} | Concurrency: ${spec.concurrency} | Tasks: ${spec.tasks!.length}`
|
|
313
|
+
)
|
|
314
|
+
if (spec.branch) console.log(` Branch: ${spec.branch}`)
|
|
315
|
+
if (spec.gates?.length) console.log(` Gates: ${spec.gates.length} validation commands`)
|
|
164
316
|
}
|
|
165
317
|
const phases = buildPhases(spec.tasks!)
|
|
166
318
|
printExecutionPlan(spec, phases)
|
|
@@ -171,78 +323,37 @@ export default async function run({ args }: CliContext): Promise<void> {
|
|
|
171
323
|
const adapter = await getAdapter(spec.adapter)
|
|
172
324
|
const available = await adapter.isAvailable()
|
|
173
325
|
if (!available) {
|
|
174
|
-
|
|
175
|
-
console.error(
|
|
176
|
-
` ✗ No agent CLI found on your PATH.\n` +
|
|
177
|
-
` Install one of the following adapters:\n` +
|
|
178
|
-
` • copilot — https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli\n` +
|
|
179
|
-
` • claude — npm install -g @anthropic-ai/claude-code\n` +
|
|
180
|
-
` • cursor — https://cursor.com (Cursor > Install CLI)\n` +
|
|
181
|
-
`\n` +
|
|
182
|
-
` Or specify an adapter explicitly: opencastle run --adapter <name>`
|
|
183
|
-
)
|
|
184
|
-
} else {
|
|
185
|
-
const hints: Record<string, string> = {
|
|
186
|
-
'claude-code':
|
|
187
|
-
' Install: npm install -g @anthropic-ai/claude-code\n' +
|
|
188
|
-
' Docs: https://docs.anthropic.com/en/docs/claude-code',
|
|
189
|
-
copilot:
|
|
190
|
-
' Requires the Copilot CLI installed and authenticated:\n' +
|
|
191
|
-
' https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli\n' +
|
|
192
|
-
' Docs: https://docs.github.com/en/copilot',
|
|
193
|
-
cursor:
|
|
194
|
-
' The Cursor agent CLI ships with the Cursor editor.\n' +
|
|
195
|
-
' Install Cursor from https://cursor.com and ensure the\n' +
|
|
196
|
-
' "agent" command is on your PATH (Cursor > Install CLI).',
|
|
197
|
-
}
|
|
198
|
-
const cliName = spec.adapter === 'claude-code' ? 'claude' : spec.adapter
|
|
199
|
-
const hint = hints[spec.adapter] ?? ''
|
|
200
|
-
console.error(
|
|
201
|
-
` ✗ Adapter "${spec.adapter}" is not available.\n` +
|
|
202
|
-
` Make sure the "${cliName}" CLI is installed and on your PATH.\n` +
|
|
203
|
-
hint
|
|
204
|
-
)
|
|
205
|
-
}
|
|
326
|
+
printAdapterError(detectionFailed, spec.adapter)
|
|
206
327
|
process.exit(1)
|
|
207
328
|
}
|
|
208
329
|
|
|
209
|
-
// ──
|
|
210
|
-
if (spec
|
|
211
|
-
const {
|
|
212
|
-
|
|
330
|
+
// ── Convoy engine path (version: 1 specs) ────────────────────
|
|
331
|
+
if (isConvoySpec(spec)) {
|
|
332
|
+
const { createConvoyEngine } = await import('./convoy/engine.js')
|
|
333
|
+
console.log(`\n \uD83C\uDFF0 OpenCastle Convoy: ${spec.name}`)
|
|
334
|
+
console.log(
|
|
335
|
+
` Adapter: ${adapter.name} | Concurrency: ${spec.concurrency} | Tasks: ${spec.tasks!.length}`
|
|
336
|
+
)
|
|
337
|
+
if (spec.branch) console.log(` Branch: ${spec.branch}`)
|
|
338
|
+
if (spec.gates?.length) console.log(` Gates: ${spec.gates.length} validation commands`)
|
|
213
339
|
|
|
214
|
-
|
|
215
|
-
spec
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const promptPath = resolve(process.cwd(), spec.loop!.prompt)
|
|
219
|
-
try {
|
|
220
|
-
await readFile(promptPath)
|
|
221
|
-
} catch {
|
|
222
|
-
console.error(` \u2717 Prompt file not found: ${spec.loop!.prompt}`)
|
|
223
|
-
process.exit(1)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
console.log(`\n \uD83C\uDFF0 OpenCastle Loop: ${spec.name}`)
|
|
227
|
-
console.log(` Adapter: ${adapter.name} | Max iterations: ${spec.loop!.max_iterations} | Timeout: ${spec.loop!.timeout}`)
|
|
228
|
-
if (spec.loop!.backpressure?.length) {
|
|
229
|
-
console.log(` Backpressure: ${spec.loop!.backpressure.join(', ')}`)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const loopReporter = createLoopReporter(spec.name, {
|
|
233
|
-
reportDir: opts.reportDir ? resolve(process.cwd(), opts.reportDir) : undefined,
|
|
340
|
+
const engine = createConvoyEngine({
|
|
341
|
+
spec,
|
|
342
|
+
specYaml: specText,
|
|
343
|
+
adapter,
|
|
234
344
|
verbose: opts.verbose,
|
|
235
345
|
})
|
|
236
346
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const failed = loopReport.stoppedReason === 'error' || loopReport.stoppedReason === 'backpressure-fail'
|
|
241
|
-
process.exit(failed ? 1 : 0)
|
|
347
|
+
const result = await engine.run()
|
|
348
|
+
printConvoyResult(result)
|
|
349
|
+
process.exit(result.status !== 'done' ? 1 : 0)
|
|
242
350
|
}
|
|
243
351
|
|
|
352
|
+
// ── Legacy executor path ──────────────────────────────────────
|
|
244
353
|
console.log(`\n \uD83C\uDFF0 OpenCastle Run: ${spec.name}`)
|
|
245
|
-
console.log(
|
|
354
|
+
console.log(
|
|
355
|
+
` Adapter: ${adapter.name} | Concurrency: ${spec.concurrency} | Tasks: ${spec.tasks!.length}`
|
|
356
|
+
)
|
|
246
357
|
|
|
247
358
|
const reporter = createReporter(spec, {
|
|
248
359
|
reportDir: opts.reportDir
|
package/src/cli/types.ts
CHANGED
|
@@ -134,22 +134,6 @@ export interface TaskDefaults {
|
|
|
134
134
|
agent?: string;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/** Loop execution configuration. */
|
|
138
|
-
export interface LoopConfig {
|
|
139
|
-
/** Maximum number of agent iterations (default 20). */
|
|
140
|
-
max_iterations: number;
|
|
141
|
-
/** Path to the prompt file read each iteration. */
|
|
142
|
-
prompt: string;
|
|
143
|
-
/** Path to the plan file (default 'IMPLEMENTATION_PLAN.md'). */
|
|
144
|
-
plan_file?: string;
|
|
145
|
-
/** Per-iteration timeout (default '10m'). */
|
|
146
|
-
timeout: string;
|
|
147
|
-
/** Model override for loop sessions. */
|
|
148
|
-
model?: string;
|
|
149
|
-
/** Shell commands that must exit 0 after each iteration. */
|
|
150
|
-
backpressure?: string[];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
137
|
/** Validated task spec from YAML. */
|
|
154
138
|
export interface TaskSpec {
|
|
155
139
|
name: string;
|
|
@@ -157,8 +141,6 @@ export interface TaskSpec {
|
|
|
157
141
|
on_failure: 'continue' | 'stop';
|
|
158
142
|
adapter: string;
|
|
159
143
|
tasks?: Task[];
|
|
160
|
-
mode?: 'tasks' | 'loop';
|
|
161
|
-
loop?: LoopConfig;
|
|
162
144
|
_verbose?: boolean;
|
|
163
145
|
/** Spec schema version (1 for Convoy Engine format). */
|
|
164
146
|
version?: number;
|
|
@@ -271,8 +253,8 @@ export interface RunOptions {
|
|
|
271
253
|
reportDir: string | null;
|
|
272
254
|
verbose: boolean;
|
|
273
255
|
help: boolean;
|
|
274
|
-
|
|
275
|
-
|
|
256
|
+
resume: boolean;
|
|
257
|
+
status: boolean;
|
|
276
258
|
}
|
|
277
259
|
|
|
278
260
|
/** Validation result. */
|
|
@@ -292,49 +274,3 @@ export interface Executor {
|
|
|
292
274
|
run(): Promise<RunReport>;
|
|
293
275
|
getPhases(): Task[][];
|
|
294
276
|
}
|
|
295
|
-
|
|
296
|
-
// ── Loop executor types ────────────────────────────────────────
|
|
297
|
-
|
|
298
|
-
/** Result of a single backpressure command run. */
|
|
299
|
-
export interface BackpressureResult {
|
|
300
|
-
command: string;
|
|
301
|
-
exitCode: number;
|
|
302
|
-
output: string;
|
|
303
|
-
passed: boolean;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/** Result of a single loop iteration. */
|
|
307
|
-
export interface LoopIterationResult {
|
|
308
|
-
iteration: number;
|
|
309
|
-
status: 'done' | 'failed' | 'backpressure-fail';
|
|
310
|
-
duration: number;
|
|
311
|
-
output: string;
|
|
312
|
-
backpressureResults?: BackpressureResult[];
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/** Final report produced by the loop executor. */
|
|
316
|
-
export interface LoopRunReport {
|
|
317
|
-
name: string;
|
|
318
|
-
mode: 'loop';
|
|
319
|
-
startedAt: string;
|
|
320
|
-
completedAt: string;
|
|
321
|
-
duration: string;
|
|
322
|
-
totalIterations: number;
|
|
323
|
-
completedIterations: number;
|
|
324
|
-
stoppedReason: 'max-iterations' | 'plan-empty' | 'backpressure-fail' | 'user-abort' | 'error';
|
|
325
|
-
iterations: LoopIterationResult[];
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/** Reporter interface for loop execution progress. */
|
|
329
|
-
export interface LoopReporter {
|
|
330
|
-
onIterationStart(iteration: number, maxIterations: number): void;
|
|
331
|
-
onIterationDone(iteration: number, result: LoopIterationResult): void;
|
|
332
|
-
onBackpressureStart(command: string): void;
|
|
333
|
-
onBackpressureResult(result: BackpressureResult): void;
|
|
334
|
-
onComplete(report: LoopRunReport): Promise<void>;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/** Executor for loop-mode run specs. */
|
|
338
|
-
export interface LoopExecutor {
|
|
339
|
-
run(): Promise<LoopRunReport>;
|
|
340
|
-
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
2
|
+
"hash": "4d31b055",
|
|
3
3
|
"configHash": "30f8ea04",
|
|
4
|
-
"lockfileHash": "
|
|
5
|
-
"browserHash": "
|
|
4
|
+
"lockfileHash": "cbad4e8c",
|
|
5
|
+
"browserHash": "a1ae7e1e",
|
|
6
6
|
"optimized": {
|
|
7
7
|
"astro > cssesc": {
|
|
8
8
|
"src": "../../../../../node_modules/cssesc/cssesc.js",
|
|
9
9
|
"file": "astro___cssesc.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "4a8534ee",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"astro > aria-query": {
|
|
14
14
|
"src": "../../../../../node_modules/aria-query/lib/index.js",
|
|
15
15
|
"file": "astro___aria-query.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "85939f45",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"astro > axobject-query": {
|
|
20
20
|
"src": "../../../../../node_modules/axobject-query/lib/index.js",
|
|
21
21
|
"file": "astro___axobject-query.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "dc75bd79",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
}
|
|
25
25
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loop-executor.d.ts","sourceRoot":"","sources":["../../../src/cli/run/loop-executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,QAAQ,EAIR,YAAY,EACZ,YAAY,EACZ,YAAY,EAEb,MAAM,aAAa,CAAA;AA2DpB,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,YAAY,GACrB,YAAY,CAwHd"}
|