opencastle 0.22.0 → 0.23.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 +1 -0
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +1 -0
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/export.d.ts +1 -0
- package/dist/cli/convoy/export.d.ts.map +1 -1
- package/dist/cli/convoy/export.js +34 -0
- package/dist/cli/convoy/export.js.map +1 -1
- package/dist/cli/convoy/pipeline.d.ts +35 -0
- package/dist/cli/convoy/pipeline.d.ts.map +1 -0
- package/dist/cli/convoy/pipeline.js +353 -0
- package/dist/cli/convoy/pipeline.js.map +1 -0
- package/dist/cli/convoy/pipeline.test.d.ts +2 -0
- package/dist/cli/convoy/pipeline.test.d.ts.map +1 -0
- package/dist/cli/convoy/pipeline.test.js +778 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -0
- package/dist/cli/convoy/store.d.ts +14 -2
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +84 -5
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +216 -7
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/types.d.ts +15 -0
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +1 -0
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/run/schema.d.ts +5 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +41 -8
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +194 -5
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +141 -2
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +3 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/convoy/engine.ts +2 -0
- package/src/cli/convoy/export.ts +41 -0
- package/src/cli/convoy/pipeline.test.ts +939 -0
- package/src/cli/convoy/pipeline.ts +430 -0
- package/src/cli/convoy/store.test.ts +239 -7
- package/src/cli/convoy/store.ts +110 -7
- package/src/cli/convoy/types.ts +17 -0
- package/src/cli/dashboard.ts +1 -0
- package/src/cli/run/schema.test.ts +244 -5
- package/src/cli/run/schema.ts +49 -8
- package/src/cli/run.ts +140 -2
- package/src/cli/types.ts +3 -1
- package/src/dashboard/dist/_astro/{index.DyyaCW8L.css → index.Cq68OHaZ.css} +1 -1
- package/src/dashboard/dist/index.html +214 -2
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/src/pages/index.astro +230 -1
- package/src/dashboard/src/styles/dashboard.css +116 -0
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +1 -1
|
@@ -32,6 +32,7 @@ function makeConvoy(overrides: Partial<Parameters<ConvoyStore['insertConvoy']>[0
|
|
|
32
32
|
branch: null,
|
|
33
33
|
created_at: new Date().toISOString(),
|
|
34
34
|
spec_yaml: 'name: test',
|
|
35
|
+
pipeline_id: null,
|
|
35
36
|
...overrides,
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -69,6 +70,19 @@ function makeWorker(overrides: Partial<Parameters<ConvoyStore['insertWorker']>[0
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function makePipeline(overrides: Partial<Parameters<ConvoyStore['insertPipeline']>[0]> = {}) {
|
|
74
|
+
return {
|
|
75
|
+
id: 'pipeline-1',
|
|
76
|
+
name: 'Test Pipeline',
|
|
77
|
+
status: 'pending' as const,
|
|
78
|
+
branch: null,
|
|
79
|
+
spec_yaml: 'name: test-pipeline\nversion: 2',
|
|
80
|
+
convoy_specs: JSON.stringify(['convoys/step1.yml', 'convoys/step2.yml']),
|
|
81
|
+
created_at: new Date().toISOString(),
|
|
82
|
+
...overrides,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
// ── DB creation and WAL mode ──────────────────────────────────────────────────
|
|
73
87
|
|
|
74
88
|
describe('DB creation', () => {
|
|
@@ -84,11 +98,11 @@ describe('DB creation', () => {
|
|
|
84
98
|
expect(row.journal_mode).toBe('wal')
|
|
85
99
|
})
|
|
86
100
|
|
|
87
|
-
it('sets schema version to
|
|
101
|
+
it('sets schema version to 4', () => {
|
|
88
102
|
const db = new DatabaseSync(dbPath)
|
|
89
103
|
const row = db.prepare('PRAGMA user_version').get() as { user_version: number }
|
|
90
104
|
db.close()
|
|
91
|
-
expect(row.user_version).toBe(
|
|
105
|
+
expect(row.user_version).toBe(4)
|
|
92
106
|
})
|
|
93
107
|
|
|
94
108
|
it('creates all required tables', () => {
|
|
@@ -102,6 +116,7 @@ describe('DB creation', () => {
|
|
|
102
116
|
expect(names).toContain('task')
|
|
103
117
|
expect(names).toContain('worker')
|
|
104
118
|
expect(names).toContain('event')
|
|
119
|
+
expect(names).toContain('pipeline')
|
|
105
120
|
})
|
|
106
121
|
|
|
107
122
|
it('reopening an existing DB does not reset schema version', () => {
|
|
@@ -113,7 +128,7 @@ describe('DB creation', () => {
|
|
|
113
128
|
store2.close()
|
|
114
129
|
// Reassign so afterEach does not double-close
|
|
115
130
|
store = createConvoyStore(dbPath)
|
|
116
|
-
expect(row.user_version).toBe(
|
|
131
|
+
expect(row.user_version).toBe(4)
|
|
117
132
|
})
|
|
118
133
|
})
|
|
119
134
|
|
|
@@ -192,8 +207,8 @@ describe('schema migration', () => {
|
|
|
192
207
|
verifyDb.close()
|
|
193
208
|
|
|
194
209
|
expect(cols.map(c => c.name)).toContain('adapter')
|
|
195
|
-
// v1 chains through v2→v3 in one init, so final version is
|
|
196
|
-
expect(version.user_version).toBe(
|
|
210
|
+
// v1 chains through v2→v3→v4 in one init, so final version is 4
|
|
211
|
+
expect(version.user_version).toBe(4)
|
|
197
212
|
})
|
|
198
213
|
|
|
199
214
|
it('schema migration v2 to v3 adds cost columns', () => {
|
|
@@ -279,7 +294,7 @@ describe('schema migration', () => {
|
|
|
279
294
|
expect(convoyColNames).toContain('total_tokens')
|
|
280
295
|
expect(convoyColNames).toContain('total_cost_usd')
|
|
281
296
|
|
|
282
|
-
expect(version.user_version).toBe(
|
|
297
|
+
expect(version.user_version).toBe(4)
|
|
283
298
|
})
|
|
284
299
|
|
|
285
300
|
it('schema migration v1 to v3 chains correctly in a single init', () => {
|
|
@@ -365,7 +380,90 @@ describe('schema migration', () => {
|
|
|
365
380
|
expect(convoyColNames).toContain('total_tokens')
|
|
366
381
|
expect(convoyColNames).toContain('total_cost_usd')
|
|
367
382
|
|
|
368
|
-
expect(version.user_version).toBe(
|
|
383
|
+
expect(version.user_version).toBe(4)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('schema migration v3 to v4 creates pipeline table and adds pipeline_id to convoy', () => {
|
|
387
|
+
const v3DbPath = join(tmpDir, 'v3.db')
|
|
388
|
+
const rawDb = new DatabaseSync(v3DbPath)
|
|
389
|
+
rawDb.exec(`
|
|
390
|
+
CREATE TABLE convoy (
|
|
391
|
+
id TEXT PRIMARY KEY,
|
|
392
|
+
name TEXT NOT NULL,
|
|
393
|
+
spec_hash TEXT NOT NULL,
|
|
394
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
395
|
+
branch TEXT,
|
|
396
|
+
created_at TEXT NOT NULL,
|
|
397
|
+
started_at TEXT,
|
|
398
|
+
finished_at TEXT,
|
|
399
|
+
spec_yaml TEXT NOT NULL,
|
|
400
|
+
total_tokens INTEGER,
|
|
401
|
+
total_cost_usd TEXT
|
|
402
|
+
);
|
|
403
|
+
CREATE TABLE task (
|
|
404
|
+
id TEXT PRIMARY KEY,
|
|
405
|
+
convoy_id TEXT NOT NULL REFERENCES convoy(id),
|
|
406
|
+
phase INTEGER NOT NULL,
|
|
407
|
+
prompt TEXT NOT NULL,
|
|
408
|
+
agent TEXT NOT NULL DEFAULT 'developer',
|
|
409
|
+
adapter TEXT,
|
|
410
|
+
model TEXT,
|
|
411
|
+
timeout_ms INTEGER NOT NULL DEFAULT 1800000,
|
|
412
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
413
|
+
worker_id TEXT,
|
|
414
|
+
worktree TEXT,
|
|
415
|
+
output TEXT,
|
|
416
|
+
exit_code INTEGER,
|
|
417
|
+
started_at TEXT,
|
|
418
|
+
finished_at TEXT,
|
|
419
|
+
retries INTEGER NOT NULL DEFAULT 0,
|
|
420
|
+
max_retries INTEGER NOT NULL DEFAULT 1,
|
|
421
|
+
files TEXT,
|
|
422
|
+
depends_on TEXT,
|
|
423
|
+
prompt_tokens INTEGER,
|
|
424
|
+
completion_tokens INTEGER,
|
|
425
|
+
total_tokens INTEGER,
|
|
426
|
+
cost_usd TEXT
|
|
427
|
+
);
|
|
428
|
+
CREATE TABLE worker (
|
|
429
|
+
id TEXT PRIMARY KEY,
|
|
430
|
+
task_id TEXT REFERENCES task(id),
|
|
431
|
+
adapter TEXT NOT NULL,
|
|
432
|
+
pid INTEGER,
|
|
433
|
+
session_id TEXT,
|
|
434
|
+
status TEXT NOT NULL DEFAULT 'spawned',
|
|
435
|
+
worktree TEXT,
|
|
436
|
+
created_at TEXT NOT NULL,
|
|
437
|
+
finished_at TEXT,
|
|
438
|
+
last_heartbeat TEXT
|
|
439
|
+
);
|
|
440
|
+
CREATE TABLE event (
|
|
441
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
442
|
+
convoy_id TEXT REFERENCES convoy(id),
|
|
443
|
+
task_id TEXT,
|
|
444
|
+
worker_id TEXT,
|
|
445
|
+
type TEXT NOT NULL,
|
|
446
|
+
data TEXT,
|
|
447
|
+
created_at TEXT NOT NULL
|
|
448
|
+
);
|
|
449
|
+
`)
|
|
450
|
+
rawDb.exec('PRAGMA user_version = 3')
|
|
451
|
+
rawDb.close()
|
|
452
|
+
|
|
453
|
+
const v3Store = createConvoyStore(v3DbPath)
|
|
454
|
+
v3Store.close()
|
|
455
|
+
|
|
456
|
+
const verifyDb = new DatabaseSync(v3DbPath)
|
|
457
|
+
const convoyCols = verifyDb.prepare('PRAGMA table_info(convoy)').all() as Array<{ name: string }>
|
|
458
|
+
const tables = verifyDb
|
|
459
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
460
|
+
.all() as Array<{ name: string }>
|
|
461
|
+
const version = verifyDb.prepare('PRAGMA user_version').get() as { user_version: number }
|
|
462
|
+
verifyDb.close()
|
|
463
|
+
|
|
464
|
+
expect(convoyCols.map(c => c.name)).toContain('pipeline_id')
|
|
465
|
+
expect(tables.map(t => t.name)).toContain('pipeline')
|
|
466
|
+
expect(version.user_version).toBe(4)
|
|
369
467
|
})
|
|
370
468
|
})
|
|
371
469
|
|
|
@@ -738,6 +836,140 @@ describe('withTransaction', () => {
|
|
|
738
836
|
})
|
|
739
837
|
})
|
|
740
838
|
|
|
839
|
+
// ── pipeline CRUD ─────────────────────────────────────────────────────────────
|
|
840
|
+
|
|
841
|
+
describe('pipeline CRUD', () => {
|
|
842
|
+
it('inserts and retrieves a pipeline record', () => {
|
|
843
|
+
store.insertPipeline(makePipeline())
|
|
844
|
+
const retrieved = store.getPipeline('pipeline-1')
|
|
845
|
+
expect(retrieved).toBeDefined()
|
|
846
|
+
expect(retrieved!.id).toBe('pipeline-1')
|
|
847
|
+
expect(retrieved!.name).toBe('Test Pipeline')
|
|
848
|
+
expect(retrieved!.status).toBe('pending')
|
|
849
|
+
expect(retrieved!.started_at).toBeNull()
|
|
850
|
+
expect(retrieved!.finished_at).toBeNull()
|
|
851
|
+
expect(retrieved!.total_tokens).toBeNull()
|
|
852
|
+
expect(retrieved!.total_cost_usd).toBeNull()
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
it('returns undefined for missing pipeline', () => {
|
|
856
|
+
expect(store.getPipeline('does-not-exist')).toBeUndefined()
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
it('getLatestPipeline returns most recent pipeline', () => {
|
|
860
|
+
store.insertPipeline(makePipeline({ id: 'pipeline-old', created_at: '2026-01-01T00:00:00.000Z' }))
|
|
861
|
+
store.insertPipeline(makePipeline({ id: 'pipeline-new', created_at: '2026-01-02T00:00:00.000Z' }))
|
|
862
|
+
const latest = store.getLatestPipeline()
|
|
863
|
+
expect(latest?.id).toBe('pipeline-new')
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
it('getLatestPipeline returns undefined when no pipelines exist', () => {
|
|
867
|
+
expect(store.getLatestPipeline()).toBeUndefined()
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
it('updatePipelineStatus updates status', () => {
|
|
871
|
+
store.insertPipeline(makePipeline())
|
|
872
|
+
store.updatePipelineStatus('pipeline-1', 'running')
|
|
873
|
+
expect(store.getPipeline('pipeline-1')!.status).toBe('running')
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
it('updatePipelineStatus sets started_at', () => {
|
|
877
|
+
const ts = '2026-01-01T00:00:00.000Z'
|
|
878
|
+
store.insertPipeline(makePipeline())
|
|
879
|
+
store.updatePipelineStatus('pipeline-1', 'running', { started_at: ts })
|
|
880
|
+
const p = store.getPipeline('pipeline-1')!
|
|
881
|
+
expect(p.status).toBe('running')
|
|
882
|
+
expect(p.started_at).toBe(ts)
|
|
883
|
+
})
|
|
884
|
+
|
|
885
|
+
it('updatePipelineStatus sets finished_at', () => {
|
|
886
|
+
const ts = '2026-01-01T01:00:00.000Z'
|
|
887
|
+
store.insertPipeline(makePipeline())
|
|
888
|
+
store.updatePipelineStatus('pipeline-1', 'done', { finished_at: ts })
|
|
889
|
+
const p = store.getPipeline('pipeline-1')!
|
|
890
|
+
expect(p.status).toBe('done')
|
|
891
|
+
expect(p.finished_at).toBe(ts)
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
it('updatePipelineStatus persists total_tokens and total_cost_usd', () => {
|
|
895
|
+
store.insertPipeline(makePipeline())
|
|
896
|
+
store.updatePipelineStatus('pipeline-1', 'done', {
|
|
897
|
+
finished_at: '2026-01-01T01:00:00.000Z',
|
|
898
|
+
total_tokens: 12000,
|
|
899
|
+
total_cost_usd: '0.036000',
|
|
900
|
+
})
|
|
901
|
+
const p = store.getPipeline('pipeline-1')!
|
|
902
|
+
expect(p.total_tokens).toBe(12000)
|
|
903
|
+
expect(p.total_cost_usd).toBe('0.036000')
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
it('pipeline status can transition through all states', () => {
|
|
907
|
+
store.insertPipeline(makePipeline())
|
|
908
|
+
const states = ['running', 'failed', 'done', 'pending'] as const
|
|
909
|
+
for (const s of states) {
|
|
910
|
+
store.updatePipelineStatus('pipeline-1', s)
|
|
911
|
+
expect(store.getPipeline('pipeline-1')!.status).toBe(s)
|
|
912
|
+
}
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
it('convoy_specs is stored and retrieved as a JSON string', () => {
|
|
916
|
+
const specs = ['convoys/build.yml', 'convoys/test.yml', 'convoys/deploy.yml']
|
|
917
|
+
store.insertPipeline(makePipeline({ convoy_specs: JSON.stringify(specs) }))
|
|
918
|
+
const p = store.getPipeline('pipeline-1')!
|
|
919
|
+
expect(JSON.parse(p.convoy_specs)).toEqual(specs)
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
// ── pipeline-convoy linking ───────────────────────────────────────────────────
|
|
924
|
+
|
|
925
|
+
describe('pipeline-convoy linking', () => {
|
|
926
|
+
it('insertConvoy accepts pipeline_id', () => {
|
|
927
|
+
store.insertPipeline(makePipeline())
|
|
928
|
+
store.insertConvoy(makeConvoy({ pipeline_id: 'pipeline-1' }))
|
|
929
|
+
const c = store.getConvoy('convoy-1')!
|
|
930
|
+
expect(c.pipeline_id).toBe('pipeline-1')
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
it('insertConvoy with null pipeline_id creates a standalone convoy', () => {
|
|
934
|
+
store.insertConvoy(makeConvoy({ pipeline_id: null }))
|
|
935
|
+
const c = store.getConvoy('convoy-1')!
|
|
936
|
+
expect(c.pipeline_id).toBeNull()
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
it('getConvoysByPipeline returns all convoys for a pipeline', () => {
|
|
940
|
+
store.insertPipeline(makePipeline())
|
|
941
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-1', pipeline_id: 'pipeline-1', created_at: '2026-01-01T00:00:00.000Z' }))
|
|
942
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-2', pipeline_id: 'pipeline-1', created_at: '2026-01-01T01:00:00.000Z' }))
|
|
943
|
+
const convoys = store.getConvoysByPipeline('pipeline-1')
|
|
944
|
+
expect(convoys).toHaveLength(2)
|
|
945
|
+
expect(convoys.map(c => c.id)).toEqual(['convoy-1', 'convoy-2'])
|
|
946
|
+
})
|
|
947
|
+
|
|
948
|
+
it('getConvoysByPipeline returns convoys ordered by created_at', () => {
|
|
949
|
+
store.insertPipeline(makePipeline())
|
|
950
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-b', pipeline_id: 'pipeline-1', created_at: '2026-01-01T02:00:00.000Z' }))
|
|
951
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-a', pipeline_id: 'pipeline-1', created_at: '2026-01-01T01:00:00.000Z' }))
|
|
952
|
+
const convoys = store.getConvoysByPipeline('pipeline-1')
|
|
953
|
+
expect(convoys[0].id).toBe('convoy-a')
|
|
954
|
+
expect(convoys[1].id).toBe('convoy-b')
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
it('getConvoysByPipeline returns empty array when no convoys are linked', () => {
|
|
958
|
+
store.insertPipeline(makePipeline())
|
|
959
|
+
expect(store.getConvoysByPipeline('pipeline-1')).toHaveLength(0)
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
it('getConvoysByPipeline does not return convoys from other pipelines', () => {
|
|
963
|
+
store.insertPipeline(makePipeline({ id: 'pipeline-1' }))
|
|
964
|
+
store.insertPipeline(makePipeline({ id: 'pipeline-2' }))
|
|
965
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-1', pipeline_id: 'pipeline-1' }))
|
|
966
|
+
store.insertConvoy(makeConvoy({ id: 'convoy-2', pipeline_id: 'pipeline-2' }))
|
|
967
|
+
const p1Convoys = store.getConvoysByPipeline('pipeline-1')
|
|
968
|
+
expect(p1Convoys).toHaveLength(1)
|
|
969
|
+
expect(p1Convoys[0].id).toBe('convoy-1')
|
|
970
|
+
})
|
|
971
|
+
})
|
|
972
|
+
|
|
741
973
|
// ── close ─────────────────────────────────────────────────────────────────────
|
|
742
974
|
|
|
743
975
|
describe('close', () => {
|
package/src/cli/convoy/store.ts
CHANGED
|
@@ -7,12 +7,14 @@ import type {
|
|
|
7
7
|
WorkerRecord,
|
|
8
8
|
WorkerStatus,
|
|
9
9
|
EventRecord,
|
|
10
|
+
PipelineRecord,
|
|
11
|
+
PipelineStatus,
|
|
10
12
|
} from './types.js'
|
|
11
13
|
|
|
12
|
-
const SCHEMA_VERSION =
|
|
14
|
+
const SCHEMA_VERSION = 4
|
|
13
15
|
|
|
14
16
|
export interface ConvoyStore {
|
|
15
|
-
insertConvoy(record: Omit<ConvoyRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd'>): void
|
|
17
|
+
insertConvoy(record: Omit<ConvoyRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd' | 'pipeline_id'> & { pipeline_id?: string | null }): void
|
|
16
18
|
getConvoy(id: string): ConvoyRecord | undefined
|
|
17
19
|
getLatestConvoy(): ConvoyRecord | undefined
|
|
18
20
|
updateConvoyStatus(
|
|
@@ -46,6 +48,15 @@ export interface ConvoyStore {
|
|
|
46
48
|
): void
|
|
47
49
|
insertEvent(record: Omit<EventRecord, 'id'>): void
|
|
48
50
|
getEvents(convoyId: string): EventRecord[]
|
|
51
|
+
insertPipeline(record: Omit<PipelineRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd'>): void
|
|
52
|
+
getPipeline(id: string): PipelineRecord | undefined
|
|
53
|
+
getLatestPipeline(): PipelineRecord | undefined
|
|
54
|
+
updatePipelineStatus(
|
|
55
|
+
id: string,
|
|
56
|
+
status: PipelineStatus,
|
|
57
|
+
extra?: { started_at?: string; finished_at?: string; total_tokens?: number | null; total_cost_usd?: string | null },
|
|
58
|
+
): void
|
|
59
|
+
getConvoysByPipeline(pipelineId: string): ConvoyRecord[]
|
|
49
60
|
withTransaction<T>(fn: () => T): T
|
|
50
61
|
close(): void
|
|
51
62
|
}
|
|
@@ -75,7 +86,22 @@ class ConvoyStoreImpl implements ConvoyStore {
|
|
|
75
86
|
finished_at TEXT,
|
|
76
87
|
spec_yaml TEXT NOT NULL,
|
|
77
88
|
total_tokens INTEGER,
|
|
78
|
-
total_cost_usd TEXT
|
|
89
|
+
total_cost_usd TEXT,
|
|
90
|
+
pipeline_id TEXT
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE TABLE IF NOT EXISTS pipeline (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
name TEXT NOT NULL,
|
|
96
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
97
|
+
branch TEXT,
|
|
98
|
+
spec_yaml TEXT NOT NULL,
|
|
99
|
+
convoy_specs TEXT NOT NULL,
|
|
100
|
+
created_at TEXT NOT NULL,
|
|
101
|
+
started_at TEXT,
|
|
102
|
+
finished_at TEXT,
|
|
103
|
+
total_tokens INTEGER,
|
|
104
|
+
total_cost_usd TEXT
|
|
79
105
|
);
|
|
80
106
|
|
|
81
107
|
CREATE TABLE IF NOT EXISTS task (
|
|
@@ -145,15 +171,35 @@ class ConvoyStoreImpl implements ConvoyStore {
|
|
|
145
171
|
this.db.exec('PRAGMA user_version = 3')
|
|
146
172
|
version = 3
|
|
147
173
|
}
|
|
174
|
+
if (version === 3) {
|
|
175
|
+
this.db.exec(`
|
|
176
|
+
CREATE TABLE IF NOT EXISTS pipeline (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
name TEXT NOT NULL,
|
|
179
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
180
|
+
branch TEXT,
|
|
181
|
+
spec_yaml TEXT NOT NULL,
|
|
182
|
+
convoy_specs TEXT NOT NULL,
|
|
183
|
+
created_at TEXT NOT NULL,
|
|
184
|
+
started_at TEXT,
|
|
185
|
+
finished_at TEXT,
|
|
186
|
+
total_tokens INTEGER,
|
|
187
|
+
total_cost_usd TEXT
|
|
188
|
+
)
|
|
189
|
+
`)
|
|
190
|
+
this.db.exec('ALTER TABLE convoy ADD COLUMN pipeline_id TEXT')
|
|
191
|
+
this.db.exec('PRAGMA user_version = 4')
|
|
192
|
+
version = 4
|
|
193
|
+
}
|
|
148
194
|
}
|
|
149
195
|
|
|
150
|
-
insertConvoy(record: Omit<ConvoyRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd'>): void {
|
|
196
|
+
insertConvoy(record: Omit<ConvoyRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd' | 'pipeline_id'> & { pipeline_id?: string | null }): void {
|
|
151
197
|
this.db
|
|
152
198
|
.prepare(
|
|
153
|
-
`INSERT INTO convoy (id, name, spec_hash, status, branch, created_at, started_at, finished_at, spec_yaml)
|
|
154
|
-
VALUES (:id, :name, :spec_hash, :status, :branch, :created_at, NULL, NULL, :spec_yaml)`,
|
|
199
|
+
`INSERT INTO convoy (id, name, spec_hash, status, branch, created_at, started_at, finished_at, spec_yaml, pipeline_id)
|
|
200
|
+
VALUES (:id, :name, :spec_hash, :status, :branch, :created_at, NULL, NULL, :spec_yaml, :pipeline_id)`,
|
|
155
201
|
)
|
|
156
|
-
.run(record)
|
|
202
|
+
.run({ ...record, pipeline_id: record.pipeline_id ?? null })
|
|
157
203
|
}
|
|
158
204
|
|
|
159
205
|
getConvoy(id: string): ConvoyRecord | undefined {
|
|
@@ -324,6 +370,63 @@ class ConvoyStoreImpl implements ConvoyStore {
|
|
|
324
370
|
.all({ convoy_id: convoyId }) as unknown as EventRecord[]
|
|
325
371
|
}
|
|
326
372
|
|
|
373
|
+
insertPipeline(record: Omit<PipelineRecord, 'started_at' | 'finished_at' | 'total_tokens' | 'total_cost_usd'>): void {
|
|
374
|
+
this.db
|
|
375
|
+
.prepare(
|
|
376
|
+
`INSERT INTO pipeline (id, name, status, branch, spec_yaml, convoy_specs, created_at,
|
|
377
|
+
started_at, finished_at, total_tokens, total_cost_usd)
|
|
378
|
+
VALUES (:id, :name, :status, :branch, :spec_yaml, :convoy_specs, :created_at,
|
|
379
|
+
NULL, NULL, NULL, NULL)`,
|
|
380
|
+
)
|
|
381
|
+
.run(record)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
getPipeline(id: string): PipelineRecord | undefined {
|
|
385
|
+
return this.db
|
|
386
|
+
.prepare('SELECT * FROM pipeline WHERE id = :id')
|
|
387
|
+
.get({ id }) as PipelineRecord | undefined
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
getLatestPipeline(): PipelineRecord | undefined {
|
|
391
|
+
return this.db
|
|
392
|
+
.prepare('SELECT * FROM pipeline ORDER BY created_at DESC LIMIT 1')
|
|
393
|
+
.get() as PipelineRecord | undefined
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
updatePipelineStatus(
|
|
397
|
+
id: string,
|
|
398
|
+
status: PipelineStatus,
|
|
399
|
+
extra?: { started_at?: string; finished_at?: string; total_tokens?: number | null; total_cost_usd?: string | null },
|
|
400
|
+
): void {
|
|
401
|
+
const sets = ['status = :status']
|
|
402
|
+
const params: Record<string, string | number | null> = { id, status }
|
|
403
|
+
|
|
404
|
+
if (extra?.started_at !== undefined) {
|
|
405
|
+
sets.push('started_at = :started_at')
|
|
406
|
+
params.started_at = extra.started_at
|
|
407
|
+
}
|
|
408
|
+
if (extra?.finished_at !== undefined) {
|
|
409
|
+
sets.push('finished_at = :finished_at')
|
|
410
|
+
params.finished_at = extra.finished_at
|
|
411
|
+
}
|
|
412
|
+
if (extra?.total_tokens !== undefined) {
|
|
413
|
+
sets.push('total_tokens = :total_tokens')
|
|
414
|
+
params.total_tokens = extra.total_tokens
|
|
415
|
+
}
|
|
416
|
+
if (extra?.total_cost_usd !== undefined) {
|
|
417
|
+
sets.push('total_cost_usd = :total_cost_usd')
|
|
418
|
+
params.total_cost_usd = extra.total_cost_usd
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.db.prepare(`UPDATE pipeline SET ${sets.join(', ')} WHERE id = :id`).run(params)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
getConvoysByPipeline(pipelineId: string): ConvoyRecord[] {
|
|
425
|
+
return this.db
|
|
426
|
+
.prepare('SELECT * FROM convoy WHERE pipeline_id = :pipeline_id ORDER BY created_at')
|
|
427
|
+
.all({ pipeline_id: pipelineId }) as unknown as ConvoyRecord[]
|
|
428
|
+
}
|
|
429
|
+
|
|
327
430
|
withTransaction<T>(fn: () => T): T {
|
|
328
431
|
this.db.exec('BEGIN')
|
|
329
432
|
try {
|
package/src/cli/convoy/types.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type ConvoyTaskStatus =
|
|
|
11
11
|
|
|
12
12
|
export type WorkerStatus = 'spawned' | 'running' | 'done' | 'failed' | 'killed'
|
|
13
13
|
|
|
14
|
+
export type PipelineStatus = 'pending' | 'running' | 'done' | 'failed'
|
|
15
|
+
|
|
14
16
|
export interface ConvoyRecord {
|
|
15
17
|
id: string
|
|
16
18
|
name: string
|
|
@@ -23,6 +25,7 @@ export interface ConvoyRecord {
|
|
|
23
25
|
spec_yaml: string
|
|
24
26
|
total_tokens: number | null
|
|
25
27
|
total_cost_usd: string | null
|
|
28
|
+
pipeline_id: string | null
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface TaskRecord {
|
|
@@ -73,3 +76,17 @@ export interface EventRecord {
|
|
|
73
76
|
data: string | null
|
|
74
77
|
created_at: string
|
|
75
78
|
}
|
|
79
|
+
|
|
80
|
+
export interface PipelineRecord {
|
|
81
|
+
id: string
|
|
82
|
+
name: string
|
|
83
|
+
status: PipelineStatus
|
|
84
|
+
branch: string | null
|
|
85
|
+
spec_yaml: string
|
|
86
|
+
convoy_specs: string
|
|
87
|
+
created_at: string
|
|
88
|
+
started_at: string | null
|
|
89
|
+
finished_at: string | null
|
|
90
|
+
total_tokens: number | null
|
|
91
|
+
total_cost_usd: string | null
|
|
92
|
+
}
|
package/src/cli/dashboard.ts
CHANGED