fix: simplify and stabilize runtime service routing
This commit is contained in:
549
src/db/schema.ts
549
src/db/schema.ts
@@ -1,29 +1,30 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
|
||||
import { normalizeServiceId } from '../config.js';
|
||||
import {
|
||||
ARBITER_AGENT_TYPE,
|
||||
CLAUDE_SERVICE_ID,
|
||||
CODEX_MAIN_SERVICE_ID,
|
||||
CODEX_REVIEW_SERVICE_ID,
|
||||
OWNER_AGENT_TYPE,
|
||||
SERVICE_SESSION_SCOPE,
|
||||
normalizeServiceId,
|
||||
} from '../config.js';
|
||||
import {
|
||||
inferAgentTypeFromServiceShadow,
|
||||
resolveRoleServiceShadow,
|
||||
} from '../role-service-shadow.js';
|
||||
import {
|
||||
resolveStablePairedTaskOwnerAgentType,
|
||||
resolveStableReviewerAgentType,
|
||||
resolveStableRoomRoleAgentType,
|
||||
} from './legacy-rebuilds.js';
|
||||
import { normalizeStoredAgentType } from './room-registration.js';
|
||||
import {
|
||||
backfillLegacyServiceSessions,
|
||||
dropLegacyServiceSessionsTable,
|
||||
migrateSessionsTableToCompositePk,
|
||||
} from './sessions.js';
|
||||
|
||||
export interface SchemaMigrationHooks {
|
||||
backfillMessageSeq(database: Database): void;
|
||||
backfillStoredRoomSettings(database: Database): void;
|
||||
backfillChannelOwnerRoleMetadata(database: Database): void;
|
||||
backfillWorkItemServiceShadows(database: Database): void;
|
||||
backfillServiceHandoffServiceShadows(database: Database): void;
|
||||
backfillPairedTaskRoleMetadata(database: Database): void;
|
||||
rebuildWorkItemsCanonicalSchema(database: Database): void;
|
||||
rebuildChannelOwnerCanonicalSchema(database: Database): void;
|
||||
rebuildPairedTasksCanonicalSchema(database: Database): void;
|
||||
rebuildServiceHandoffsCanonicalSchema(database: Database): void;
|
||||
}
|
||||
|
||||
function getTableColumns(database: Database, tableName: string): string[] {
|
||||
return (
|
||||
database.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{
|
||||
@@ -48,6 +49,43 @@ function tryExecMigration(database: Database, sql: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function backfillMessageSeq(database: Database): void {
|
||||
const rows = database
|
||||
.prepare(
|
||||
`SELECT rowid, seq
|
||||
FROM messages
|
||||
ORDER BY CASE WHEN seq IS NULL THEN 1 ELSE 0 END, seq, timestamp, rowid`,
|
||||
)
|
||||
.all() as Array<{ rowid: number; seq: number | null }>;
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextSeq = 1;
|
||||
const assignSeq = database.prepare(
|
||||
'UPDATE messages SET seq = ? WHERE rowid = ? AND seq IS NULL',
|
||||
);
|
||||
const tx = database.transaction(() => {
|
||||
for (const row of rows) {
|
||||
if (row.seq === null) {
|
||||
assignSeq.run(nextSeq, row.rowid);
|
||||
}
|
||||
nextSeq = Math.max(nextSeq, (row.seq ?? nextSeq) + 1);
|
||||
}
|
||||
});
|
||||
tx();
|
||||
|
||||
const maxSeqRow = database
|
||||
.prepare('SELECT MAX(seq) AS maxSeq FROM messages')
|
||||
.get() as { maxSeq: number | null };
|
||||
const maxSeq = maxSeqRow.maxSeq ?? 0;
|
||||
if (maxSeq > 0) {
|
||||
database
|
||||
.prepare('INSERT OR IGNORE INTO message_sequence (id) VALUES (?)')
|
||||
.run(maxSeq);
|
||||
}
|
||||
}
|
||||
|
||||
interface LegacyExecutionLeaseServiceRow {
|
||||
rowid: number;
|
||||
role: string;
|
||||
@@ -59,13 +97,53 @@ interface LegacyExecutionLeaseServiceRow {
|
||||
arbiter_agent_type?: string | null;
|
||||
}
|
||||
|
||||
function normalizePairedAgentType(
|
||||
agentType: string | null | undefined,
|
||||
): 'codex' | 'claude-code' | null {
|
||||
if (agentType === 'codex' || agentType === 'claude-code') {
|
||||
return agentType;
|
||||
}
|
||||
return null;
|
||||
interface StoredPairedTaskServiceRow {
|
||||
rowid: number;
|
||||
chat_jid: string;
|
||||
group_folder: string;
|
||||
owner_service_id?: string | null;
|
||||
reviewer_service_id?: string | null;
|
||||
owner_agent_type?: string | null;
|
||||
reviewer_agent_type?: string | null;
|
||||
}
|
||||
|
||||
interface StoredChannelOwnerLeaseServiceRow {
|
||||
rowid: number;
|
||||
chat_jid: string;
|
||||
owner_service_id?: string | null;
|
||||
reviewer_service_id?: string | null;
|
||||
arbiter_service_id?: string | null;
|
||||
owner_agent_type?: string | null;
|
||||
reviewer_agent_type?: string | null;
|
||||
arbiter_agent_type?: string | null;
|
||||
}
|
||||
|
||||
interface StoredServiceHandoffServiceRow {
|
||||
id: number;
|
||||
chat_jid: string;
|
||||
group_folder: string;
|
||||
source_service_id?: string | null;
|
||||
target_service_id?: string | null;
|
||||
source_role?: string | null;
|
||||
target_role?: string | null;
|
||||
source_agent_type?: string | null;
|
||||
target_agent_type?: string | null;
|
||||
intended_role?: string | null;
|
||||
}
|
||||
|
||||
interface StoredWorkItemServiceRow {
|
||||
id: number;
|
||||
agent_type?: string | null;
|
||||
service_id?: string | null;
|
||||
delivery_role?: string | null;
|
||||
}
|
||||
|
||||
function normalizePairedRole(
|
||||
role: string | null | undefined,
|
||||
): 'owner' | 'reviewer' | 'arbiter' | null {
|
||||
return role === 'owner' || role === 'reviewer' || role === 'arbiter'
|
||||
? role
|
||||
: null;
|
||||
}
|
||||
|
||||
function inferLegacyExecutionLeaseServiceId(
|
||||
@@ -73,7 +151,7 @@ function inferLegacyExecutionLeaseServiceId(
|
||||
): string | null {
|
||||
switch (row.role) {
|
||||
case 'owner': {
|
||||
const ownerAgentType = normalizePairedAgentType(row.owner_agent_type);
|
||||
const ownerAgentType = normalizeStoredAgentType(row.owner_agent_type);
|
||||
return (
|
||||
(row.owner_service_id
|
||||
? normalizeServiceId(row.owner_service_id)
|
||||
@@ -81,7 +159,7 @@ function inferLegacyExecutionLeaseServiceId(
|
||||
);
|
||||
}
|
||||
case 'reviewer': {
|
||||
const reviewerAgentType = normalizePairedAgentType(
|
||||
const reviewerAgentType = normalizeStoredAgentType(
|
||||
row.reviewer_agent_type,
|
||||
);
|
||||
return (
|
||||
@@ -91,7 +169,7 @@ function inferLegacyExecutionLeaseServiceId(
|
||||
);
|
||||
}
|
||||
case 'arbiter': {
|
||||
const arbiterAgentType = normalizePairedAgentType(row.arbiter_agent_type);
|
||||
const arbiterAgentType = normalizeStoredAgentType(row.arbiter_agent_type);
|
||||
return (
|
||||
(row.arbiter_service_id
|
||||
? normalizeServiceId(row.arbiter_service_id)
|
||||
@@ -181,14 +259,344 @@ function backfillLegacyExecutionLeaseServiceShadows(database: Database): void {
|
||||
tx(rows);
|
||||
}
|
||||
|
||||
function backfillCanonicalPairedTaskServiceIds(database: Database): void {
|
||||
if (
|
||||
!tableHasColumn(database, 'paired_tasks', 'owner_service_id') ||
|
||||
!tableHasColumn(database, 'paired_tasks', 'reviewer_service_id')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = database
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
rowid,
|
||||
chat_jid,
|
||||
group_folder,
|
||||
owner_service_id,
|
||||
reviewer_service_id,
|
||||
owner_agent_type,
|
||||
reviewer_agent_type
|
||||
FROM paired_tasks
|
||||
`,
|
||||
)
|
||||
.all() as StoredPairedTaskServiceRow[];
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = database.prepare(
|
||||
`
|
||||
UPDATE paired_tasks
|
||||
SET owner_service_id = ?,
|
||||
reviewer_service_id = ?
|
||||
WHERE rowid = ?
|
||||
`,
|
||||
);
|
||||
const tx = database.transaction((taskRows: StoredPairedTaskServiceRow[]) => {
|
||||
for (const row of taskRows) {
|
||||
const persistedOwnerAgentType = normalizeStoredAgentType(
|
||||
row.owner_agent_type,
|
||||
);
|
||||
const persistedReviewerAgentType = normalizeStoredAgentType(
|
||||
row.reviewer_agent_type,
|
||||
);
|
||||
const stableOwnerAgentType = resolveStablePairedTaskOwnerAgentType(
|
||||
database,
|
||||
row,
|
||||
);
|
||||
const ownerAgentType =
|
||||
stableOwnerAgentType ??
|
||||
(row.owner_service_id
|
||||
? inferAgentTypeFromServiceShadow(row.owner_service_id)
|
||||
: undefined);
|
||||
const stableReviewerAgentType = resolveStableReviewerAgentType(
|
||||
stableOwnerAgentType,
|
||||
row.reviewer_agent_type ?? null,
|
||||
);
|
||||
const reviewerAgentType =
|
||||
persistedReviewerAgentType ??
|
||||
stableReviewerAgentType ??
|
||||
(row.reviewer_service_id
|
||||
? inferAgentTypeFromServiceShadow(row.reviewer_service_id)
|
||||
: null) ??
|
||||
resolveStableReviewerAgentType(ownerAgentType, null);
|
||||
|
||||
const ownerServiceId =
|
||||
(persistedOwnerAgentType
|
||||
? resolveRoleServiceShadow('owner', ownerAgentType)
|
||||
: null) ??
|
||||
row.owner_service_id ??
|
||||
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
||||
CODEX_MAIN_SERVICE_ID;
|
||||
const reviewerServiceId =
|
||||
(persistedReviewerAgentType
|
||||
? resolveRoleServiceShadow('reviewer', reviewerAgentType)
|
||||
: null) ??
|
||||
row.reviewer_service_id ??
|
||||
resolveRoleServiceShadow('reviewer', reviewerAgentType) ??
|
||||
CODEX_REVIEW_SERVICE_ID;
|
||||
|
||||
if (
|
||||
row.owner_service_id === ownerServiceId &&
|
||||
row.reviewer_service_id === reviewerServiceId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update.run(ownerServiceId, reviewerServiceId, row.rowid);
|
||||
}
|
||||
});
|
||||
|
||||
tx(rows);
|
||||
}
|
||||
|
||||
function backfillCanonicalChannelOwnerServiceIds(database: Database): void {
|
||||
if (!tableHasColumn(database, 'channel_owner', 'owner_service_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = database
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
rowid,
|
||||
chat_jid,
|
||||
owner_service_id,
|
||||
reviewer_service_id,
|
||||
arbiter_service_id,
|
||||
owner_agent_type,
|
||||
reviewer_agent_type,
|
||||
arbiter_agent_type
|
||||
FROM channel_owner
|
||||
`,
|
||||
)
|
||||
.all() as StoredChannelOwnerLeaseServiceRow[];
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = database.prepare(
|
||||
`
|
||||
UPDATE channel_owner
|
||||
SET owner_service_id = ?,
|
||||
reviewer_service_id = ?,
|
||||
arbiter_service_id = ?
|
||||
WHERE rowid = ?
|
||||
`,
|
||||
);
|
||||
const tx = database.transaction(
|
||||
(leaseRows: StoredChannelOwnerLeaseServiceRow[]) => {
|
||||
for (const row of leaseRows) {
|
||||
const persistedOwnerAgentType = normalizeStoredAgentType(
|
||||
row.owner_agent_type,
|
||||
);
|
||||
const persistedReviewerAgentType = normalizeStoredAgentType(
|
||||
row.reviewer_agent_type,
|
||||
);
|
||||
const persistedArbiterAgentType = normalizeStoredAgentType(
|
||||
row.arbiter_agent_type,
|
||||
);
|
||||
const ownerAgentType =
|
||||
persistedOwnerAgentType ??
|
||||
(row.owner_service_id
|
||||
? inferAgentTypeFromServiceShadow(row.owner_service_id)
|
||||
: undefined) ??
|
||||
OWNER_AGENT_TYPE;
|
||||
const reviewerAgentType =
|
||||
row.reviewer_agent_type == null && row.reviewer_service_id == null
|
||||
? null
|
||||
: (persistedReviewerAgentType ??
|
||||
(row.reviewer_service_id
|
||||
? inferAgentTypeFromServiceShadow(row.reviewer_service_id)
|
||||
: null) ??
|
||||
resolveStableReviewerAgentType(ownerAgentType, null));
|
||||
const arbiterAgentType =
|
||||
row.arbiter_agent_type == null && row.arbiter_service_id == null
|
||||
? null
|
||||
: (persistedArbiterAgentType ??
|
||||
(row.arbiter_service_id
|
||||
? inferAgentTypeFromServiceShadow(row.arbiter_service_id)
|
||||
: undefined) ??
|
||||
ARBITER_AGENT_TYPE ??
|
||||
null);
|
||||
|
||||
const ownerServiceId =
|
||||
row.owner_service_id ??
|
||||
resolveRoleServiceShadow('owner', ownerAgentType) ??
|
||||
CLAUDE_SERVICE_ID;
|
||||
const reviewerServiceId =
|
||||
row.reviewer_service_id ??
|
||||
(reviewerAgentType == null
|
||||
? null
|
||||
: resolveRoleServiceShadow('reviewer', reviewerAgentType));
|
||||
const arbiterServiceId =
|
||||
row.arbiter_service_id ??
|
||||
(arbiterAgentType == null
|
||||
? null
|
||||
: resolveRoleServiceShadow('arbiter', arbiterAgentType));
|
||||
|
||||
if (
|
||||
row.owner_service_id === ownerServiceId &&
|
||||
row.reviewer_service_id === reviewerServiceId &&
|
||||
row.arbiter_service_id === arbiterServiceId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update.run(
|
||||
ownerServiceId,
|
||||
reviewerServiceId,
|
||||
arbiterServiceId,
|
||||
row.rowid,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
tx(rows);
|
||||
}
|
||||
|
||||
function backfillCanonicalServiceHandoffServiceIds(database: Database): void {
|
||||
if (!tableHasColumn(database, 'service_handoffs', 'source_service_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = database
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
id,
|
||||
chat_jid,
|
||||
group_folder,
|
||||
source_service_id,
|
||||
target_service_id,
|
||||
source_role,
|
||||
target_role,
|
||||
source_agent_type,
|
||||
target_agent_type,
|
||||
intended_role
|
||||
FROM service_handoffs
|
||||
`,
|
||||
)
|
||||
.all() as StoredServiceHandoffServiceRow[];
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = database.prepare(
|
||||
`
|
||||
UPDATE service_handoffs
|
||||
SET source_service_id = ?,
|
||||
target_service_id = ?
|
||||
WHERE id = ?
|
||||
`,
|
||||
);
|
||||
const tx = database.transaction(
|
||||
(handoffRows: StoredServiceHandoffServiceRow[]) => {
|
||||
for (const row of handoffRows) {
|
||||
const sourceRole =
|
||||
normalizePairedRole(row.source_role) ??
|
||||
normalizePairedRole(row.intended_role);
|
||||
const targetRole =
|
||||
normalizePairedRole(row.target_role) ??
|
||||
normalizePairedRole(row.intended_role);
|
||||
const sourceAgentType =
|
||||
normalizeStoredAgentType(row.source_agent_type) ??
|
||||
(sourceRole
|
||||
? resolveStableRoomRoleAgentType(database, {
|
||||
chatJid: row.chat_jid,
|
||||
groupFolder: row.group_folder,
|
||||
role: sourceRole,
|
||||
})
|
||||
: null);
|
||||
const targetAgentType =
|
||||
normalizeStoredAgentType(row.target_agent_type) ??
|
||||
(targetRole
|
||||
? resolveStableRoomRoleAgentType(database, {
|
||||
chatJid: row.chat_jid,
|
||||
groupFolder: row.group_folder,
|
||||
role: targetRole,
|
||||
})
|
||||
: null) ??
|
||||
'claude-code';
|
||||
|
||||
const sourceServiceId =
|
||||
row.source_service_id ??
|
||||
(sourceRole != null
|
||||
? (resolveRoleServiceShadow(sourceRole, sourceAgentType) ??
|
||||
SERVICE_SESSION_SCOPE)
|
||||
: SERVICE_SESSION_SCOPE);
|
||||
const targetServiceId =
|
||||
row.target_service_id ??
|
||||
(targetRole != null
|
||||
? (resolveRoleServiceShadow(targetRole, targetAgentType) ??
|
||||
SERVICE_SESSION_SCOPE)
|
||||
: SERVICE_SESSION_SCOPE);
|
||||
|
||||
if (
|
||||
row.source_service_id === sourceServiceId &&
|
||||
row.target_service_id === targetServiceId
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update.run(sourceServiceId, targetServiceId, row.id);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
tx(rows);
|
||||
}
|
||||
|
||||
function backfillCanonicalWorkItemServiceIds(database: Database): void {
|
||||
if (!tableHasColumn(database, 'work_items', 'service_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = database
|
||||
.prepare(
|
||||
`SELECT id, agent_type, service_id, delivery_role
|
||||
FROM work_items`,
|
||||
)
|
||||
.all() as StoredWorkItemServiceRow[];
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = database.prepare(
|
||||
`UPDATE work_items
|
||||
SET service_id = ?
|
||||
WHERE id = ?`,
|
||||
);
|
||||
const tx = database.transaction((workItemRows: StoredWorkItemServiceRow[]) => {
|
||||
for (const row of workItemRows) {
|
||||
const agentType = normalizeStoredAgentType(row.agent_type) ?? 'claude-code';
|
||||
const deliveryRole = normalizePairedRole(row.delivery_role) ?? 'owner';
|
||||
const serviceId =
|
||||
(row.service_id ? normalizeServiceId(row.service_id) : null) ??
|
||||
resolveRoleServiceShadow(deliveryRole, agentType) ??
|
||||
SERVICE_SESSION_SCOPE;
|
||||
|
||||
if (row.service_id === serviceId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update.run(serviceId, row.id);
|
||||
}
|
||||
});
|
||||
|
||||
tx(rows);
|
||||
}
|
||||
|
||||
export function applySchemaMigrations(
|
||||
database: Database,
|
||||
args: {
|
||||
assistantName: string;
|
||||
hooks: SchemaMigrationHooks;
|
||||
},
|
||||
): void {
|
||||
const { assistantName, hooks } = args;
|
||||
const { assistantName } = args;
|
||||
|
||||
tryExecMigration(
|
||||
database,
|
||||
@@ -269,6 +677,14 @@ export function applySchemaMigrations(
|
||||
database,
|
||||
`ALTER TABLE service_handoffs ADD COLUMN source_agent_type TEXT`,
|
||||
);
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE service_handoffs ADD COLUMN source_service_id TEXT`,
|
||||
);
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE service_handoffs ADD COLUMN target_service_id TEXT`,
|
||||
);
|
||||
|
||||
tryExecMigration(
|
||||
database,
|
||||
@@ -282,6 +698,14 @@ export function applySchemaMigrations(
|
||||
database,
|
||||
`ALTER TABLE paired_tasks ADD COLUMN arbiter_agent_type TEXT`,
|
||||
);
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE paired_tasks ADD COLUMN owner_service_id TEXT`,
|
||||
);
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE paired_tasks ADD COLUMN reviewer_service_id TEXT`,
|
||||
);
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE paired_task_execution_leases ADD COLUMN expires_at TEXT`,
|
||||
@@ -311,6 +735,7 @@ export function applySchemaMigrations(
|
||||
database,
|
||||
`ALTER TABLE work_items ADD COLUMN delivery_role TEXT`,
|
||||
);
|
||||
tryExecMigration(database, `ALTER TABLE work_items ADD COLUMN service_id TEXT`);
|
||||
|
||||
database.exec(
|
||||
`UPDATE service_handoffs
|
||||
@@ -333,6 +758,22 @@ export function applySchemaMigrations(
|
||||
WHERE source_role IS NULL`,
|
||||
);
|
||||
|
||||
for (const column of [
|
||||
'owner_service_id',
|
||||
'reviewer_service_id',
|
||||
'arbiter_service_id',
|
||||
'owner_agent_type',
|
||||
'reviewer_agent_type',
|
||||
'arbiter_agent_type',
|
||||
]) {
|
||||
tryExecMigration(database, `ALTER TABLE channel_owner ADD COLUMN ${column} TEXT`);
|
||||
}
|
||||
|
||||
backfillCanonicalPairedTaskServiceIds(database);
|
||||
backfillCanonicalChannelOwnerServiceIds(database);
|
||||
backfillCanonicalServiceHandoffServiceIds(database);
|
||||
backfillCanonicalWorkItemServiceIds(database);
|
||||
|
||||
database.exec(
|
||||
`UPDATE room_settings
|
||||
SET mode_source = 'explicit'
|
||||
@@ -375,7 +816,7 @@ export function applySchemaMigrations(
|
||||
|
||||
tryExecMigration(database, `ALTER TABLE messages ADD COLUMN seq INTEGER`);
|
||||
|
||||
hooks.backfillMessageSeq(database);
|
||||
backfillMessageSeq(database);
|
||||
|
||||
database.exec(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_messages_seq ON messages(seq);
|
||||
@@ -385,9 +826,9 @@ export function applySchemaMigrations(
|
||||
database.exec(`DROP INDEX IF EXISTS idx_work_items_open;`);
|
||||
database.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_work_items_group_agent
|
||||
ON work_items(chat_jid, agent_type, delivery_role, status);
|
||||
ON work_items(chat_jid, agent_type, service_id, delivery_role, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_work_items_open
|
||||
ON work_items(chat_jid, agent_type, IFNULL(delivery_role, ''))
|
||||
ON work_items(chat_jid, agent_type, IFNULL(service_id, ''), IFNULL(delivery_role, ''))
|
||||
WHERE status IN ('produced', 'delivery_retry');
|
||||
`);
|
||||
|
||||
@@ -516,6 +957,8 @@ export function applySchemaMigrations(
|
||||
id TEXT PRIMARY KEY,
|
||||
chat_jid TEXT NOT NULL,
|
||||
group_folder TEXT NOT NULL,
|
||||
owner_service_id TEXT NOT NULL,
|
||||
reviewer_service_id TEXT NOT NULL,
|
||||
owner_agent_type TEXT,
|
||||
reviewer_agent_type TEXT,
|
||||
arbiter_agent_type TEXT,
|
||||
@@ -548,11 +991,18 @@ export function applySchemaMigrations(
|
||||
.get() as { sql?: string } | undefined;
|
||||
const ptSql = ptSqlRow?.sql || '';
|
||||
if (ptSql && !ptSql.includes('arbiter_requested')) {
|
||||
const pairedTaskCols = getTableColumns(database, 'paired_tasks');
|
||||
const selectPairedTaskColumn = (columnName: string): string =>
|
||||
pairedTaskCols.includes(columnName)
|
||||
? columnName
|
||||
: `NULL AS ${columnName}`;
|
||||
database.exec(`
|
||||
CREATE TABLE paired_tasks_new (
|
||||
id TEXT PRIMARY KEY,
|
||||
chat_jid TEXT NOT NULL,
|
||||
group_folder TEXT NOT NULL,
|
||||
owner_service_id TEXT,
|
||||
reviewer_service_id TEXT,
|
||||
owner_agent_type TEXT,
|
||||
reviewer_agent_type TEXT,
|
||||
arbiter_agent_type TEXT,
|
||||
@@ -573,12 +1023,16 @@ export function applySchemaMigrations(
|
||||
CHECK (arbiter_agent_type IN ('claude-code', 'codex') OR arbiter_agent_type IS NULL)
|
||||
);
|
||||
INSERT INTO paired_tasks_new (
|
||||
id, chat_jid, group_folder, owner_agent_type, reviewer_agent_type,
|
||||
id, chat_jid, group_folder, owner_service_id, reviewer_service_id,
|
||||
owner_agent_type, reviewer_agent_type,
|
||||
arbiter_agent_type, title, source_ref, plan_notes, review_requested_at,
|
||||
round_trip_count, status, created_at, updated_at
|
||||
)
|
||||
SELECT
|
||||
id, chat_jid, group_folder, owner_agent_type, reviewer_agent_type,
|
||||
id, chat_jid, group_folder,
|
||||
${selectPairedTaskColumn('owner_service_id')},
|
||||
${selectPairedTaskColumn('reviewer_service_id')},
|
||||
owner_agent_type, reviewer_agent_type,
|
||||
arbiter_agent_type, title, source_ref, plan_notes, review_requested_at,
|
||||
round_trip_count, status, created_at, updated_at
|
||||
FROM paired_tasks;
|
||||
@@ -590,17 +1044,6 @@ export function applySchemaMigrations(
|
||||
}
|
||||
}
|
||||
|
||||
for (const column of [
|
||||
'owner_agent_type',
|
||||
'reviewer_agent_type',
|
||||
'arbiter_agent_type',
|
||||
]) {
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE channel_owner ADD COLUMN ${column} TEXT`,
|
||||
);
|
||||
}
|
||||
|
||||
tryExecMigration(
|
||||
database,
|
||||
`ALTER TABLE paired_tasks ADD COLUMN completion_reason TEXT`,
|
||||
@@ -663,6 +1106,8 @@ export function applySchemaMigrations(
|
||||
}
|
||||
|
||||
migrateSessionsTableToCompositePk(database, 'claude-code');
|
||||
backfillLegacyServiceSessions(database, inferAgentTypeFromServiceShadow);
|
||||
dropLegacyServiceSessionsTable(database);
|
||||
|
||||
try {
|
||||
database.exec(`ALTER TABLE chats ADD COLUMN channel TEXT`);
|
||||
@@ -683,24 +1128,4 @@ export function applySchemaMigrations(
|
||||
/* columns already exist */
|
||||
}
|
||||
|
||||
hooks.backfillStoredRoomSettings(database);
|
||||
if (tableHasColumn(database, 'channel_owner', 'owner_service_id')) {
|
||||
hooks.backfillChannelOwnerRoleMetadata(database);
|
||||
}
|
||||
backfillLegacyServiceSessions(database, inferAgentTypeFromServiceShadow);
|
||||
if (tableHasColumn(database, 'work_items', 'service_id')) {
|
||||
hooks.backfillWorkItemServiceShadows(database);
|
||||
}
|
||||
if (tableHasColumn(database, 'service_handoffs', 'source_service_id')) {
|
||||
hooks.backfillServiceHandoffServiceShadows(database);
|
||||
}
|
||||
if (tableHasColumn(database, 'paired_tasks', 'owner_service_id')) {
|
||||
hooks.backfillPairedTaskRoleMetadata(database);
|
||||
}
|
||||
|
||||
hooks.rebuildWorkItemsCanonicalSchema(database);
|
||||
dropLegacyServiceSessionsTable(database);
|
||||
hooks.rebuildChannelOwnerCanonicalSchema(database);
|
||||
hooks.rebuildPairedTasksCanonicalSchema(database);
|
||||
hooks.rebuildServiceHandoffsCanonicalSchema(database);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user