diff --git a/packages/opencode/migration/20260407155823_aiv_index_fixes/migration.sql b/packages/opencode/migration/20260407155823_aiv_index_fixes/migration.sql new file mode 100644 index 000000000..045073dff --- /dev/null +++ b/packages/opencode/migration/20260407155823_aiv_index_fixes/migration.sql @@ -0,0 +1,5 @@ +DROP INDEX IF EXISTS `aiv_event_session_idx`; +--> statement-breakpoint +DROP INDEX IF EXISTS `aiv_event_type_idx`; +--> statement-breakpoint +CREATE INDEX `aiv_state_time_updated_idx` ON `aiv_state` (`time_updated`); diff --git a/packages/opencode/src/aiv/aiv.sql.ts b/packages/opencode/src/aiv/aiv.sql.ts index fb7444331..d8b5e6af7 100644 --- a/packages/opencode/src/aiv/aiv.sql.ts +++ b/packages/opencode/src/aiv/aiv.sql.ts @@ -31,23 +31,25 @@ export const AivEventTable = sqliteTable( ...Timestamps, }, (table) => [ - index("aiv_event_session_idx").on(table.session_id), index("aiv_event_session_time_idx").on(table.session_id, table.time_created), - index("aiv_event_type_idx").on(table.type), ], ) /** Materialized current state per session (upserted from in-memory state) */ -export const AivStateTable = sqliteTable("aiv_state", { - session_id: text() - .$type() - .primaryKey() - .references(() => SessionTable.id, { onDelete: "cascade" }), - summary: text(), - work_type: text().notNull().default("unknown"), - location: text().notNull().default("unknown"), - scope_files: integer().notNull().default(0), - scope_modules: integer().notNull().default(0), - strategy_changes: text({ mode: "json" }).$type<{ from: string; to: string; timestamp: number }[]>(), - ...Timestamps, -}) +export const AivStateTable = sqliteTable( + "aiv_state", + { + session_id: text() + .$type() + .primaryKey() + .references(() => SessionTable.id, { onDelete: "cascade" }), + summary: text(), + work_type: text().notNull().default("unknown"), + location: text().notNull().default("unknown"), + scope_files: integer().notNull().default(0), + scope_modules: integer().notNull().default(0), + strategy_changes: text({ mode: "json" }).$type<{ from: string; to: string; timestamp: number }[]>(), + ...Timestamps, + }, + (table) => [index("aiv_state_time_updated_idx").on(table.time_updated)], +) diff --git a/packages/opencode/src/aiv/persistence.ts b/packages/opencode/src/aiv/persistence.ts index ea56f4537..9875e2a72 100644 --- a/packages/opencode/src/aiv/persistence.ts +++ b/packages/opencode/src/aiv/persistence.ts @@ -62,38 +62,30 @@ export namespace AivPersistence { strategyChanges: AivSchema.StrategyChange[] }) { try { - Database.transaction((tx) => { - const existing = tx - .select() - .from(AivStateTable) - .where(eq(AivStateTable.session_id, input.sessionID as SessionID)) - .get() - - if (existing) { - tx.update(AivStateTable) - .set({ - summary: input.summary ?? existing.summary, - work_type: input.workType, - location: input.location, - scope_files: input.scopeFiles, - scope_modules: input.scopeModules, - strategy_changes: input.strategyChanges, - }) - .where(eq(AivStateTable.session_id, input.sessionID as SessionID)) - .run() - } else { - tx.insert(AivStateTable) - .values({ - session_id: input.sessionID as SessionID, - summary: input.summary ?? null, + Database.use((db) => { + db.insert(AivStateTable) + .values({ + session_id: input.sessionID as SessionID, + summary: input.summary ?? null, + work_type: input.workType, + location: input.location, + scope_files: input.scopeFiles, + scope_modules: input.scopeModules, + strategy_changes: input.strategyChanges, + }) + .onConflictDoUpdate({ + target: AivStateTable.session_id, + set: { + summary: input.summary ?? undefined, work_type: input.workType, location: input.location, scope_files: input.scopeFiles, scope_modules: input.scopeModules, strategy_changes: input.strategyChanges, - }) - .run() - } + time_updated: Date.now(), + }, + }) + .run() }) } catch (e) { log.error("failed to upsert state", { error: e }) diff --git a/packages/opencode/src/server/routes/aiv.ts b/packages/opencode/src/server/routes/aiv.ts index 7e2db4034..112d3943a 100644 --- a/packages/opencode/src/server/routes/aiv.ts +++ b/packages/opencode/src/server/routes/aiv.ts @@ -112,6 +112,7 @@ export const AIVRoutes = lazy(() => }, }, }), + validator("param", z.object({ sessionID: SessionID.zod })), validator( "query", z.object({ @@ -119,7 +120,7 @@ export const AIVRoutes = lazy(() => }), ), async (c) => { - const sessionID = c.req.param("sessionID") + const { sessionID } = c.req.valid("param") const { limit } = c.req.valid("query") const events = AivPersistence.timeline(sessionID, limit) return c.json(events)