opencastle 0.27.0 → 0.27.2
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/bin/cli.mjs +6 -0
- package/dist/cli/agents.d.ts +3 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +161 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/baselines.d.ts +3 -0
- package/dist/cli/baselines.d.ts.map +1 -0
- package/dist/cli/baselines.js +128 -0
- package/dist/cli/baselines.js.map +1 -0
- package/dist/cli/convoy/dashboard-types.d.ts +146 -0
- package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
- package/dist/cli/convoy/dashboard-types.js +2 -0
- package/dist/cli/convoy/dashboard-types.js.map +1 -0
- package/dist/cli/convoy/engine.d.ts +67 -2
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +2036 -28
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +1659 -70
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/cli/convoy/event-schemas.d.ts +9 -0
- package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
- package/dist/cli/convoy/event-schemas.js +185 -0
- package/dist/cli/convoy/event-schemas.js.map +1 -0
- package/dist/cli/convoy/events.d.ts +12 -1
- package/dist/cli/convoy/events.d.ts.map +1 -1
- package/dist/cli/convoy/events.js +186 -13
- package/dist/cli/convoy/events.js.map +1 -1
- package/dist/cli/convoy/events.test.js +325 -28
- package/dist/cli/convoy/events.test.js.map +1 -1
- package/dist/cli/convoy/expertise.d.ts +16 -0
- package/dist/cli/convoy/expertise.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.js +121 -0
- package/dist/cli/convoy/expertise.js.map +1 -0
- package/dist/cli/convoy/expertise.test.d.ts +2 -0
- package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.test.js +96 -0
- package/dist/cli/convoy/expertise.test.js.map +1 -0
- package/dist/cli/convoy/export.test.js +1 -0
- package/dist/cli/convoy/export.test.js.map +1 -1
- package/dist/cli/convoy/formula.d.ts +19 -0
- package/dist/cli/convoy/formula.d.ts.map +1 -0
- package/dist/cli/convoy/formula.js +142 -0
- package/dist/cli/convoy/formula.js.map +1 -0
- package/dist/cli/convoy/formula.test.d.ts +2 -0
- package/dist/cli/convoy/formula.test.d.ts.map +1 -0
- package/dist/cli/convoy/formula.test.js +342 -0
- package/dist/cli/convoy/formula.test.js.map +1 -0
- package/dist/cli/convoy/gates.d.ts +128 -0
- package/dist/cli/convoy/gates.d.ts.map +1 -0
- package/dist/cli/convoy/gates.js +606 -0
- package/dist/cli/convoy/gates.js.map +1 -0
- package/dist/cli/convoy/gates.test.d.ts +2 -0
- package/dist/cli/convoy/gates.test.d.ts.map +1 -0
- package/dist/cli/convoy/gates.test.js +976 -0
- package/dist/cli/convoy/gates.test.js.map +1 -0
- package/dist/cli/convoy/health.d.ts +11 -0
- package/dist/cli/convoy/health.d.ts.map +1 -1
- package/dist/cli/convoy/health.js +54 -0
- package/dist/cli/convoy/health.js.map +1 -1
- package/dist/cli/convoy/health.test.js +56 -1
- package/dist/cli/convoy/health.test.js.map +1 -1
- package/dist/cli/convoy/issues.d.ts +8 -0
- package/dist/cli/convoy/issues.d.ts.map +1 -0
- package/dist/cli/convoy/issues.js +98 -0
- package/dist/cli/convoy/issues.js.map +1 -0
- package/dist/cli/convoy/issues.test.d.ts +2 -0
- package/dist/cli/convoy/issues.test.d.ts.map +1 -0
- package/dist/cli/convoy/issues.test.js +107 -0
- package/dist/cli/convoy/issues.test.js.map +1 -0
- package/dist/cli/convoy/knowledge.d.ts +5 -0
- package/dist/cli/convoy/knowledge.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.js +116 -0
- package/dist/cli/convoy/knowledge.js.map +1 -0
- package/dist/cli/convoy/knowledge.test.d.ts +2 -0
- package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.test.js +87 -0
- package/dist/cli/convoy/knowledge.test.js.map +1 -0
- package/dist/cli/convoy/lessons.d.ts +17 -0
- package/dist/cli/convoy/lessons.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.js +149 -0
- package/dist/cli/convoy/lessons.js.map +1 -0
- package/dist/cli/convoy/lessons.test.d.ts +2 -0
- package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.test.js +135 -0
- package/dist/cli/convoy/lessons.test.js.map +1 -0
- package/dist/cli/convoy/lock.d.ts +13 -0
- package/dist/cli/convoy/lock.d.ts.map +1 -0
- package/dist/cli/convoy/lock.js +88 -0
- package/dist/cli/convoy/lock.js.map +1 -0
- package/dist/cli/convoy/lock.test.d.ts +2 -0
- package/dist/cli/convoy/lock.test.d.ts.map +1 -0
- package/dist/cli/convoy/lock.test.js +136 -0
- package/dist/cli/convoy/lock.test.js.map +1 -0
- package/dist/cli/convoy/log-merge.test.d.ts +2 -0
- package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
- package/dist/cli/convoy/log-merge.test.js +147 -0
- package/dist/cli/convoy/log-merge.test.js.map +1 -0
- package/dist/cli/convoy/merge.d.ts +4 -0
- package/dist/cli/convoy/merge.d.ts.map +1 -1
- package/dist/cli/convoy/merge.js +18 -1
- package/dist/cli/convoy/merge.js.map +1 -1
- package/dist/cli/convoy/merge.test.js +6 -7
- package/dist/cli/convoy/merge.test.js.map +1 -1
- package/dist/cli/convoy/partition.d.ts +51 -0
- package/dist/cli/convoy/partition.d.ts.map +1 -0
- package/dist/cli/convoy/partition.js +186 -0
- package/dist/cli/convoy/partition.js.map +1 -0
- package/dist/cli/convoy/partition.test.d.ts +2 -0
- package/dist/cli/convoy/partition.test.d.ts.map +1 -0
- package/dist/cli/convoy/partition.test.js +315 -0
- package/dist/cli/convoy/partition.test.js.map +1 -0
- package/dist/cli/convoy/pipeline.test.js +6 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts +99 -7
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +764 -31
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +1810 -18
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/types.d.ts +427 -5
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/convoy/types.js +42 -1
- package/dist/cli/convoy/types.js.map +1 -1
- package/dist/cli/log.d.ts +11 -0
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +114 -2
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/run/adapters/claude.d.ts +2 -0
- package/dist/cli/run/adapters/claude.d.ts.map +1 -1
- package/dist/cli/run/adapters/claude.js +89 -49
- package/dist/cli/run/adapters/claude.js.map +1 -1
- package/dist/cli/run/adapters/claude.test.d.ts +2 -0
- package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude.test.js +205 -0
- package/dist/cli/run/adapters/claude.test.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +1 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
- package/dist/cli/run/adapters/copilot.js +84 -46
- package/dist/cli/run/adapters/copilot.js.map +1 -1
- package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
- package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.test.js +195 -0
- package/dist/cli/run/adapters/copilot.test.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +1 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/run/adapters/cursor.js +83 -47
- package/dist/cli/run/adapters/cursor.js.map +1 -1
- package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
- package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.test.js +129 -0
- package/dist/cli/run/adapters/cursor.test.js.map +1 -0
- package/dist/cli/run/adapters/opencode.d.ts +1 -0
- package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/run/adapters/opencode.js +81 -47
- package/dist/cli/run/adapters/opencode.js.map +1 -1
- package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
- package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/opencode.test.js +119 -0
- package/dist/cli/run/adapters/opencode.test.js.map +1 -0
- package/dist/cli/run/executor.js +1 -1
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +245 -4
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +669 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +362 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +85 -2
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/watch.d.ts +15 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +279 -0
- package/dist/cli/watch.js.map +1 -0
- package/package.json +5 -1
- package/src/cli/agents.ts +177 -0
- package/src/cli/baselines.ts +143 -0
- package/src/cli/convoy/TELEMETRY.md +203 -0
- package/src/cli/convoy/dashboard-types.ts +141 -0
- package/src/cli/convoy/engine.test.ts +1937 -70
- package/src/cli/convoy/engine.ts +2350 -40
- package/src/cli/convoy/event-schemas.ts +195 -0
- package/src/cli/convoy/events.test.ts +384 -39
- package/src/cli/convoy/events.ts +202 -16
- package/src/cli/convoy/expertise.test.ts +128 -0
- package/src/cli/convoy/expertise.ts +163 -0
- package/src/cli/convoy/export.test.ts +1 -0
- package/src/cli/convoy/formula.test.ts +405 -0
- package/src/cli/convoy/formula.ts +174 -0
- package/src/cli/convoy/gates.test.ts +1169 -0
- package/src/cli/convoy/gates.ts +774 -0
- package/src/cli/convoy/health.test.ts +64 -2
- package/src/cli/convoy/health.ts +80 -2
- package/src/cli/convoy/issues.test.ts +143 -0
- package/src/cli/convoy/issues.ts +136 -0
- package/src/cli/convoy/knowledge.test.ts +101 -0
- package/src/cli/convoy/knowledge.ts +132 -0
- package/src/cli/convoy/lessons.test.ts +188 -0
- package/src/cli/convoy/lessons.ts +164 -0
- package/src/cli/convoy/lock.test.ts +181 -0
- package/src/cli/convoy/lock.ts +103 -0
- package/src/cli/convoy/log-merge.test.ts +179 -0
- package/src/cli/convoy/merge.test.ts +6 -7
- package/src/cli/convoy/merge.ts +19 -1
- package/src/cli/convoy/partition.test.ts +423 -0
- package/src/cli/convoy/partition.ts +232 -0
- package/src/cli/convoy/pipeline.test.ts +6 -0
- package/src/cli/convoy/store.test.ts +2041 -20
- package/src/cli/convoy/store.ts +945 -46
- package/src/cli/convoy/types.ts +278 -4
- package/src/cli/log.ts +120 -2
- package/src/cli/run/adapters/claude.test.ts +234 -0
- package/src/cli/run/adapters/claude.ts +45 -5
- package/src/cli/run/adapters/copilot.test.ts +224 -0
- package/src/cli/run/adapters/copilot.ts +34 -4
- package/src/cli/run/adapters/cursor.test.ts +144 -0
- package/src/cli/run/adapters/cursor.ts +33 -2
- package/src/cli/run/adapters/opencode.test.ts +135 -0
- package/src/cli/run/adapters/opencode.ts +30 -2
- package/src/cli/run/executor.ts +1 -1
- package/src/cli/run/schema.test.ts +758 -0
- package/src/cli/run/schema.ts +300 -25
- package/src/cli/run.ts +341 -21
- package/src/cli/types.ts +86 -1
- package/src/cli/watch.ts +298 -0
- package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
- package/src/dashboard/dist/data/.gitkeep +0 -0
- package/src/dashboard/dist/data/convoy-list.json +1 -0
- package/src/dashboard/dist/data/overall-stats.json +24 -0
- package/src/dashboard/dist/index.html +701 -3
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/.gitkeep +0 -0
- package/src/dashboard/public/data/convoy-list.json +1 -0
- package/src/dashboard/public/data/overall-stats.json +24 -0
- package/src/dashboard/scripts/etl.test.ts +210 -0
- package/src/dashboard/scripts/etl.ts +108 -0
- package/src/dashboard/scripts/integration-test.ts +504 -0
- package/src/dashboard/src/pages/index.astro +854 -15
- package/src/dashboard/src/styles/dashboard.css +557 -1
- package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
package/src/cli/run.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
|
+
import { stringify as yamlStringify } from 'yaml'
|
|
4
5
|
import { parseTaskSpecText, isConvoySpec, isPipelineSpec } from './run/schema.js'
|
|
5
6
|
import { createExecutor, buildPhases } from './run/executor.js'
|
|
6
7
|
import { getAdapter, detectAdapter } from './run/adapters/index.js'
|
|
@@ -9,6 +10,7 @@ import { c } from './prompt.js'
|
|
|
9
10
|
import type { CliContext, RunOptions } from './types.js'
|
|
10
11
|
import type { ConvoyResult } from './convoy/engine.js'
|
|
11
12
|
import type { PipelineResult } from './convoy/pipeline.js'
|
|
13
|
+
import { EngineAlreadyRunningError } from './convoy/lock.js'
|
|
12
14
|
|
|
13
15
|
function formatTokens(n: number): string {
|
|
14
16
|
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M'
|
|
@@ -24,13 +26,24 @@ const HELP = `
|
|
|
24
26
|
|
|
25
27
|
Options:
|
|
26
28
|
--file, -f <path> Task spec file
|
|
29
|
+
--formula <path> Use a formula template (alternative to --file)
|
|
30
|
+
--set key=value Set a formula variable (repeatable)
|
|
27
31
|
--dry-run Show execution plan without running
|
|
28
32
|
--concurrency, -c <n> Override max parallel tasks
|
|
29
33
|
--adapter, -a <name> Override agent runtime adapter
|
|
30
34
|
--report-dir <path> Where to write run reports (default: .opencastle/runs)
|
|
31
35
|
--verbose Show full agent output
|
|
32
36
|
--resume Resume the last interrupted convoy from .opencastle/convoy.db
|
|
37
|
+
--retry-failed [task-id] Retry failed/gate-failed/timed-out tasks from the last convoy
|
|
33
38
|
--status Print the current convoy state from .opencastle/convoy.db
|
|
39
|
+
--dlq-list List dead letter queue entries
|
|
40
|
+
--dlq-resolve <id> Resolve a DLQ entry (requires --resolution)
|
|
41
|
+
--dlq-retry <id> Reset a DLQ task to pending for retry
|
|
42
|
+
--convoy <id> Filter by convoy ID (used with --dlq-list)
|
|
43
|
+
--resolution <text> Resolution text (used with --dlq-resolve)
|
|
44
|
+
--watch Keep running, re-triggering on file changes, cron, or git push
|
|
45
|
+
--watch-config <p> Path to watch configuration file (overrides spec watch config)
|
|
46
|
+
--clear-scratchpad Clear scratchpad data at watch start
|
|
34
47
|
--help, -h Show this help
|
|
35
48
|
`
|
|
36
49
|
|
|
@@ -48,6 +61,20 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
48
61
|
help: false,
|
|
49
62
|
resume: false,
|
|
50
63
|
status: false,
|
|
64
|
+
retryFailed: false,
|
|
65
|
+
retryFailedTaskIds: undefined,
|
|
66
|
+
dlqList: false,
|
|
67
|
+
dlqResolve: false,
|
|
68
|
+
dlqResolveId: undefined,
|
|
69
|
+
dlqResolveText: undefined,
|
|
70
|
+
dlqRetry: false,
|
|
71
|
+
dlqRetryId: undefined,
|
|
72
|
+
dlqConvoyFilter: undefined,
|
|
73
|
+
formula: null,
|
|
74
|
+
setVars: {},
|
|
75
|
+
watch: false,
|
|
76
|
+
watchConfig: null,
|
|
77
|
+
clearScratchpad: false,
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -92,9 +119,59 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
92
119
|
case '--resume':
|
|
93
120
|
opts.resume = true
|
|
94
121
|
break
|
|
122
|
+
case '--retry-failed':
|
|
123
|
+
opts.retryFailed = true
|
|
124
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
125
|
+
opts.retryFailedTaskIds = [args[++i]]
|
|
126
|
+
}
|
|
127
|
+
break
|
|
95
128
|
case '--status':
|
|
96
129
|
opts.status = true
|
|
97
130
|
break
|
|
131
|
+
case '--dlq-list':
|
|
132
|
+
opts.dlqList = true
|
|
133
|
+
break
|
|
134
|
+
case '--dlq-resolve':
|
|
135
|
+
opts.dlqResolve = true
|
|
136
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) opts.dlqResolveId = args[++i]
|
|
137
|
+
break
|
|
138
|
+
case '--dlq-retry':
|
|
139
|
+
opts.dlqRetry = true
|
|
140
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) opts.dlqRetryId = args[++i]
|
|
141
|
+
break
|
|
142
|
+
case '--resolution':
|
|
143
|
+
if (i + 1 >= args.length) { console.error(' ✗ --resolution requires text'); process.exit(1) }
|
|
144
|
+
opts.dlqResolveText = args[++i]
|
|
145
|
+
break
|
|
146
|
+
case '--convoy':
|
|
147
|
+
if (i + 1 >= args.length) { console.error(' ✗ --convoy requires an ID'); process.exit(1) }
|
|
148
|
+
opts.dlqConvoyFilter = args[++i]
|
|
149
|
+
break
|
|
150
|
+
case '--formula':
|
|
151
|
+
if (i + 1 >= args.length) { console.error(' ✗ --formula requires a path'); process.exit(1) }
|
|
152
|
+
opts.formula = args[++i]
|
|
153
|
+
break
|
|
154
|
+
case '--set': {
|
|
155
|
+
if (i + 1 >= args.length) { console.error(' ✗ --set requires key=value'); process.exit(1) }
|
|
156
|
+
const pair = args[++i]
|
|
157
|
+
const eqIdx = pair.indexOf('=')
|
|
158
|
+
if (eqIdx < 1) {
|
|
159
|
+
console.error(` ✗ --set value must be in key=value format, got: ${pair}`)
|
|
160
|
+
process.exit(1)
|
|
161
|
+
}
|
|
162
|
+
opts.setVars[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1)
|
|
163
|
+
break
|
|
164
|
+
}
|
|
165
|
+
case '--watch':
|
|
166
|
+
opts.watch = true
|
|
167
|
+
break
|
|
168
|
+
case '--watch-config':
|
|
169
|
+
if (i + 1 >= args.length) { console.error(' ✗ --watch-config requires a path'); process.exit(1) }
|
|
170
|
+
opts.watchConfig = args[++i]
|
|
171
|
+
break
|
|
172
|
+
case '--clear-scratchpad':
|
|
173
|
+
opts.clearScratchpad = true
|
|
174
|
+
break
|
|
98
175
|
default:
|
|
99
176
|
console.error(` ✗ Unknown option: ${arg}`)
|
|
100
177
|
console.log(HELP)
|
|
@@ -205,6 +282,99 @@ export default async function run({ args, pkgRoot }: CliContext): Promise<void>
|
|
|
205
282
|
|
|
206
283
|
const dbPath = resolve(process.cwd(), '.opencastle', 'convoy.db')
|
|
207
284
|
|
|
285
|
+
// ── --dlq-list flag ───────────────────────────────────────────
|
|
286
|
+
if (opts.dlqList) {
|
|
287
|
+
if (!existsSync(dbPath)) {
|
|
288
|
+
console.log(' No convoy database found at .opencastle/convoy.db')
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
292
|
+
const store = createConvoyStore(dbPath)
|
|
293
|
+
try {
|
|
294
|
+
const entries = store.listDlqEntries(opts.dlqConvoyFilter)
|
|
295
|
+
if (entries.length === 0) {
|
|
296
|
+
console.log(' No DLQ entries found.')
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
console.log(`\n Dead Letter Queue (${entries.length} entries):\n`)
|
|
300
|
+
for (const e of entries) {
|
|
301
|
+
const status = e.resolved ? c.green('resolved') : c.red('unresolved')
|
|
302
|
+
console.log(` ${e.id} ${status}`)
|
|
303
|
+
console.log(` Task: ${e.task_id} | Agent: ${e.agent} | Type: ${e.failure_type}`)
|
|
304
|
+
console.log(` Attempts: ${e.attempts} | Created: ${e.created_at}`)
|
|
305
|
+
if (e.resolution) console.log(` Resolution: ${e.resolution}`)
|
|
306
|
+
console.log()
|
|
307
|
+
}
|
|
308
|
+
} finally {
|
|
309
|
+
store.close()
|
|
310
|
+
}
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ── --dlq-resolve flag ────────────────────────────────────────
|
|
315
|
+
if (opts.dlqResolve) {
|
|
316
|
+
if (!opts.dlqResolveId) {
|
|
317
|
+
console.error(' \u2717 --dlq-resolve requires a DLQ entry ID')
|
|
318
|
+
process.exit(1)
|
|
319
|
+
}
|
|
320
|
+
if (!opts.dlqResolveText) {
|
|
321
|
+
console.error(' \u2717 --dlq-resolve requires --resolution "text"')
|
|
322
|
+
process.exit(1)
|
|
323
|
+
}
|
|
324
|
+
if (!existsSync(dbPath)) {
|
|
325
|
+
console.error(' \u2717 No convoy database found at .opencastle/convoy.db')
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
328
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
329
|
+
const store = createConvoyStore(dbPath)
|
|
330
|
+
try {
|
|
331
|
+
store.resolveDlqEntry(opts.dlqResolveId, opts.dlqResolveText)
|
|
332
|
+
console.log(` \u2713 DLQ entry ${opts.dlqResolveId} resolved.`)
|
|
333
|
+
} finally {
|
|
334
|
+
store.close()
|
|
335
|
+
}
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── --dlq-retry flag ──────────────────────────────────────────
|
|
340
|
+
if (opts.dlqRetry) {
|
|
341
|
+
if (!opts.dlqRetryId) {
|
|
342
|
+
console.error(' \u2717 --dlq-retry requires a DLQ entry ID')
|
|
343
|
+
process.exit(1)
|
|
344
|
+
}
|
|
345
|
+
if (!existsSync(dbPath)) {
|
|
346
|
+
console.error(' \u2717 No convoy database found at .opencastle/convoy.db')
|
|
347
|
+
process.exit(1)
|
|
348
|
+
}
|
|
349
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
350
|
+
const store = createConvoyStore(dbPath)
|
|
351
|
+
try {
|
|
352
|
+
const entries = store.listDlqEntries()
|
|
353
|
+
const entry = entries.find(e => e.id === opts.dlqRetryId)
|
|
354
|
+
if (!entry) {
|
|
355
|
+
console.error(` \u2717 DLQ entry "${opts.dlqRetryId}" not found`)
|
|
356
|
+
process.exit(1)
|
|
357
|
+
}
|
|
358
|
+
// Reset the task to pending
|
|
359
|
+
store.updateTaskStatus(entry.task_id, entry.convoy_id, 'pending', {
|
|
360
|
+
worker_id: null,
|
|
361
|
+
worktree: null,
|
|
362
|
+
started_at: null,
|
|
363
|
+
finished_at: null,
|
|
364
|
+
})
|
|
365
|
+
store.resolveDlqEntry(entry.id, 'Retried via CLI')
|
|
366
|
+
// Reset convoy status to running if needed
|
|
367
|
+
const convoy = store.getConvoy(entry.convoy_id)
|
|
368
|
+
if (convoy && (convoy.status === 'failed' || convoy.status === 'done')) {
|
|
369
|
+
store.updateConvoyStatus(entry.convoy_id, 'running', {})
|
|
370
|
+
}
|
|
371
|
+
console.log(` \u2713 Task ${entry.task_id} reset to pending. Run 'opencastle run --resume' to execute.`)
|
|
372
|
+
} finally {
|
|
373
|
+
store.close()
|
|
374
|
+
}
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
208
378
|
// ── --status flag ─────────────────────────────────────────────
|
|
209
379
|
if (opts.status) {
|
|
210
380
|
if (!existsSync(dbPath)) {
|
|
@@ -288,6 +458,70 @@ export default async function run({ args, pkgRoot }: CliContext): Promise<void>
|
|
|
288
458
|
return
|
|
289
459
|
}
|
|
290
460
|
|
|
461
|
+
// ── --retry-failed flag ───────────────────────────────────────
|
|
462
|
+
if (opts.retryFailed) {
|
|
463
|
+
if (!existsSync(dbPath)) {
|
|
464
|
+
console.error(' ✗ No convoy database found at .opencastle/convoy.db')
|
|
465
|
+
console.error(' Run a convoy spec first: opencastle run convoy.yml')
|
|
466
|
+
process.exit(1)
|
|
467
|
+
}
|
|
468
|
+
const { createConvoyStore } = await import('./convoy/store.js')
|
|
469
|
+
const store = createConvoyStore(dbPath)
|
|
470
|
+
const convoy = store.getLatestConvoy()
|
|
471
|
+
store.close()
|
|
472
|
+
if (!convoy) {
|
|
473
|
+
console.error(' ✗ No convoy records found in .opencastle/convoy.db')
|
|
474
|
+
process.exit(1)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const retrySpec = parseTaskSpecText(convoy.spec_yaml)
|
|
478
|
+
if (opts.concurrency !== null) retrySpec.concurrency = opts.concurrency
|
|
479
|
+
if (opts.adapter !== null) retrySpec.adapter = opts.adapter
|
|
480
|
+
if (opts.verbose) retrySpec._verbose = true
|
|
481
|
+
|
|
482
|
+
let retryDetectionFailed = false
|
|
483
|
+
if (!retrySpec.adapter) {
|
|
484
|
+
const detected = await detectAdapter()
|
|
485
|
+
if (detected) {
|
|
486
|
+
retrySpec.adapter = detected
|
|
487
|
+
console.log(` ℹ Auto-detected adapter: ${detected}`)
|
|
488
|
+
} else {
|
|
489
|
+
retryDetectionFailed = true
|
|
490
|
+
retrySpec.adapter = 'claude'
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const retryAdapter = await getAdapter(retrySpec.adapter)
|
|
495
|
+
const retryAvailable = await retryAdapter.isAvailable()
|
|
496
|
+
if (!retryAvailable) {
|
|
497
|
+
printAdapterError(retryDetectionFailed, retrySpec.adapter)
|
|
498
|
+
process.exit(1)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
console.log(`\n 🏰 OpenCastle Convoy (Retry Failed): ${convoy.name}`)
|
|
502
|
+
console.log(` Convoy ID: ${convoy.id}`)
|
|
503
|
+
const { createConvoyEngine } = await import('./convoy/engine.js')
|
|
504
|
+
const retryEngine = createConvoyEngine({
|
|
505
|
+
spec: retrySpec,
|
|
506
|
+
specYaml: convoy.spec_yaml,
|
|
507
|
+
adapter: retryAdapter,
|
|
508
|
+
verbose: opts.verbose,
|
|
509
|
+
})
|
|
510
|
+
await retryEngine.retryFailed(convoy.id, opts.retryFailedTaskIds)
|
|
511
|
+
let retryResult: ConvoyResult
|
|
512
|
+
try {
|
|
513
|
+
retryResult = await retryEngine.resume(convoy.id)
|
|
514
|
+
} catch (err) {
|
|
515
|
+
if (err instanceof EngineAlreadyRunningError) {
|
|
516
|
+
console.error(` ✗ ${err.message}`)
|
|
517
|
+
process.exit(1)
|
|
518
|
+
}
|
|
519
|
+
throw err
|
|
520
|
+
}
|
|
521
|
+
printConvoyResult(retryResult)
|
|
522
|
+
process.exit(retryResult.status !== 'done' ? 1 : 0)
|
|
523
|
+
}
|
|
524
|
+
|
|
291
525
|
// ── --resume flag ─────────────────────────────────────────────
|
|
292
526
|
if (opts.resume) {
|
|
293
527
|
if (!existsSync(dbPath)) {
|
|
@@ -385,32 +619,83 @@ export default async function run({ args, pkgRoot }: CliContext): Promise<void>
|
|
|
385
619
|
adapter: resumeAdapter,
|
|
386
620
|
verbose: opts.verbose,
|
|
387
621
|
})
|
|
388
|
-
|
|
622
|
+
let resumeResult: ConvoyResult
|
|
623
|
+
try {
|
|
624
|
+
resumeResult = await resumeEngine.resume(convoy.id)
|
|
625
|
+
} catch (err) {
|
|
626
|
+
if (err instanceof EngineAlreadyRunningError) {
|
|
627
|
+
console.error(` ✗ ${err.message}`)
|
|
628
|
+
process.exit(1)
|
|
629
|
+
}
|
|
630
|
+
throw err
|
|
631
|
+
}
|
|
389
632
|
printConvoyResult(resumeResult)
|
|
390
633
|
process.exit(resumeResult.status !== 'done' ? 1 : 0)
|
|
391
634
|
}
|
|
392
635
|
|
|
393
|
-
// ── Read and validate spec
|
|
394
|
-
const specPath = resolve(process.cwd(), opts.file)
|
|
636
|
+
// ── Formula template resolution / Read and validate spec ─────
|
|
395
637
|
let specText = ''
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
638
|
+
let spec: ReturnType<typeof parseTaskSpecText>
|
|
639
|
+
|
|
640
|
+
if (opts.formula) {
|
|
641
|
+
const { parseFormula, substituteVariables, validateTemplate } = await import('./convoy/formula.js')
|
|
642
|
+
const formulaPath = resolve(process.cwd(), opts.formula)
|
|
643
|
+
let template
|
|
644
|
+
try {
|
|
645
|
+
template = parseFormula(formulaPath)
|
|
646
|
+
} catch (err: unknown) {
|
|
647
|
+
console.error(` ✗ ${(err as Error).message}`)
|
|
648
|
+
process.exit(1)
|
|
404
649
|
}
|
|
405
|
-
process.exit(1)
|
|
406
|
-
}
|
|
407
650
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
651
|
+
const validation = validateTemplate(template)
|
|
652
|
+
if (!validation.valid) {
|
|
653
|
+
console.error(` ✗ Invalid formula template:\n • ${validation.errors.join('\n • ')}`)
|
|
654
|
+
process.exit(1)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (opts.dryRun) {
|
|
658
|
+
console.log(`\n 📋 Formula: ${template.name}`)
|
|
659
|
+
if (template.description) console.log(` ${template.description}`)
|
|
660
|
+
console.log(` Variables:`)
|
|
661
|
+
for (const [key, val] of Object.entries(opts.setVars)) {
|
|
662
|
+
console.log(` ${key} = ${val}`)
|
|
663
|
+
}
|
|
664
|
+
for (const [key, def] of Object.entries(template.variables)) {
|
|
665
|
+
if (!(key in opts.setVars) && !def.required && def.default) {
|
|
666
|
+
console.log(` ${key} = ${def.default} (default)`)
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
spec = substituteVariables(template, opts.setVars)
|
|
673
|
+
} catch (err: unknown) {
|
|
674
|
+
console.error(` ✗ ${(err as Error).message}`)
|
|
675
|
+
process.exit(1)
|
|
676
|
+
}
|
|
677
|
+
specText = yamlStringify(spec)
|
|
678
|
+
} else {
|
|
679
|
+
// ── Read and validate spec ──────────────────────────────────
|
|
680
|
+
const specPath = resolve(process.cwd(), opts.file)
|
|
681
|
+
try {
|
|
682
|
+
specText = await readFile(specPath, 'utf8')
|
|
683
|
+
} catch (err: unknown) {
|
|
684
|
+
const e = err as Error & { code?: string }
|
|
685
|
+
if (e.code === 'ENOENT') {
|
|
686
|
+
console.error(` ✗ Task spec file not found: ${opts.file}`)
|
|
687
|
+
} else {
|
|
688
|
+
console.error(` ✗ Cannot read task spec file: ${e.message}`)
|
|
689
|
+
}
|
|
690
|
+
process.exit(1)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
spec = parseTaskSpecText(specText)
|
|
695
|
+
} catch (err: unknown) {
|
|
696
|
+
console.error(` ✗ ${(err as Error).message}`)
|
|
697
|
+
process.exit(1)
|
|
698
|
+
}
|
|
414
699
|
}
|
|
415
700
|
|
|
416
701
|
// Apply CLI overrides
|
|
@@ -493,7 +778,16 @@ export default async function run({ args, pkgRoot }: CliContext): Promise<void>
|
|
|
493
778
|
verbose: opts.verbose,
|
|
494
779
|
})
|
|
495
780
|
|
|
496
|
-
|
|
781
|
+
let pipelineResult: PipelineResult
|
|
782
|
+
try {
|
|
783
|
+
pipelineResult = await pipelineOrchestrator.run()
|
|
784
|
+
} catch (err) {
|
|
785
|
+
if (err instanceof EngineAlreadyRunningError) {
|
|
786
|
+
console.error(` ✗ ${err.message}`)
|
|
787
|
+
process.exit(1)
|
|
788
|
+
}
|
|
789
|
+
throw err
|
|
790
|
+
}
|
|
497
791
|
printPipelineResult(pipelineResult)
|
|
498
792
|
if (pipelineDashboardResult) {
|
|
499
793
|
console.log(`\n ${c.dim('Results saved to .opencastle/logs/convoys.ndjson')}`)
|
|
@@ -535,7 +829,33 @@ export default async function run({ args, pkgRoot }: CliContext): Promise<void>
|
|
|
535
829
|
verbose: opts.verbose,
|
|
536
830
|
})
|
|
537
831
|
|
|
538
|
-
|
|
832
|
+
if (opts.watch) {
|
|
833
|
+
const pidPath = resolve(process.cwd(), '.opencastle', 'watch.pid')
|
|
834
|
+
const { watchLoop } = await import('./watch.js')
|
|
835
|
+
await watchLoop({
|
|
836
|
+
spec,
|
|
837
|
+
specText,
|
|
838
|
+
specPath: resolve(process.cwd(), opts.file),
|
|
839
|
+
adapter,
|
|
840
|
+
verbose: opts.verbose,
|
|
841
|
+
pidPath,
|
|
842
|
+
clearScratchpad: opts.clearScratchpad,
|
|
843
|
+
watchConfigPath: opts.watchConfig ? resolve(process.cwd(), opts.watchConfig) : null,
|
|
844
|
+
printResult: printConvoyResult,
|
|
845
|
+
})
|
|
846
|
+
return
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
let result: ConvoyResult
|
|
850
|
+
try {
|
|
851
|
+
result = await engine.run()
|
|
852
|
+
} catch (err) {
|
|
853
|
+
if (err instanceof EngineAlreadyRunningError) {
|
|
854
|
+
console.error(` ✗ ${err.message}`)
|
|
855
|
+
process.exit(1)
|
|
856
|
+
}
|
|
857
|
+
throw err
|
|
858
|
+
}
|
|
539
859
|
printConvoyResult(result)
|
|
540
860
|
if (dashboardResult) {
|
|
541
861
|
console.log(`\n ${c.dim('Results saved to .opencastle/logs/convoys.ndjson')}`)
|
package/src/cli/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { BuiltInGatesConfig, BrowserTestConfig, GuardConfig, CircuitBreakerConfig, TaskStep, Hook, TaskOutput, TaskInput, WatchConfig, MCPServerConfig } from './convoy/types.js';
|
|
2
3
|
|
|
3
4
|
// ── Stack selection types ──────────────────────────────────────
|
|
4
5
|
|
|
@@ -141,6 +142,15 @@ export const IDE_LABELS: Record<IdeChoice, string> = {
|
|
|
141
142
|
|
|
142
143
|
// ── Run command types ──────────────────────────────────────────
|
|
143
144
|
|
|
145
|
+
/** Heuristics for routing tasks to review levels. */
|
|
146
|
+
export interface ReviewHeuristics {
|
|
147
|
+
panel_paths?: string[];
|
|
148
|
+
panel_agents?: string[];
|
|
149
|
+
auto_pass_agents?: string[];
|
|
150
|
+
auto_pass_max_lines?: number;
|
|
151
|
+
auto_pass_max_files?: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
/** Default values merged into each task for Convoy Engine (version: 1) specs. */
|
|
145
155
|
export interface TaskDefaults {
|
|
146
156
|
timeout?: string;
|
|
@@ -148,12 +158,42 @@ export interface TaskDefaults {
|
|
|
148
158
|
max_retries?: number;
|
|
149
159
|
agent?: string;
|
|
150
160
|
adapter?: string;
|
|
161
|
+
gates?: string[];
|
|
162
|
+
built_in_gates?: BuiltInGatesConfig;
|
|
163
|
+
gate_timeout?: number;
|
|
164
|
+
on_exhausted?: 'dlq' | 'skip' | 'stop';
|
|
165
|
+
escalate_to?: string;
|
|
166
|
+
circuit_breaker?: CircuitBreakerConfig;
|
|
167
|
+
review?: 'auto' | 'fast' | 'panel' | 'none';
|
|
168
|
+
reviewer_model?: string;
|
|
169
|
+
review_budget?: number;
|
|
170
|
+
on_review_budget_exceeded?: 'skip' | 'downgrade' | 'stop';
|
|
171
|
+
max_concurrent_reviews?: number;
|
|
172
|
+
review_heuristics?: ReviewHeuristics;
|
|
173
|
+
detect_drift?: boolean;
|
|
174
|
+
on_dispute?: 'continue' | 'stop';
|
|
175
|
+
/** Enable automated lesson injection into task prompts (Phase 18.1). */
|
|
176
|
+
inject_lessons?: boolean;
|
|
177
|
+
/** Enable discovered issues tracking in task prompts (Phase 18.4). */
|
|
178
|
+
track_discovered_issues?: boolean;
|
|
179
|
+
/** Skip assigning agent to tasks matching their weak areas (Phase 18.2). */
|
|
180
|
+
avoid_weak_agents?: boolean;
|
|
181
|
+
/** Maximum concurrent tasks in swarm mode (default: 8). */
|
|
182
|
+
max_swarm_concurrency?: number;
|
|
183
|
+
/** MCP servers available to tasks (Phase 19.7). */
|
|
184
|
+
mcp_servers?: MCPServerConfig[];
|
|
185
|
+
/** Auto-approve all MCP tool calls without prompting (Phase 19.7). */
|
|
186
|
+
mcp_approve_all?: boolean;
|
|
187
|
+
/** Timeout in seconds for MCP server approval prompts (Phase 19.7). */
|
|
188
|
+
mcp_server_approval_timeout?: number;
|
|
189
|
+
/** Browser test gate configuration for default built-in gates. */
|
|
190
|
+
browser_test?: BrowserTestConfig;
|
|
151
191
|
}
|
|
152
192
|
|
|
153
193
|
/** Validated task spec from YAML. */
|
|
154
194
|
export interface TaskSpec {
|
|
155
195
|
name: string;
|
|
156
|
-
concurrency: number;
|
|
196
|
+
concurrency: number | 'auto';
|
|
157
197
|
on_failure: 'continue' | 'stop';
|
|
158
198
|
adapter: string;
|
|
159
199
|
tasks?: Task[];
|
|
@@ -170,6 +210,12 @@ export interface TaskSpec {
|
|
|
170
210
|
branch?: string;
|
|
171
211
|
/** Other convoy spec names to run before this one (version: 2 pipeline specs). */
|
|
172
212
|
depends_on_convoy?: string[];
|
|
213
|
+
/** Optional post-convoy guard configuration. */
|
|
214
|
+
guard?: GuardConfig;
|
|
215
|
+
/** Post-convoy lifecycle hooks. */
|
|
216
|
+
hooks?: Hook[];
|
|
217
|
+
/** Watch mode configuration (Phase 17.1). */
|
|
218
|
+
watch?: WatchConfig;
|
|
173
219
|
}
|
|
174
220
|
|
|
175
221
|
/** A single task in the spec. */
|
|
@@ -188,6 +234,24 @@ export interface Task {
|
|
|
188
234
|
max_retries: number;
|
|
189
235
|
/** Per-task adapter override. */
|
|
190
236
|
adapter?: string;
|
|
237
|
+
/** Per-task gate shell commands run after adapter success. */
|
|
238
|
+
gates?: string[];
|
|
239
|
+
/** Multi-step task sub-prompts. */
|
|
240
|
+
steps?: TaskStep[];
|
|
241
|
+
/** Review level override for this task. */
|
|
242
|
+
review?: 'auto' | 'fast' | 'panel' | 'none';
|
|
243
|
+
/** Lifecycle hooks for this task. */
|
|
244
|
+
hooks?: Hook[];
|
|
245
|
+
/** Opt-in drift detection (streaming adapters only). */
|
|
246
|
+
detect_drift?: boolean;
|
|
247
|
+
/** Outputs this task produces as named artifacts. */
|
|
248
|
+
outputs?: TaskOutput[];
|
|
249
|
+
/** Inputs this task consumes from upstream task artifacts. */
|
|
250
|
+
inputs?: TaskInput[];
|
|
251
|
+
/** Whether this task has persistent agent identity (Phase 17.2). */
|
|
252
|
+
persistent?: boolean;
|
|
253
|
+
/** Browser test gate configuration for this task. */
|
|
254
|
+
browser_test?: BrowserTestConfig;
|
|
191
255
|
}
|
|
192
256
|
|
|
193
257
|
/** Task execution status. */
|
|
@@ -196,6 +260,7 @@ export type TaskStatus =
|
|
|
196
260
|
| 'running'
|
|
197
261
|
| 'done'
|
|
198
262
|
| 'failed'
|
|
263
|
+
| 'gate-failed'
|
|
199
264
|
| 'skipped'
|
|
200
265
|
| 'timed-out';
|
|
201
266
|
|
|
@@ -233,6 +298,8 @@ export interface AgentAdapter {
|
|
|
233
298
|
isAvailable(): Promise<boolean>;
|
|
234
299
|
execute(_task: Task, _options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
235
300
|
kill?(_task: Task): void;
|
|
301
|
+
/** Whether the adapter supports reusing sessions across multi-step task steps. Defaults to false. */
|
|
302
|
+
supportsSessionContinuity?(): boolean;
|
|
236
303
|
}
|
|
237
304
|
|
|
238
305
|
/** Options for agent execution. */
|
|
@@ -240,6 +307,10 @@ export interface ExecuteOptions {
|
|
|
240
307
|
verbose?: boolean;
|
|
241
308
|
/** Working directory for the agent process (defaults to process.cwd()). */
|
|
242
309
|
cwd?: string;
|
|
310
|
+
/** MCP servers to make available during execution (Phase 19.7). */
|
|
311
|
+
mcpServers?: MCPServerConfig[];
|
|
312
|
+
/** Automatically approve all MCP permission requests. */
|
|
313
|
+
mcp_approve_all?: boolean;
|
|
243
314
|
}
|
|
244
315
|
|
|
245
316
|
/** Token usage data from adapter execution. */
|
|
@@ -286,6 +357,20 @@ export interface RunOptions {
|
|
|
286
357
|
help: boolean;
|
|
287
358
|
resume: boolean;
|
|
288
359
|
status: boolean;
|
|
360
|
+
retryFailed: boolean;
|
|
361
|
+
retryFailedTaskIds?: string[];
|
|
362
|
+
dlqList: boolean;
|
|
363
|
+
dlqResolve: boolean;
|
|
364
|
+
dlqResolveId?: string;
|
|
365
|
+
dlqResolveText?: string;
|
|
366
|
+
dlqRetry: boolean;
|
|
367
|
+
dlqRetryId?: string;
|
|
368
|
+
dlqConvoyFilter?: string;
|
|
369
|
+
formula: string | null;
|
|
370
|
+
setVars: Record<string, string>;
|
|
371
|
+
watch: boolean;
|
|
372
|
+
watchConfig: string | null;
|
|
373
|
+
clearScratchpad: boolean;
|
|
289
374
|
}
|
|
290
375
|
|
|
291
376
|
/** Validation result. */
|