db: split runtime facade by domain

This commit is contained in:
ejclaw
2026-04-11 08:09:29 +09:00
parent 10fa7fb747
commit af04fe04cf
5 changed files with 1117 additions and 956 deletions

1069
src/db.ts

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
import { Database } from 'bun:sqlite';
import {
openInitializedDatabaseFromFile,
openInitializedInMemoryDatabase,
openInitializedPersistentDatabase,
} from './database-lifecycle.js';
let db: Database | undefined;
export function initDatabase(): void {
db = openInitializedPersistentDatabase();
}
/** @internal - for tests only. Creates a fresh in-memory database. */
export function _initTestDatabase(): void {
db = openInitializedInMemoryDatabase();
}
/** @internal - for tests only. Opens an existing database file and runs schema/migrations. */
export function _initTestDatabaseFromFile(dbPath: string): void {
db = openInitializedDatabaseFromFile(dbPath);
}
export function requireDatabase(): Database {
if (!db) {
throw new Error('Database not initialized');
}
return db;
}
export function getDatabaseIfInitialized(): Database | undefined {
return db;
}

View File

@@ -0,0 +1,284 @@
import { SERVICE_SESSION_SCOPE } from '../config.js';
import { AgentType, NewMessage } from '../types.js';
import { requireDatabase } from './runtime-database.js';
import {
type MemoryRecord,
type MemoryScopeKind,
type MemorySourceKind,
type RecallMemoryQuery,
archiveMemoryInDatabase,
enforceMemoryBoundsInDatabase,
expireStaleMemoriesInDatabase,
recallMemoriesFromDatabase,
rememberMemoryInDatabase,
touchMemoriesInDatabase,
} from './memories.js';
import {
type ChatInfo,
getAllChatsFromDatabase,
getLastHumanMessageContentFromDatabase,
getLastHumanMessageSenderFromDatabase,
getLastHumanMessageTimestampFromDatabase,
getLatestMessageSeqAtOrBeforeFromDatabase,
getMessagesSinceFromDatabase,
getMessagesSinceSeqFromDatabase,
getNewMessagesBySeqFromDatabase,
getNewMessagesFromDatabase,
getRecentChatMessagesFromDatabase,
hasRecentRestartAnnouncementInDatabase,
storeChatMetadataInDatabase,
storeMessageInDatabase,
} from './messages.js';
import {
type CreateProducedWorkItemInput,
type WorkItem,
createProducedWorkItemInDatabase,
getOpenWorkItemForChatFromDatabase,
getOpenWorkItemFromDatabase,
markWorkItemDeliveredInDatabase,
markWorkItemDeliveryRetryInDatabase,
} from './work-items.js';
/** @internal - for tests only. */
export function _setMemoryTimestampsForTests(
id: number,
args: {
createdAt?: string;
lastUsedAt?: string | null;
archivedAt?: string | null;
},
): void {
const db = requireDatabase();
const existing = db
.prepare(
`SELECT created_at, last_used_at, archived_at
FROM memories
WHERE id = ?`,
)
.get(id) as
| {
created_at: string;
last_used_at: string | null;
archived_at: string | null;
}
| undefined;
if (!existing) throw new Error(`Memory ${id} not found`);
db.prepare(
`UPDATE memories
SET created_at = ?, last_used_at = ?, archived_at = ?
WHERE id = ?`,
).run(
args.createdAt ?? existing.created_at,
args.lastUsedAt === undefined ? existing.last_used_at : args.lastUsedAt,
args.archivedAt === undefined ? existing.archived_at : args.archivedAt,
id,
);
}
export function touchMemories(ids: number[]): void {
touchMemoriesInDatabase(requireDatabase(), ids);
}
export function archiveMemory(id: number): void {
archiveMemoryInDatabase(requireDatabase(), id);
}
export function expireStaleMemories(args?: {
scopeKind?: MemoryScopeKind;
scopeKey?: string;
now?: string;
}): number {
return expireStaleMemoriesInDatabase(requireDatabase(), args);
}
export function enforceMemoryBounds(
scopeKind: MemoryScopeKind,
scopeKey: string,
): void {
enforceMemoryBoundsInDatabase(requireDatabase(), scopeKind, scopeKey);
}
export function rememberMemory(input: {
scopeKind: MemoryScopeKind;
scopeKey: string;
content: string;
keywords?: string[];
memoryKind?: string | null;
sourceKind: MemorySourceKind;
sourceRef?: string | null;
}): number {
return rememberMemoryInDatabase(requireDatabase(), input);
}
export function recallMemories(query: RecallMemoryQuery): MemoryRecord[] {
return recallMemoriesFromDatabase(requireDatabase(), query);
}
export function storeChatMetadata(
chatJid: string,
timestamp: string,
name?: string,
channel?: string,
isGroup?: boolean,
): void {
storeChatMetadataInDatabase(
requireDatabase(),
chatJid,
timestamp,
name,
channel,
isGroup,
);
}
export function getAllChats(): ChatInfo[] {
return getAllChatsFromDatabase(requireDatabase());
}
export function storeMessage(msg: NewMessage): void {
storeMessageInDatabase(requireDatabase(), msg);
}
export function getNewMessages(
jids: string[],
lastTimestamp: string,
botPrefix: string,
limit: number = 200,
): { messages: NewMessage[]; newTimestamp: string } {
return getNewMessagesFromDatabase(
requireDatabase(),
jids,
lastTimestamp,
botPrefix,
limit,
);
}
export function getMessagesSince(
chatJid: string,
sinceTimestamp: string,
botPrefix: string,
limit: number = 200,
): NewMessage[] {
return getMessagesSinceFromDatabase(
requireDatabase(),
chatJid,
sinceTimestamp,
botPrefix,
limit,
);
}
export function getLatestMessageSeqAtOrBefore(
timestamp: string,
chatJid?: string,
): number {
return getLatestMessageSeqAtOrBeforeFromDatabase(
requireDatabase(),
timestamp,
chatJid,
);
}
export function getNewMessagesBySeq(
jids: string[],
lastSeqCursor: string | number,
botPrefix: string,
limit: number = 200,
): { messages: NewMessage[]; newSeqCursor: string } {
return getNewMessagesBySeqFromDatabase(
requireDatabase(),
jids,
lastSeqCursor,
botPrefix,
limit,
);
}
export function getMessagesSinceSeq(
chatJid: string,
sinceSeqCursor: string | number,
botPrefix: string,
limit: number = 200,
): NewMessage[] {
return getMessagesSinceSeqFromDatabase(
requireDatabase(),
chatJid,
sinceSeqCursor,
botPrefix,
limit,
);
}
export function getRecentChatMessages(
chatJid: string,
limit: number = 20,
): NewMessage[] {
return getRecentChatMessagesFromDatabase(requireDatabase(), chatJid, limit);
}
export function getLastHumanMessageTimestamp(chatJid: string): string | null {
return getLastHumanMessageTimestampFromDatabase(requireDatabase(), chatJid);
}
export function getLastHumanMessageSender(chatJid: string): string | null {
return getLastHumanMessageSenderFromDatabase(requireDatabase(), chatJid);
}
export function getLastHumanMessageContent(chatJid: string): string | null {
return getLastHumanMessageContentFromDatabase(requireDatabase(), chatJid);
}
export function hasRecentRestartAnnouncement(
chatJid: string,
sinceTimestamp: string,
): boolean {
return hasRecentRestartAnnouncementInDatabase(
requireDatabase(),
chatJid,
sinceTimestamp,
);
}
export function getOpenWorkItem(
chatJid: string,
agentType: AgentType = 'claude-code',
serviceId: string = SERVICE_SESSION_SCOPE,
): WorkItem | undefined {
return getOpenWorkItemFromDatabase(
requireDatabase(),
chatJid,
agentType,
serviceId,
);
}
export function getOpenWorkItemForChat(
chatJid: string,
serviceId: string = SERVICE_SESSION_SCOPE,
): WorkItem | undefined {
return getOpenWorkItemForChatFromDatabase(
requireDatabase(),
chatJid,
serviceId,
);
}
export function createProducedWorkItem(
input: CreateProducedWorkItemInput,
): WorkItem {
return createProducedWorkItemInDatabase(requireDatabase(), input);
}
export function markWorkItemDelivered(
id: number,
deliveryMessageId?: string | null,
): void {
markWorkItemDeliveredInDatabase(requireDatabase(), id, deliveryMessageId);
}
export function markWorkItemDeliveryRetry(id: number, error: string): void {
markWorkItemDeliveryRetryInDatabase(requireDatabase(), id, error);
}

