omp-conductor 0.12.0 → 0.13.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 +40 -19
- package/package.json +1 -1
- package/src/board.ts +1 -1
- package/src/briefs/orchestrator.md +32 -2
- package/src/cli.ts +104 -23
- package/src/config.ts +10 -2
- package/src/daemon.ts +818 -131
- package/src/diff-flags.ts +4 -0
- package/src/escalate.ts +5 -5
- package/src/failure-class.ts +7 -5
- package/src/fleet.ts +7 -2
- package/src/omp.ts +8 -4
- package/src/orchestrator-tick.ts +35 -20
- package/src/orchestrator.ts +3 -2
- package/src/plugin.ts +28 -2
- package/src/release-policy.ts +66 -6
- package/src/session-host.ts +4 -3
- package/src/setup.ts +4 -2
- package/src/store.ts +190 -29
- package/src/tracker/github.ts +261 -56
- package/src/types.ts +141 -28
- package/src/verbs/actions.ts +127 -15
- package/src/verbs/protocol.ts +7 -6
- package/src/verbs/server.ts +95 -13
- package/src/worker.ts +33 -5
- package/src/worktree.ts +5 -0
package/src/store.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
SessionRole,
|
|
42
42
|
SettlementFlag,
|
|
43
43
|
Store,
|
|
44
|
+
TurnOverride,
|
|
44
45
|
VerbDecision,
|
|
45
46
|
VerbLedgerDraft,
|
|
46
47
|
VerbLedgerEntry,
|
|
@@ -73,6 +74,7 @@ const FRICTION_HOLD_REASONS: ReadonlySet<FrictionAdmissionReason> = new Set([
|
|
|
73
74
|
"failed-attempts",
|
|
74
75
|
"continuations",
|
|
75
76
|
"parent-lookup-error",
|
|
77
|
+
"issue-state-lookup-error",
|
|
76
78
|
"open-pr-lookup-error",
|
|
77
79
|
"unroutable:no-repo-label",
|
|
78
80
|
"unroutable:multiple-repo-labels",
|
|
@@ -107,6 +109,10 @@ const UPDATABLE_COLUMNS: Record<string, true> = {
|
|
|
107
109
|
sessionFile: true,
|
|
108
110
|
prUrl: true,
|
|
109
111
|
headSha: true,
|
|
112
|
+
mergeSha: true,
|
|
113
|
+
baseRef: true,
|
|
114
|
+
baseCheck: true,
|
|
115
|
+
baseCheckAt: true,
|
|
110
116
|
salvageSha: true,
|
|
111
117
|
salvageError: true,
|
|
112
118
|
salvageAckAt: true,
|
|
@@ -139,6 +145,10 @@ interface RunRow {
|
|
|
139
145
|
sessionFile: string | null;
|
|
140
146
|
prUrl: string | null;
|
|
141
147
|
headSha: string | null;
|
|
148
|
+
mergeSha: string | null;
|
|
149
|
+
baseRef: string | null;
|
|
150
|
+
baseCheck: string | null;
|
|
151
|
+
baseCheckAt: number | null;
|
|
142
152
|
salvageSha: string | null;
|
|
143
153
|
salvageError: string | null;
|
|
144
154
|
salvageAckAt: number | null;
|
|
@@ -256,6 +266,10 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
256
266
|
sessionFile TEXT,
|
|
257
267
|
prUrl TEXT,
|
|
258
268
|
headSha TEXT,
|
|
269
|
+
mergeSha TEXT,
|
|
270
|
+
baseRef TEXT,
|
|
271
|
+
baseCheck TEXT,
|
|
272
|
+
baseCheckAt INTEGER,
|
|
259
273
|
salvageSha TEXT,
|
|
260
274
|
salvageError TEXT,
|
|
261
275
|
salvageAckAt INTEGER,
|
|
@@ -276,6 +290,28 @@ CREATE TABLE IF NOT EXISTS notifications (
|
|
|
276
290
|
at INTEGER NOT NULL
|
|
277
291
|
);
|
|
278
292
|
|
|
293
|
+
-- A one-shot operator-selected ceiling for an issue's next claimed attempt.
|
|
294
|
+
-- The run insert and consumption happen in one transaction.
|
|
295
|
+
CREATE TABLE IF NOT EXISTS turn_overrides (
|
|
296
|
+
project TEXT NOT NULL,
|
|
297
|
+
issue INTEGER NOT NULL CHECK (issue > 0),
|
|
298
|
+
maxTurns INTEGER NOT NULL CHECK (maxTurns > 0),
|
|
299
|
+
setAt INTEGER NOT NULL CHECK (setAt >= 0),
|
|
300
|
+
PRIMARY KEY (project, issue)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
-- Append-only audit of every operator-selected ceiling. Consuming or replacing
|
|
304
|
+
-- the pending value never erases what was requested.
|
|
305
|
+
CREATE TABLE IF NOT EXISTS turn_override_ledger (
|
|
306
|
+
id TEXT PRIMARY KEY,
|
|
307
|
+
project TEXT NOT NULL,
|
|
308
|
+
issue INTEGER NOT NULL CHECK (issue > 0),
|
|
309
|
+
maxTurns INTEGER NOT NULL CHECK (maxTurns > 0),
|
|
310
|
+
setAt INTEGER NOT NULL CHECK (setAt >= 0)
|
|
311
|
+
);
|
|
312
|
+
CREATE INDEX IF NOT EXISTS turn_override_ledger_project_at
|
|
313
|
+
ON turn_override_ledger (project, setAt);
|
|
314
|
+
|
|
279
315
|
CREATE TABLE IF NOT EXISTS dispatch_summaries (
|
|
280
316
|
project TEXT PRIMARY KEY,
|
|
281
317
|
summary TEXT NOT NULL
|
|
@@ -519,6 +555,10 @@ function toRecord(row: RunRow): RunRecord {
|
|
|
519
555
|
if (row.sessionFile !== null) record.sessionFile = row.sessionFile;
|
|
520
556
|
if (row.prUrl !== null) record.prUrl = row.prUrl;
|
|
521
557
|
if (row.headSha !== null) record.headSha = row.headSha;
|
|
558
|
+
if (row.mergeSha !== null) record.mergeSha = row.mergeSha;
|
|
559
|
+
if (row.baseRef !== null) record.baseRef = row.baseRef;
|
|
560
|
+
if (row.baseCheck !== null) record.baseCheck = row.baseCheck as RunRecord["baseCheck"];
|
|
561
|
+
if (row.baseCheckAt !== null) record.baseCheckAt = row.baseCheckAt;
|
|
522
562
|
if (row.salvageSha !== null) record.salvageSha = row.salvageSha;
|
|
523
563
|
if (row.salvageError !== null) record.salvageError = row.salvageError;
|
|
524
564
|
if (row.salvageAckAt !== null) record.salvageAckAt = row.salvageAckAt;
|
|
@@ -744,6 +784,18 @@ export function openStore(dbPath: string): Store {
|
|
|
744
784
|
if (!columns.some((column) => column.name === "headSha")) {
|
|
745
785
|
db.exec("ALTER TABLE runs ADD COLUMN headSha TEXT");
|
|
746
786
|
}
|
|
787
|
+
// Historical merged rows were never watched after settlement, so NULL is
|
|
788
|
+
// "not observed", never permission to claim their base branch was green.
|
|
789
|
+
for (const [name, type] of [
|
|
790
|
+
["mergeSha", "TEXT"],
|
|
791
|
+
["baseRef", "TEXT"],
|
|
792
|
+
["baseCheck", "TEXT"],
|
|
793
|
+
["baseCheckAt", "INTEGER"],
|
|
794
|
+
] as const) {
|
|
795
|
+
if (!columns.some((column) => column.name === name)) {
|
|
796
|
+
db.exec(`ALTER TABLE runs ADD COLUMN ${name} ${type}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
747
799
|
// v0.3.22 and earlier removed a blocked run's dirty tree without saving it
|
|
748
800
|
// (#118), so no row before this release has anywhere to record where the
|
|
749
801
|
// work went. A NULL `salvageSha` on a historical row therefore means "never
|
|
@@ -878,9 +930,10 @@ export function openStore(dbPath: string): Store {
|
|
|
878
930
|
const insertRun = db.query<unknown, SqlValue[]>(
|
|
879
931
|
`INSERT INTO runs (
|
|
880
932
|
id, project, issue, repo, branch, worktree, state, attempt, turns,
|
|
881
|
-
maxTurns, spendUsd, sessionFile, prUrl, headSha,
|
|
882
|
-
|
|
883
|
-
|
|
933
|
+
maxTurns, spendUsd, sessionFile, prUrl, headSha, mergeSha, baseRef,
|
|
934
|
+
baseCheck, baseCheckAt, salvageSha, salvageError, salvageAckAt, startedAt,
|
|
935
|
+
endedAt, lastError, settlementFlags, report
|
|
936
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
884
937
|
);
|
|
885
938
|
const selectRun = db.query<RunRow, [string]>(`SELECT * FROM runs WHERE id = ?`);
|
|
886
939
|
const selectActive = db.query<RunRow, SqlValue[]>(
|
|
@@ -910,6 +963,26 @@ export function openStore(dbPath: string): Store {
|
|
|
910
963
|
AND (state <> 'merged' OR COALESCE(endedAt, startedAt) >= ?)
|
|
911
964
|
ORDER BY startedAt DESC`,
|
|
912
965
|
);
|
|
966
|
+
const selectPendingBaseChecks = db.query<RunRow, [string, number]>(
|
|
967
|
+
`SELECT * FROM runs
|
|
968
|
+
WHERE project = ? AND state = 'merged' AND baseCheck = 'pending'
|
|
969
|
+
ORDER BY COALESCE(endedAt, startedAt) ASC, rowid ASC
|
|
970
|
+
LIMIT ?`,
|
|
971
|
+
);
|
|
972
|
+
const selectLatestBaseChecks = db.query<RunRow & { rank: number }, [string, number]>(
|
|
973
|
+
`SELECT * FROM (
|
|
974
|
+
SELECT runs.*,
|
|
975
|
+
ROW_NUMBER() OVER (
|
|
976
|
+
PARTITION BY repo
|
|
977
|
+
ORDER BY COALESCE(endedAt, startedAt) DESC, runs.rowid DESC
|
|
978
|
+
) AS rank
|
|
979
|
+
FROM runs
|
|
980
|
+
WHERE project = ? AND state = 'merged' AND baseCheck IS NOT NULL
|
|
981
|
+
AND COALESCE(baseCheckAt, endedAt, startedAt) >= ?
|
|
982
|
+
)
|
|
983
|
+
WHERE rank = 1
|
|
984
|
+
ORDER BY repo ASC`,
|
|
985
|
+
);
|
|
913
986
|
const countAttempts = db.query<{ n: number }, [string, number]>(
|
|
914
987
|
`SELECT COUNT(*) AS n FROM runs WHERE project = ? AND issue = ?`,
|
|
915
988
|
);
|
|
@@ -993,6 +1066,38 @@ export function openStore(dbPath: string): Store {
|
|
|
993
1066
|
ORDER BY startedAt DESC, rowid DESC
|
|
994
1067
|
LIMIT 1`,
|
|
995
1068
|
);
|
|
1069
|
+
const upsertTurnOverride = db.query<unknown, [string, number, number, number]>(
|
|
1070
|
+
`INSERT INTO turn_overrides (project, issue, maxTurns, setAt) VALUES (?, ?, ?, ?)
|
|
1071
|
+
ON CONFLICT(project, issue) DO UPDATE SET
|
|
1072
|
+
maxTurns = excluded.maxTurns,
|
|
1073
|
+
setAt = excluded.setAt`,
|
|
1074
|
+
);
|
|
1075
|
+
const insertTurnOverrideLedger = db.query<
|
|
1076
|
+
unknown,
|
|
1077
|
+
[string, string, number, number, number]
|
|
1078
|
+
>(
|
|
1079
|
+
`INSERT INTO turn_override_ledger (id, project, issue, maxTurns, setAt)
|
|
1080
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
1081
|
+
);
|
|
1082
|
+
const selectTurnOverride = db.query<{ maxTurns: number }, [string, number]>(
|
|
1083
|
+
`SELECT maxTurns FROM turn_overrides WHERE project = ? AND issue = ?`,
|
|
1084
|
+
);
|
|
1085
|
+
const deleteTurnOverride = db.query<unknown, [string, number]>(
|
|
1086
|
+
`DELETE FROM turn_overrides WHERE project = ? AND issue = ?`,
|
|
1087
|
+
);
|
|
1088
|
+
const selectTurnOverrides = db.query<TurnOverride, [string]>(
|
|
1089
|
+
`SELECT project, issue, maxTurns, setAt FROM turn_overrides
|
|
1090
|
+
WHERE project = ? ORDER BY issue ASC`,
|
|
1091
|
+
);
|
|
1092
|
+
const selectTurnOverrideLedger = db.query<
|
|
1093
|
+
TurnOverride,
|
|
1094
|
+
[string, number, number, number]
|
|
1095
|
+
>(
|
|
1096
|
+
`SELECT project, issue, maxTurns, setAt FROM turn_override_ledger
|
|
1097
|
+
WHERE project = ? AND (? = 0 OR issue = ?)
|
|
1098
|
+
ORDER BY setAt DESC, rowid DESC
|
|
1099
|
+
LIMIT ?`,
|
|
1100
|
+
);
|
|
996
1101
|
// Every attempt that settled with a report, in attempt order. NULLs are the
|
|
997
1102
|
// rows written before #199, or one killed before the worker returned a
|
|
998
1103
|
// report — skipping them makes a continuation read exactly today's priors.
|
|
@@ -1347,34 +1452,58 @@ export function openStore(dbPath: string): Store {
|
|
|
1347
1452
|
recordGhRefusalTx(at);
|
|
1348
1453
|
};
|
|
1349
1454
|
|
|
1455
|
+
const insertRunRecord = (r: Omit<RunRecord, "id">): RunRecord => {
|
|
1456
|
+
const record: RunRecord = { ...r, id: crypto.randomUUID() };
|
|
1457
|
+
insertRun.run(
|
|
1458
|
+
record.id,
|
|
1459
|
+
record.project,
|
|
1460
|
+
record.issue,
|
|
1461
|
+
record.repo,
|
|
1462
|
+
record.branch,
|
|
1463
|
+
record.worktree,
|
|
1464
|
+
record.state,
|
|
1465
|
+
record.attempt,
|
|
1466
|
+
record.turns,
|
|
1467
|
+
record.maxTurns,
|
|
1468
|
+
record.spendUsd,
|
|
1469
|
+
toSql(record.sessionFile),
|
|
1470
|
+
toSql(record.prUrl),
|
|
1471
|
+
toSql(record.headSha),
|
|
1472
|
+
toSql(record.mergeSha),
|
|
1473
|
+
toSql(record.baseRef),
|
|
1474
|
+
toSql(record.baseCheck),
|
|
1475
|
+
toSql(record.baseCheckAt),
|
|
1476
|
+
toSql(record.salvageSha),
|
|
1477
|
+
toSql(record.salvageError),
|
|
1478
|
+
toSql(record.salvageAckAt),
|
|
1479
|
+
record.startedAt,
|
|
1480
|
+
toSql(record.endedAt),
|
|
1481
|
+
toSql(record.lastError),
|
|
1482
|
+
toSql(record.settlementFlags),
|
|
1483
|
+
toSql(record.report),
|
|
1484
|
+
);
|
|
1485
|
+
return record;
|
|
1486
|
+
};
|
|
1487
|
+
const createRunTx = db.transaction((r: Omit<RunRecord, "id">): RunRecord => {
|
|
1488
|
+
if (r.state !== "claimed") return insertRunRecord(r);
|
|
1489
|
+
const turnOverride = selectTurnOverride.get(r.project, r.issue)?.maxTurns;
|
|
1490
|
+
const record = insertRunRecord({
|
|
1491
|
+
...r,
|
|
1492
|
+
maxTurns: Math.max(r.maxTurns, turnOverride ?? r.maxTurns),
|
|
1493
|
+
});
|
|
1494
|
+
if (turnOverride !== undefined) deleteTurnOverride.run(r.project, r.issue);
|
|
1495
|
+
return record;
|
|
1496
|
+
});
|
|
1497
|
+
const setTurnOverrideTx = db.transaction(
|
|
1498
|
+
(project: string, issue: number, maxTurns: number, setAt: number): void => {
|
|
1499
|
+
upsertTurnOverride.run(project, issue, maxTurns, setAt);
|
|
1500
|
+
insertTurnOverrideLedger.run(crypto.randomUUID(), project, issue, maxTurns, setAt);
|
|
1501
|
+
},
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1350
1504
|
return {
|
|
1351
1505
|
createRun(r: Omit<RunRecord, "id">): RunRecord {
|
|
1352
|
-
|
|
1353
|
-
insertRun.run(
|
|
1354
|
-
record.id,
|
|
1355
|
-
record.project,
|
|
1356
|
-
record.issue,
|
|
1357
|
-
record.repo,
|
|
1358
|
-
record.branch,
|
|
1359
|
-
record.worktree,
|
|
1360
|
-
record.state,
|
|
1361
|
-
record.attempt,
|
|
1362
|
-
record.turns,
|
|
1363
|
-
record.maxTurns,
|
|
1364
|
-
record.spendUsd,
|
|
1365
|
-
toSql(record.sessionFile),
|
|
1366
|
-
toSql(record.prUrl),
|
|
1367
|
-
toSql(record.headSha),
|
|
1368
|
-
toSql(record.salvageSha),
|
|
1369
|
-
toSql(record.salvageError),
|
|
1370
|
-
toSql(record.salvageAckAt),
|
|
1371
|
-
record.startedAt,
|
|
1372
|
-
toSql(record.endedAt),
|
|
1373
|
-
toSql(record.lastError),
|
|
1374
|
-
toSql(record.settlementFlags),
|
|
1375
|
-
toSql(record.report),
|
|
1376
|
-
);
|
|
1377
|
-
return record;
|
|
1506
|
+
return createRunTx(r);
|
|
1378
1507
|
},
|
|
1379
1508
|
|
|
1380
1509
|
updateRun(id: string, patch: Partial<RunRecord>): void {
|
|
@@ -1414,6 +1543,14 @@ export function openStore(dbPath: string): Store {
|
|
|
1414
1543
|
recentRuns(project: string, mergedSinceEpochMs: number): RunRecord[] {
|
|
1415
1544
|
return selectRecentRuns.all(project, mergedSinceEpochMs).map(toRecord);
|
|
1416
1545
|
},
|
|
1546
|
+
runsNeedingBaseCheck(project: string, limit = 20): RunRecord[] {
|
|
1547
|
+
return selectPendingBaseChecks.all(project, limit).map(toRecord);
|
|
1548
|
+
},
|
|
1549
|
+
|
|
1550
|
+
latestBaseChecks(project: string, sinceEpochMs: number): RunRecord[] {
|
|
1551
|
+
return selectLatestBaseChecks.all(project, sinceEpochMs).map(toRecord);
|
|
1552
|
+
},
|
|
1553
|
+
|
|
1417
1554
|
|
|
1418
1555
|
salvagedRuns(project: string): RunRecord[] {
|
|
1419
1556
|
return selectSalvaged.all(project).map(toRecord);
|
|
@@ -1440,6 +1577,30 @@ export function openStore(dbPath: string): Store {
|
|
|
1440
1577
|
return row ? toRecord(row) : undefined;
|
|
1441
1578
|
},
|
|
1442
1579
|
|
|
1580
|
+
setTurnOverride(project: string, issue: number, maxTurns: number, setAt = Date.now()): void {
|
|
1581
|
+
setTurnOverrideTx(project, issue, maxTurns, setAt);
|
|
1582
|
+
},
|
|
1583
|
+
|
|
1584
|
+
turnOverride(project: string, issue: number): number | undefined {
|
|
1585
|
+
return selectTurnOverride.get(project, issue)?.maxTurns;
|
|
1586
|
+
},
|
|
1587
|
+
|
|
1588
|
+
clearTurnOverride(project: string, issue: number): void {
|
|
1589
|
+
deleteTurnOverride.run(project, issue);
|
|
1590
|
+
},
|
|
1591
|
+
|
|
1592
|
+
listTurnOverrides(project: string): TurnOverride[] {
|
|
1593
|
+
return selectTurnOverrides.all(project);
|
|
1594
|
+
},
|
|
1595
|
+
|
|
1596
|
+
turnOverrideLedger(
|
|
1597
|
+
project: string,
|
|
1598
|
+
opts: { issue?: number; limit?: number } = {},
|
|
1599
|
+
): TurnOverride[] {
|
|
1600
|
+
const issue = opts.issue ?? 0;
|
|
1601
|
+
return selectTurnOverrideLedger.all(project, issue, issue, opts.limit ?? 50);
|
|
1602
|
+
},
|
|
1603
|
+
|
|
1443
1604
|
attemptReports(project: string, issue: number): { attempt: number; report: string }[] {
|
|
1444
1605
|
return selectAttemptReports.all(project, issue);
|
|
1445
1606
|
},
|