311
src/db/runtime-paired.ts Normal file
View File

@@ -0,0 +1,311 @@
import { SERVICE_SESSION_SCOPE } from '../config.js';
import { type PairedTurnIdentity } from '../paired-turn-identity.js';
import {
AgentType,
PairedProject,
PairedRoomRole,
PairedTask,
PairedTurnOutput,
PairedTurnReservationIntentKind,
PairedWorkspace,
} from '../types.js';
import {
clearChannelOwnerLeaseInDatabase,
getAllChannelOwnerLeasesFromDatabase,
getChannelOwnerLeaseFromDatabase,
setChannelOwnerLeaseInDatabase,
type ChannelOwnerLeaseRow,
type SetChannelOwnerLeaseInput,
} from './channel-owner-leases.js';
import {
claimPairedTurnReservationInDatabase,
clearPairedTaskExecutionLeasesInDatabase,
clearPairedTurnReservationsInDatabase,
type PairedTaskUpdates,
createPairedTaskInDatabase,
getLastBotFinalMessageFromDatabase,
getLatestOpenPairedTaskForChatFromDatabase,
getLatestPairedTaskForChatFromDatabase,
getPairedProjectFromDatabase,
getPairedTaskByIdFromDatabase,
getPairedWorkspaceFromDatabase,
listPairedWorkspacesForTaskFromDatabase,
refreshPairedTaskExecutionLeaseInDatabase,
releasePairedTaskExecutionLeaseInDatabase,
reservePairedTurnReservationInDatabase,
updatePairedTaskIfUnchangedInDatabase,
updatePairedTaskInDatabase,
upsertPairedProjectInDatabase,
upsertPairedWorkspaceInDatabase,
} from './paired-state.js';
import {
clearPairedTurnAttemptsInDatabase,
getPairedTurnAttemptsForTurnFromDatabase,
type PairedTurnAttemptRecord,
} from './paired-turn-attempts.js';
import {
getLatestTurnNumberFromDatabase,
getPairedTurnOutputsFromDatabase,
insertPairedTurnOutputInDatabase,
} from './paired-turn-outputs.js';
import {
clearPairedTurnsInDatabase,
completePairedTurnInDatabase,
failPairedTurnInDatabase,
getPairedTurnByIdFromDatabase,
getPairedTurnsForTaskFromDatabase,
markPairedTurnRunningInDatabase,
type PairedTurnRecord,
} from './paired-turns.js';
import { requireDatabase } from './runtime-database.js';
import {
type CompleteServiceHandoffCursorInput,
type CreateServiceHandoffInput,
type ServiceHandoff,
claimServiceHandoffInDatabase,
completeServiceHandoffAndAdvanceTargetCursorInDatabase,
completeServiceHandoffInDatabase,
createServiceHandoffInDatabase,
failServiceHandoffInDatabase,
getAllPendingServiceHandoffsFromDatabase,
getPendingServiceHandoffsFromDatabase,
} from './service-handoffs.js';
export function upsertPairedProject(project: PairedProject): void {
upsertPairedProjectInDatabase(requireDatabase(), project);
}
export function getPairedProject(chatJid: string): PairedProject | undefined {
return getPairedProjectFromDatabase(requireDatabase(), chatJid);
}
export function createPairedTask(task: PairedTask): void {
createPairedTaskInDatabase(requireDatabase(), task);
}
export function getPairedTaskById(id: string): PairedTask | undefined {
return getPairedTaskByIdFromDatabase(requireDatabase(), id);
}
export function getLatestPairedTaskForChat(
chatJid: string,
): PairedTask | undefined {
return getLatestPairedTaskForChatFromDatabase(requireDatabase(), chatJid);
}
export function getLatestOpenPairedTaskForChat(
chatJid: string,
): PairedTask | undefined {
return getLatestOpenPairedTaskForChatFromDatabase(requireDatabase(), chatJid);
}
export function updatePairedTask(id: string, updates: PairedTaskUpdates): void {
updatePairedTaskInDatabase(requireDatabase(), id, updates);
}
export function updatePairedTaskIfUnchanged(
id: string,
expectedUpdatedAt: string,
updates: PairedTaskUpdates,
): boolean {
return updatePairedTaskIfUnchangedInDatabase(
requireDatabase(),
id,
expectedUpdatedAt,
updates,
);
}
export function reservePairedTurnReservation(args: {
chatJid: string;
taskId: string;
taskStatus: PairedTask['status'];
roundTripCount: number;
taskUpdatedAt: string;
intentKind: PairedTurnReservationIntentKind;
runId: string;
}): boolean {
return reservePairedTurnReservationInDatabase(requireDatabase(), args);
}
export function claimPairedTurnReservation(args: {
chatJid: string;
taskId: string;
taskStatus: PairedTask['status'];
roundTripCount: number;
taskUpdatedAt: string;
intentKind: PairedTurnReservationIntentKind;
runId: string;
}): boolean {
return claimPairedTurnReservationInDatabase(requireDatabase(), args);
}
export function releasePairedTaskExecutionLease(args: {
taskId: string;
runId: string;
}): void {
releasePairedTaskExecutionLeaseInDatabase(requireDatabase(), args);
}
export function refreshPairedTaskExecutionLease(args: {
taskId: string;
runId: string;
now?: string;
}): boolean {
return refreshPairedTaskExecutionLeaseInDatabase(requireDatabase(), args);
}
export function markPairedTurnRunning(args: {
turnIdentity: PairedTurnIdentity;
executorServiceId?: string | null;
executorAgentType?: AgentType | null;
runId?: string | null;
}): void {
markPairedTurnRunningInDatabase(requireDatabase(), args);
}
export function completePairedTurn(turnIdentity: PairedTurnIdentity): void {
completePairedTurnInDatabase(requireDatabase(), turnIdentity);
}
export function failPairedTurn(args: {
turnIdentity: PairedTurnIdentity;
error?: string | null;
}): void {
failPairedTurnInDatabase(requireDatabase(), args);
}
export function getPairedTurnById(
turnId: string,
): PairedTurnRecord | undefined {
return getPairedTurnByIdFromDatabase(requireDatabase(), turnId);
}
export function getPairedTurnsForTask(taskId: string): PairedTurnRecord[] {
return getPairedTurnsForTaskFromDatabase(requireDatabase(), taskId);
}
export function getPairedTurnAttempts(
turnId: string,
): PairedTurnAttemptRecord[] {
return getPairedTurnAttemptsForTurnFromDatabase(requireDatabase(), turnId);
}
export function upsertPairedWorkspace(workspace: PairedWorkspace): void {
upsertPairedWorkspaceInDatabase(requireDatabase(), workspace);
}
export function getPairedWorkspace(
taskId: string,
role: PairedWorkspace['role'],
): PairedWorkspace | undefined {
return getPairedWorkspaceFromDatabase(requireDatabase(), taskId, role);
}
export function listPairedWorkspacesForTask(taskId: string): PairedWorkspace[] {
return listPairedWorkspacesForTaskFromDatabase(requireDatabase(), taskId);
}
/** @internal - for tests only. */
export function _clearPairedTurnReservationsForTests(): void {
const db = requireDatabase();
clearPairedTurnReservationsInDatabase(db);
clearPairedTaskExecutionLeasesInDatabase(db);
clearPairedTurnAttemptsInDatabase(db);
clearPairedTurnsInDatabase(db);
}
export function getLastBotFinalMessage(
chatJid: string,
agentType: AgentType = 'claude-code',
limit: number = 1,
): Array<{ content: string; timestamp: string }> {
return getLastBotFinalMessageFromDatabase(
requireDatabase(),
chatJid,
agentType,
limit,
);
}
export function getChannelOwnerLease(
chatJid: string,
): ChannelOwnerLeaseRow | undefined {
return getChannelOwnerLeaseFromDatabase(requireDatabase(), chatJid);
}
export function getAllChannelOwnerLeases(): ChannelOwnerLeaseRow[] {
return getAllChannelOwnerLeasesFromDatabase(requireDatabase());
}
export function setChannelOwnerLease(input: SetChannelOwnerLeaseInput): void {
setChannelOwnerLeaseInDatabase(requireDatabase(), input);
}
export function clearChannelOwnerLease(chatJid: string): void {
clearChannelOwnerLeaseInDatabase(requireDatabase(), chatJid);
}
export function createServiceHandoff(
input: CreateServiceHandoffInput,
): ServiceHandoff {
return createServiceHandoffInDatabase(requireDatabase(), input);
}
export function getPendingServiceHandoffs(
targetServiceId: string = SERVICE_SESSION_SCOPE,
): ServiceHandoff[] {
return getPendingServiceHandoffsFromDatabase(
requireDatabase(),
targetServiceId,
);
}
export function getAllPendingServiceHandoffs(): ServiceHandoff[] {
return getAllPendingServiceHandoffsFromDatabase(requireDatabase());
}
export function claimServiceHandoff(id: number): boolean {
return claimServiceHandoffInDatabase(requireDatabase(), id);
}
export function completeServiceHandoff(id: number): void {
completeServiceHandoffInDatabase(requireDatabase(), id);
}
export function failServiceHandoff(id: number, error: string): void {
failServiceHandoffInDatabase(requireDatabase(), id, error);
}
export function completeServiceHandoffAndAdvanceTargetCursor(
input: CompleteServiceHandoffCursorInput,
): string | null {
return completeServiceHandoffAndAdvanceTargetCursorInDatabase(
requireDatabase(),
input,
);
}
export function insertPairedTurnOutput(
taskId: string,
turnNumber: number,
role: PairedRoomRole,
outputText: string,
): void {
insertPairedTurnOutputInDatabase(
requireDatabase(),
taskId,
turnNumber,
role,
outputText,
);
}
export function getPairedTurnOutputs(taskId: string): PairedTurnOutput[] {
return getPairedTurnOutputsFromDatabase(requireDatabase(), taskId);
}
export function getLatestTurnNumber(taskId: string): number {
return getLatestTurnNumberFromDatabase(requireDatabase(), taskId);
}

View File

@@ -0,0 +1,375 @@
import fs from 'fs';
import {
CLAUDE_SERVICE_ID,
CODEX_MAIN_SERVICE_ID,
CODEX_REVIEW_SERVICE_ID,
SERVICE_ID,
SERVICE_SESSION_SCOPE,
} from '../config.js';
import {
resolveServiceTaskSessionsPath as resolveServiceTaskSessionsPathFromGroup,
resolveTaskRuntimeIpcPath as resolveTaskRuntimeIpcPathFromGroup,
resolveTaskSessionsPath as resolveTaskSessionsPathFromGroup,
} from '../group-folder.js';
import { logger } from '../logger.js';
import { inferAgentTypeFromServiceShadow } from '../role-service-shadow.js';
import { getTaskRuntimeTaskId } from '../task-watch-status.js';
import {
AgentType,
RegisteredGroup,
RoomMode,
ScheduledTask,
TaskRunLog,
} from '../types.js';
import {
type AssignRoomInput,
assignRoomInDatabase,
clearExplicitRoomModeInDatabase,
deleteStoredRoomSettingsForTestsInDatabase,
getAllRoomBindingsFromDatabase,
getEffectiveRoomModeFromDatabase,
getEffectiveRuntimeRoomModeFromDatabase,
getExplicitRoomModeFromDatabase,
getRegisteredAgentTypesForJidFromDatabase,
getRegisteredGroupFromDatabase,
getStoredRoomSettingsFromDatabase,
setExplicitRoomModeInDatabase,
setRegisteredGroupForTestsInDatabase,
setStoredRoomOwnerAgentTypeForTestsInDatabase,
updateRegisteredGroupNameInDatabase,
} from './rooms.js';
import { type StoredRoomSettings } from './room-registration.js';
import {
deleteAllSessionsForGroupFromDatabase,
deleteSessionFromDatabase,
getAllSessionsForAgentTypeFromDatabase,
getSessionFromDatabase,
setSessionInDatabase,
} from './sessions.js';
import {
getLastRespondingAgentTypeFromDatabase,
getRouterStateForServiceFromDatabase,
getRouterStateFromDatabase,
setRouterStateForServiceInDatabase,
setRouterStateInDatabase,
} from './router-state.js';
import {
getDatabaseIfInitialized,
requireDatabase,
} from './runtime-database.js';
import {
type CreateScheduledTaskInput,
type ScheduledTaskStatusTrackingUpdates,
type ScheduledTaskUpdates,
createTaskInDatabase,
findDuplicateCiWatcherInDatabase,
getAllTasksFromDatabase,
getDueTasksFromDatabase,
getRecentConsecutiveErrorsFromDatabase,
getTaskByIdFromDatabase,
getTasksForGroupFromDatabase,
hasActiveCiWatcherForChatInDatabase,
logTaskRunInDatabase,
updateTaskAfterRunInDatabase,
updateTaskInDatabase,
updateTaskStatusTrackingInDatabase,
} from './tasks.js';
/** @internal - for tests only. */
export function _setStoredRoomOwnerAgentTypeForTests(
chatJid: string,
ownerAgentType: AgentType | null,
): void {
setStoredRoomOwnerAgentTypeForTestsInDatabase(
requireDatabase(),
chatJid,
ownerAgentType,
);
}
/** @internal - for tests only. */
export function _deleteStoredRoomSettingsForTests(chatJid: string): void {
deleteStoredRoomSettingsForTestsInDatabase(requireDatabase(), chatJid);
}
export function createTask(task: CreateScheduledTaskInput): void {
createTaskInDatabase(requireDatabase(), task);
}
export function getTaskById(id: string): ScheduledTask | undefined {
return getTaskByIdFromDatabase(requireDatabase(), id);
}
export function findDuplicateCiWatcher(
chatJid: string,
ciProvider: string,
ciMetadata: string,
): ScheduledTask | undefined {
return findDuplicateCiWatcherInDatabase(
requireDatabase(),
chatJid,
ciProvider,
ciMetadata,
);
}
export function getTasksForGroup(
groupFolder: string,
agentType?: AgentType,
): ScheduledTask[] {
return getTasksForGroupFromDatabase(
requireDatabase(),
groupFolder,
agentType,
);
}
export function getAllTasks(agentType?: AgentType): ScheduledTask[] {
return getAllTasksFromDatabase(requireDatabase(), agentType);
}
export function updateTask(id: string, updates: ScheduledTaskUpdates): void {
updateTaskInDatabase(requireDatabase(), id, updates);
}
export function updateTaskStatusTracking(
id: string,
updates: ScheduledTaskStatusTrackingUpdates,
): void {
updateTaskStatusTrackingInDatabase(requireDatabase(), id, updates);
}
export function deleteTask(id: string): void {
const db = requireDatabase();
const task = getTaskByIdFromDatabase(db, id);
db.prepare('DELETE FROM task_run_logs WHERE task_id = ?').run(id);
db.prepare('DELETE FROM scheduled_tasks WHERE id = ?').run(id);
if (!task) return;
const runtimeTaskId = getTaskRuntimeTaskId(task);
if (!runtimeTaskId) return;
const cleanupTargets: string[] = [];
try {
cleanupTargets.push(
resolveTaskRuntimeIpcPathFromGroup(task.group_folder, runtimeTaskId),
resolveTaskSessionsPathFromGroup(task.group_folder, runtimeTaskId),
resolveServiceTaskSessionsPathFromGroup(
task.group_folder,
CLAUDE_SERVICE_ID,
runtimeTaskId,
),
resolveServiceTaskSessionsPathFromGroup(
task.group_folder,
CODEX_MAIN_SERVICE_ID,
runtimeTaskId,
),
resolveServiceTaskSessionsPathFromGroup(
task.group_folder,
CODEX_REVIEW_SERVICE_ID,
runtimeTaskId,
),
);
} catch (err) {
logger.warn(
{ taskId: id, groupFolder: task.group_folder, err },
'Failed to resolve task-scoped cleanup paths',
);
return;
}
for (const cleanupPath of cleanupTargets) {
try {
fs.rmSync(cleanupPath, { recursive: true, force: true });
} catch (err) {
logger.warn(
{ taskId: id, cleanupPath, err },
'Failed to remove task-scoped runtime artifacts',
);
}
}
}
export function hasActiveCiWatcherForChat(chatJid: string): boolean {
return hasActiveCiWatcherForChatInDatabase(requireDatabase(), chatJid);
}
export function getDueTasks(): ScheduledTask[] {
return getDueTasksFromDatabase(requireDatabase());
}
export function updateTaskAfterRun(
id: string,
nextRun: string | null,
lastResult: string,
): void {
updateTaskAfterRunInDatabase(requireDatabase(), id, nextRun, lastResult);
}
export function logTaskRun(log: TaskRunLog): void {
logTaskRunInDatabase(requireDatabase(), log);
}
export function getRecentConsecutiveErrors(
taskId: string,
limit: number = 5,
): string[] {
return getRecentConsecutiveErrorsFromDatabase(
requireDatabase(),
taskId,
limit,
);
}
export function getRouterState(key: string): string | undefined {
return getRouterStateFromDatabase(requireDatabase(), key, SERVICE_ID);
}
export function getRouterStateForService(
key: string,
serviceId: string,
): string | undefined {
return getRouterStateForServiceFromDatabase(
requireDatabase(),
key,
serviceId,
);
}
export function setRouterState(key: string, value: string): void {
setRouterStateInDatabase(requireDatabase(), key, value);
}
export function setRouterStateForService(
key: string,
value: string,
serviceId: string,
): void {
setRouterStateForServiceInDatabase(requireDatabase(), key, value, serviceId);
}
export function getSession(
groupFolder: string,
agentType: AgentType = 'claude-code',
): string | undefined {
return getSessionFromDatabase(requireDatabase(), groupFolder, agentType);
}
export function setSession(
groupFolder: string,
sessionId: string,
agentType: AgentType = 'claude-code',
): void {
setSessionInDatabase(requireDatabase(), groupFolder, agentType, sessionId);
}
export function deleteSession(
groupFolder: string,
agentType: AgentType = 'claude-code',
): void {
deleteSessionFromDatabase(requireDatabase(), groupFolder, agentType);
}
export function deleteAllSessionsForGroup(groupFolder: string): void {
deleteAllSessionsForGroupFromDatabase(requireDatabase(), groupFolder);
}
export function getAllSessions(): Record<string, string> {
const currentAgentType =
inferAgentTypeFromServiceShadow(SERVICE_SESSION_SCOPE) ?? 'claude-code';
return getAllSessionsForAgentTypeFromDatabase(
requireDatabase(),
currentAgentType,
);
}
export function getSessionForAgentType(
groupFolder: string,
agentType: string,
): string | undefined {
return getSessionFromDatabase(requireDatabase(), groupFolder, agentType);
}
export function setSessionForAgentType(
groupFolder: string,
agentType: string,
sessionId: string,
): void {
setSessionInDatabase(requireDatabase(), groupFolder, agentType, sessionId);
}
export function getLastRespondingAgentType(
chatJid: string,
): AgentType | undefined {
return getLastRespondingAgentTypeFromDatabase(requireDatabase(), chatJid);
}
export function getRegisteredGroup(
jid: string,
agentType?: string,
): (RegisteredGroup & { jid: string }) | undefined {
return getRegisteredGroupFromDatabase(requireDatabase(), jid, agentType);
}
/** @internal Test helper for seeding canonical room bindings. */
export function _setRegisteredGroupForTests(
jid: string,
group: RegisteredGroup,
): void {
setRegisteredGroupForTestsInDatabase(requireDatabase(), jid, group);
}
export function assignRoom(
chatJid: string,
input: AssignRoomInput,
): (RegisteredGroup & { jid: string }) | undefined {
return assignRoomInDatabase(requireDatabase(), chatJid, input);
}
export function updateRegisteredGroupName(jid: string, name: string): void {
updateRegisteredGroupNameInDatabase(requireDatabase(), jid, name);
}
export function getAllRoomBindings(
agentTypeFilter?: string,
): Record<string, RegisteredGroup> {
return getAllRoomBindingsFromDatabase(requireDatabase(), agentTypeFilter);
}
export function getRegisteredAgentTypesForJid(jid: string): AgentType[] {
const db = getDatabaseIfInitialized();
if (!db) return [];
return getRegisteredAgentTypesForJidFromDatabase(db, jid);
}
export function getStoredRoomSettings(
chatJid: string,
): StoredRoomSettings | undefined {
const db = getDatabaseIfInitialized();
if (!db) return undefined;
return getStoredRoomSettingsFromDatabase(db, chatJid);
}
export function getExplicitRoomMode(chatJid: string): RoomMode | undefined {
return getExplicitRoomModeFromDatabase(requireDatabase(), chatJid);
}
export function setExplicitRoomMode(chatJid: string, roomMode: RoomMode): void {
setExplicitRoomModeInDatabase(requireDatabase(), chatJid, roomMode);
}
export function clearExplicitRoomMode(chatJid: string): void {
clearExplicitRoomModeInDatabase(requireDatabase(), chatJid);
}
export function getEffectiveRoomMode(chatJid: string): RoomMode {
return getEffectiveRoomModeFromDatabase(requireDatabase(), chatJid);
}
export function getEffectiveRuntimeRoomMode(chatJid: string): RoomMode {
return getEffectiveRuntimeRoomModeFromDatabase(requireDatabase(), chatJid);
}