diff --git a/src/main/agent-permissions.ts b/src/main/agent-permissions.ts index 88c9654e..c9e93fc2 100644 --- a/src/main/agent-permissions.ts +++ b/src/main/agent-permissions.ts @@ -6,7 +6,7 @@ function safeGetAgent(db: AppDatabase, agentId: string | null | undefined) { return null; } try { - return db.entityCrud.getAgent(agentId); + return db.core.entity.getAgent(agentId); } catch (error) { if (error instanceof Error && error.message.startsWith("Agent not found:")) { return null; @@ -46,14 +46,14 @@ export function hasManagementAuthority(agent: { role: string; title: string; dep } function hasOrganizationPermission(db: AppDatabase, agentId: string) { - return db.agents.hasAgentPermission(agentId, "organization_manage"); + return db.core.agents.hasAgentPermission(agentId, "organization_manage"); } function managesAgent(db: AppDatabase, managerId: string, targetAgentId: string): boolean { if (managerId === targetAgentId) { return true; } - return db.agents.getChainOfCommand(targetAgentId).some((agent) => agent.id === managerId); + return db.core.agents.getChainOfCommand(targetAgentId).some((agent) => agent.id === managerId); } export function canControlAgent(db: AppDatabase, companyId: string, actorAgentId: string, targetAgentId: string): boolean { @@ -96,7 +96,7 @@ export function canRequestHire(db: AppDatabase, companyId: string, agentId: stri if (canManageOrganization(db, companyId, agentId)) { return true; } - if (db.agents.getDirectReports(agentId, companyId).length > 0) { + if (db.core.agents.getDirectReports(agentId, companyId).length > 0) { return true; } return hasManagementAuthority(agent); @@ -154,9 +154,9 @@ function getDepartmentLeadCandidateId( .filter((candidate) => candidate.id !== excludeAgentId) .map((candidate) => ({ id: candidate.id, - hasReports: db.agents.getDirectReports(candidate.id, companyId).length > 0, + hasReports: db.core.agents.getDirectReports(candidate.id, companyId).length > 0, leadershipScore: hasManagementAuthority(candidate) ? 1 : 0, - chainDepth: db.agents.getChainOfCommand(candidate.id).length, + chainDepth: db.core.agents.getChainOfCommand(candidate.id).length, createdAt: candidate.createdAt, })) .filter((candidate) => candidate.hasReports || candidate.leadershipScore > 0) @@ -187,7 +187,7 @@ export function resolveHireReportsTo( } if (!department) { - return requester?.id ?? db.agents.getCeoForCompany(companyId)?.id ?? null; + return requester?.id ?? db.core.agents.getCeoForCompany(companyId)?.id ?? null; } if (requester && (requester.department === department || !requester.reportsTo)) { @@ -203,7 +203,7 @@ export function resolveHireReportsTo( return requester.id; } - return db.agents.getCeoForCompany(companyId)?.id ?? null; + return db.core.agents.getCeoForCompany(companyId)?.id ?? null; } function normalizeComparableText(value: string): string { @@ -281,7 +281,7 @@ export function canAssignTask(db: AppDatabase, companyId: string, actorAgentId: if (!targetAgentId || targetAgentId === actorAgentId) { return true; } - if (!db.settings.belongsToCompany("agents", targetAgentId, companyId)) { + if (!db.core.settings.belongsToCompany("agents", targetAgentId, companyId)) { return false; } if (canManageOrganization(db, companyId, actorAgentId)) { diff --git a/src/main/agent-runtime/artifact-work-products.ts b/src/main/agent-runtime/artifact-work-products.ts index 6cf26c69..804760b0 100644 --- a/src/main/agent-runtime/artifact-work-products.ts +++ b/src/main/agent-runtime/artifact-work-products.ts @@ -51,7 +51,7 @@ export function retireDeletedArtifacts( deleted: string[], ): ProjectionOutcome[] { if (deleted.length === 0) return []; - const archived = db.documentsMod.archiveWorkProductsByExternalPaths({ + const archived = db.core.documents.archiveWorkProductsByExternalPaths({ companyId: ctx.companyId, workspaceId: ctx.workspaceId, provider: "filesystem", @@ -97,24 +97,24 @@ function upsertFilesystemArtifact( const title = lastSlash >= 0 ? relativePath.slice(lastSlash + 1) : relativePath; const provider = "filesystem"; const resolvedWorkspaceId = - ctx.workspaceId ?? db.workspacesMod.findWorkspaceIdByLocalPath(ctx.companyId, workspacePath); - const existing = db.documentsMod.findWorkProductByExternalId( + ctx.workspaceId ?? db.core.workspaces.findWorkspaceIdByLocalPath(ctx.companyId, workspacePath); + const existing = db.core.documents.findWorkProductByExternalId( ctx.companyId, provider, relativePath, resolvedWorkspaceId, ); if (existing) { - const patch: Parameters[2] = { + const patch: Parameters[2] = { title, status: "active", lastTouchedByRunId: ctx.runId, }; if (resolvedWorkspaceId) patch.workspaceId = resolvedWorkspaceId; - db.documentsMod.updateWorkProduct(existing.id, ctx.companyId, patch); + db.core.documents.updateWorkProduct(existing.id, ctx.companyId, patch); return { kind: "updated", workProductId: existing.id, relativePath }; } - const workProductId = db.documentsMod.createWorkProduct({ + const workProductId = db.core.documents.createWorkProduct({ companyId: ctx.companyId, taskId: ctx.taskId, workspaceId: resolvedWorkspaceId, diff --git a/src/main/agent-runtime/cost-writer.ts b/src/main/agent-runtime/cost-writer.ts index 24f019c5..1e8407c4 100644 --- a/src/main/agent-runtime/cost-writer.ts +++ b/src/main/agent-runtime/cost-writer.ts @@ -45,7 +45,7 @@ export class CostWriter { ); } - this.deps.db.runs.recordCostEntry({ + this.deps.db.core.runs.recordCostEntry({ runId: input.runId, companyId: input.companyId, agentId: input.agentId, diff --git a/src/main/agent-runtime/hook-handlers.ts b/src/main/agent-runtime/hook-handlers.ts index 99e94af2..5696cfd5 100644 --- a/src/main/agent-runtime/hook-handlers.ts +++ b/src/main/agent-runtime/hook-handlers.ts @@ -113,9 +113,9 @@ function checkOperationalApprovalBlock( toolName: string, ): BeforeToolCallRpcResult { try { - const blocking = deps.db.approvalsMod.getBlockingApproval(ctx.companyId, ctx.agentId, ctx.taskId); + const blocking = deps.db.core.approvals.getBlockingApproval(ctx.companyId, ctx.agentId, ctx.taskId); if (!blocking) return { block: false, reason: null }; - const companyMode = deps.db.company.getCompanyExecutionMode(ctx.companyId); + const companyMode = deps.db.core.company.getCompanyExecutionMode(ctx.companyId); const cofounderCheckpointBlocks = companyMode === "ai_cofounder" && isCofounderMandatoryApproval(blocking.type); if (!isOperationalApproval(blocking.type) && !cofounderCheckpointBlocks) { diff --git a/src/main/agent-runtime/internal-tools/file-approval-handler.ts b/src/main/agent-runtime/internal-tools/file-approval-handler.ts index c7d4f08d..71aaad1c 100644 --- a/src/main/agent-runtime/internal-tools/file-approval-handler.ts +++ b/src/main/agent-runtime/internal-tools/file-approval-handler.ts @@ -5,7 +5,7 @@ * `api-server/routes-approvals.ts`: validate → enforce the * `approve_ceo_strategy` ordering gate → request approval → auto- * approve or notify. Both the tool path and the HTTP path funnel - * through the same `db.approvalsMod.requestApproval` + `shouldAutoApprove` + * through the same `db.core.approvals.requestApproval` + `shouldAutoApprove` * plumbing, so the board sees identical rows regardless of entry point. * * No fallbacks: malformed input throws UserInputError, DB failures @@ -71,7 +71,7 @@ export function handleFileApprovalToolCall( // otherwise re-open the exact loop the HTTP gate was written to // close. if (input.type === "approve_ceo_strategy") { - const companyHireApprovals = db.approvalsMod.listCompanyApprovalsByType(companyId, "hire_agent"); + const companyHireApprovals = db.core.approvals.listCompanyApprovalsByType(companyId, "hire_agent"); if (companyHireApprovals.length === 0) { throw new UserInputError({ code: "file_approval.strategy.before_hires", @@ -79,7 +79,7 @@ export function handleFileApprovalToolCall( "approve_ceo_strategy requires at least one hire_agent approval to exist first. File the intended hires, then the strategy.", }); } - const existingStrategy = db.approvalsMod + const existingStrategy = db.core.approvals .listCompanyApprovalsByType(companyId, "approve_ceo_strategy") .find((a) => a.state === "pending" || a.state === "approved"); if (existingStrategy) { @@ -111,7 +111,7 @@ export function handleFileApprovalToolCall( let approvalId: string; try { - approvalId = db.approvalsMod.requestApproval({ + approvalId = db.core.approvals.requestApproval({ companyId, type: input.type, relatedTaskId: input.relatedTaskId ?? null, @@ -131,7 +131,7 @@ export function handleFileApprovalToolCall( } try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: requesterId, action: "approval.created", diff --git a/src/main/agent-runtime/internal-tools/hire-agent-handler.ts b/src/main/agent-runtime/internal-tools/hire-agent-handler.ts index fcc452e7..300d5f89 100644 --- a/src/main/agent-runtime/internal-tools/hire-agent-handler.ts +++ b/src/main/agent-runtime/internal-tools/hire-agent-handler.ts @@ -137,7 +137,7 @@ export function handleHireAgentToolCall( resolvedReportsTo && resolvedReportsTo !== requesterId && !canManageOrganization(db, companyId, requesterId) && - !db.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === requesterId) + !db.core.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === requesterId) ) { throw new UserInputError({ code: "hire_agent.routing.not_in_chain", @@ -149,7 +149,7 @@ export function handleHireAgentToolCall( const connectorRecord = (() => { try { - return db.connectorsCrud.getConnector(input.connectorId); + return db.core.connectors.getConnector(input.connectorId); } catch (error) { throw new UserInputError({ code: "hire_agent.connector.missing", @@ -167,7 +167,7 @@ export function handleHireAgentToolCall( }); } - const existingAgents = db.agents.listExistingAgentsForHireCheck(companyId); + const existingAgents = db.core.agents.listExistingAgentsForHireCheck(companyId); const duplicateByName = existingAgents.find( (a) => a.name.toLowerCase() === input.name.toLowerCase(), ); @@ -178,7 +178,7 @@ export function handleHireAgentToolCall( }); } - const pendingHires = db.approvalsMod.listPendingHireApprovalPayloads(companyId); + const pendingHires = db.core.approvals.listPendingHireApprovalPayloads(companyId); for (const pending of pendingHires) { try { const payload = pending.payloadJson ? JSON.parse(pending.payloadJson) as Record : {}; @@ -199,7 +199,7 @@ export function handleHireAgentToolCall( const managerLabel = resolvedReportsTo ? (() => { try { - return db.entityCrud.getAgent(resolvedReportsTo).name; + return db.core.entity.getAgent(resolvedReportsTo).name; } catch { return resolvedReportsTo; } @@ -252,7 +252,7 @@ export function handleHireAgentToolCall( } try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: requesterId, action: "agent.hire_requested", diff --git a/src/main/agent-runtime/runtime-bridge.ts b/src/main/agent-runtime/runtime-bridge.ts index 9908535e..bb649eeb 100644 --- a/src/main/agent-runtime/runtime-bridge.ts +++ b/src/main/agent-runtime/runtime-bridge.ts @@ -235,7 +235,7 @@ export class RuntimeBridge { */ private resolveAgentName(agentId: string): string { try { - const name = this.deps.db.agents.findAgentName(agentId); + const name = this.deps.db.core.agents.findAgentName(agentId); if (name && name.length > 0) return name; } catch (error) { this.deps.logger.warn( @@ -434,11 +434,11 @@ export class RuntimeBridge { // `?unreadOnly=true`, so agents posted status comments instead of // replies. The explicit read endpoint is the sole signal. const store = this.getOrCreateStore(payload.taskSessionId); - const inboxRecords = this.deps.db.messages.listAgentInboxUnread(payload.companyId, payload.agentId); + const inboxRecords = this.deps.db.core.messages.listAgentInboxUnread(payload.companyId, payload.agentId); if (inboxRecords.length > 0) { this.deps.db.withTransaction(() => { for (const record of inboxRecords) { - const claimed = this.deps.db.inboxProjections.claimProjection(record.id, payload.taskSessionId); + const claimed = this.deps.db.projection.inbox.claimProjection(record.id, payload.taskSessionId); if (!claimed) continue; // `projectInboundMessage` allocates a monotonic pi-timestamp // and indexes the correlation under `record.id`. Two rows diff --git a/src/main/agent-runtime/runtime-thread-projector.ts b/src/main/agent-runtime/runtime-thread-projector.ts index c79eb357..87078904 100644 --- a/src/main/agent-runtime/runtime-thread-projector.ts +++ b/src/main/agent-runtime/runtime-thread-projector.ts @@ -52,7 +52,7 @@ export class RuntimeThreadProjector { try { const body = extractTextBody(message.content); const actor = this.resolveThreadEventActor(envelope, message); - this.deps.db.threads.addThreadEvent({ + this.deps.db.core.threads.addThreadEvent({ threadId: thread.id, actorId: actor.actorId, actorType: actor.actorType, @@ -91,7 +91,7 @@ export class RuntimeThreadProjector { : record.senderKind === "system" ? "system" : "agent"; - this.deps.db.threads.addThreadEvent({ + this.deps.db.core.threads.addThreadEvent({ threadId: thread.id, actorId: record.fromAgentId, actorType, @@ -116,7 +116,7 @@ export class RuntimeThreadProjector { private resolveThreadForRun(envelope: RuntimeThreadEnvelope) { if (envelope.taskId) { try { - const task = this.deps.db.entityCrud.getTask(envelope.taskId); + const task = this.deps.db.core.entity.getTask(envelope.taskId); return ensureTaskThread(this.deps.db, envelope.taskId, envelope.companyId, task.title); } catch (error) { this.deps.logger.warn( diff --git a/src/main/agent-runtime/sqlite-session-store.ts b/src/main/agent-runtime/sqlite-session-store.ts index 7905532e..a97c443d 100644 --- a/src/main/agent-runtime/sqlite-session-store.ts +++ b/src/main/agent-runtime/sqlite-session-store.ts @@ -18,7 +18,7 @@ export class SqliteSessionStore { ) {} loadMessages(): AgentMessage[] { - const rows = this.db.sessionEntries.listActive(this.taskSessionId); + const rows = this.db.projection.sessionEntries.listActive(this.taskSessionId); const result: AgentMessage[] = []; for (const row of rows) { if (row.kind === "message") { @@ -29,7 +29,7 @@ export class SqliteSessionStore { } appendMessage(message: AgentMessage): void { - this.db.sessionEntries.append({ + this.db.projection.sessionEntries.append({ taskSessionId: this.taskSessionId, kind: "message", role: (message as { role?: string }).role ?? null, diff --git a/src/main/api-server/routes-agent-control.ts b/src/main/api-server/routes-agent-control.ts index 694231e6..26778a9d 100644 --- a/src/main/api-server/routes-agent-control.ts +++ b/src/main/api-server/routes-agent-control.ts @@ -12,7 +12,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { const targetAgentId = requireParam(params, "agentId"); const targetAgent = (() => { try { - return db.entityCrud.getAgent(targetAgentId); + return db.core.entity.getAgent(targetAgentId); } catch (err) { log.warn(`[api/agent-control] getAgent failed for ${targetAgentId}:`, err instanceof Error ? err.message : String(err)); return null; @@ -31,7 +31,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { }); const parsed = wakeSchema.safeParse(body); const reason = parsed.success ? parsed.data.reason : "peer_request"; - const alreadyActive = db.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId }); + const alreadyActive = db.core.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId }); const runId = onWakeAgent(targetAgentId, token.companyId, reason); if (runId) { server.json(res, 200, { ok: true, runId, message: `Wake started for agent ${targetAgentId}` }); @@ -52,7 +52,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { const targetAgentId = requireParam(params, "agentId"); let targetAgent; try { - targetAgent = db.entityCrud.getAgent(targetAgentId); + targetAgent = db.core.entity.getAgent(targetAgentId); } catch (err) { log.warn(`[api/agent-control] getAgent failed for heartbeat ${targetAgentId}:`, err instanceof Error ? err.message : String(err)); targetAgent = null; @@ -65,7 +65,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Current agent is not allowed to trigger heartbeat for this agent" }); return; } - const alreadyActive = db.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId }); + const alreadyActive = db.core.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId }); const runId = onWakeAgent(targetAgentId, token.companyId, "peer_request"); if (runId) { server.json(res, 200, { ok: true, runId, message: `Heartbeat triggered for agent ${targetAgentId}` }); @@ -86,7 +86,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { const agentId = requireParam(params, "agentId"); let agent; try { - agent = db.entityCrud.getAgent(agentId); + agent = db.core.entity.getAgent(agentId); } catch (error) { log.warn(`[api/agent-skills:GET] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`); agent = null; @@ -95,9 +95,9 @@ export function registerAgentControlRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Agent not found in your company" }); return; } - const skillIds = db.skills.listAgentSkillAssignments(agentId); + const skillIds = db.core.skills.listAgentSkillAssignments(agentId); const skills = skillIds - .map((sid) => db.skills.getCompanySkill(token.companyId, sid)) + .map((sid) => db.core.skills.getCompanySkill(token.companyId, sid)) .filter((s): s is NonNullable => s != null); server.json(res, 200, skills.map((s) => ({ id: s.id, @@ -118,7 +118,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { } let agent; try { - agent = db.entityCrud.getAgent(agentId); + agent = db.core.entity.getAgent(agentId); } catch (error) { log.warn(`[api/agent-skills:POST] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`); agent = null; @@ -127,12 +127,12 @@ export function registerAgentControlRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Agent not found in your company" }); return; } - const skill = db.skills.getCompanySkill(token.companyId, parsed.data.skillId); + const skill = db.core.skills.getCompanySkill(token.companyId, parsed.data.skillId); if (!skill) { server.json(res, 404, { error: "Skill not found in your company" }); return; } - db.skills.assignSkillToAgent(agentId, parsed.data.skillId); + db.core.skills.assignSkillToAgent(agentId, parsed.data.skillId); onDomainChanged(); server.json(res, 200, { ok: true, message: `Skill "${skill.name}" assigned to agent ${agent.name}` }); }); @@ -142,7 +142,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { const skillId = requireParam(params, "skillId"); let agent; try { - agent = db.entityCrud.getAgent(agentId); + agent = db.core.entity.getAgent(agentId); } catch (error) { log.warn(`[api/agent-skills:DELETE] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`); agent = null; @@ -151,7 +151,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Agent not found in your company" }); return; } - db.skills.removeSkillFromAgent(agentId, skillId); + db.core.skills.removeSkillFromAgent(agentId, skillId); onDomainChanged(); server.json(res, 200, { ok: true, message: `Skill removed from agent ${agent.name}` }); }); diff --git a/src/main/api-server/routes-agents-standup.ts b/src/main/api-server/routes-agents-standup.ts index a6f63914..f209cb0e 100644 --- a/src/main/api-server/routes-agents-standup.ts +++ b/src/main/api-server/routes-agents-standup.ts @@ -3,23 +3,23 @@ import type { AgentApiServer } from "./server-core"; export function registerAgentStandupRoute(server: AgentApiServer): void { const db = server.db; server.addRoute("GET", "/api/agents/me/standup", (_req, res, _params, token) => { - const agent = db.entityCrud.getAgent(token.agentId); + const agent = db.core.entity.getAgent(token.agentId); if (!agent || agent.companyId !== token.companyId) { server.json(res, 404, { error: "Agent not found" }); return; } - const directReports = db.agents.listDirectReportsForStandup(token.companyId, token.agentId); + const directReports = db.core.agents.listDirectReportsForStandup(token.companyId, token.agentId); - const myTasks = db.tasks.listAssigneeTasksForStandup(token.companyId, token.agentId); + const myTasks = db.core.tasks.listAssigneeTasksForStandup(token.companyId, token.agentId); - const reportTasks = db.tasks.listTasksForAssignees( + const reportTasks = db.core.tasks.listTasksForAssignees( token.companyId, directReports.map((r) => r.id), ); const reportStatusRows = directReports.length > 0 - ? db.agents.listReportStatusesForStandup(token.companyId, token.agentId) + ? db.core.agents.listReportStatusesForStandup(token.companyId, token.agentId) : []; const tasksAwaitingMyReview = reportTasks.filter((t) => t.status === "in_review"); @@ -33,20 +33,20 @@ export function registerAgentStandupRoute(server: AgentApiServer): void { ...reportTasks.map((t) => t.id), ]); const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(); - const recentComments = db.activityMod.listRecentTaskComments([...allRelevantTaskIds], twoHoursAgo, 20); + const recentComments = db.core.activity.listRecentTaskComments([...allRelevantTaskIds], twoHoursAgo, 20); - const myPendingApprovals = db.approvalsMod.listAgentPendingApprovals(token.companyId, token.agentId); - const myResolvedApprovals = db.approvalsMod.listAgentResolvedApprovals(token.companyId, token.agentId, 5); + const myPendingApprovals = db.core.approvals.listAgentPendingApprovals(token.companyId, token.agentId); + const myResolvedApprovals = db.core.approvals.listAgentResolvedApprovals(token.companyId, token.agentId, 5); // Unread inbox lives in the standup briefing as the agent's // canonical view of pending communication. Pre-fix the agent had // to make a second API call to /messages?unreadOnly=true that the // model frequently skipped, so messages from peers and managers // sat dark while the agent posted heartbeat status comments. - const unreadInbox = db.messages.listAgentInboxUnread(token.companyId, token.agentId); + const unreadInbox = db.core.messages.listAgentInboxUnread(token.companyId, token.agentId); const senderIds = new Set(unreadInbox.map((m) => m.fromAgentId)); const senderNames = senderIds.size > 0 - ? new Map(db.agents.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name])) + ? new Map(db.core.agents.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name])) : new Map(); // Recent outputs the AGENT itself produced — closes the @@ -59,10 +59,10 @@ export function registerAgentStandupRoute(server: AgentApiServer): void { // surface keeps the briefing readable. const RECENT_OUTPUT_LIMIT = 20; const myTaskIds = new Set(myTasks.map((t) => t.id)); - const recentDocuments = db.documentsMod.listDocuments(token.companyId) + const recentDocuments = db.core.documents.listDocuments(token.companyId) .filter((doc) => doc.authorAgentId === token.agentId) .slice(0, RECENT_OUTPUT_LIMIT); - const recentWorkProducts = db.documentsMod.listWorkProductsForCompany(token.companyId) + const recentWorkProducts = db.core.documents.listWorkProductsForCompany(token.companyId) .filter((wp) => wp.taskId !== null && myTaskIds.has(wp.taskId)) .slice(0, RECENT_OUTPUT_LIMIT); @@ -71,7 +71,7 @@ export function registerAgentStandupRoute(server: AgentApiServer): void { ...myTasks.map<[string, string]>((t) => [t.id, t.title]), ...reportTasks.map<[string, string]>((t) => [t.id, t.title]), ]); - const socialAccounts = db.social.listSocialAccounts(token.companyId); + const socialAccounts = db.core.social.listSocialAccounts(token.companyId); server.json(res, 200, { agent: { id: agent.id, name: agent.name, role: agent.role, status: agent.status }, diff --git a/src/main/api-server/routes-agents.ts b/src/main/api-server/routes-agents.ts index bd58def4..79d6ecef 100644 --- a/src/main/api-server/routes-agents.ts +++ b/src/main/api-server/routes-agents.ts @@ -24,9 +24,9 @@ export function registerAgentRoutes(server: AgentApiServer) { const callbacks = server.callbacks; const onDomainChanged = server.onDomainChanged; server.addRoute("GET", "/api/agents/me", (_req, res, _params, token) => { - const agent = db.entityCrud.getAgent(token.agentId); - const chainOfCommand = db.agents.getChainOfCommand(token.agentId); - const directReports = db.agents.getDirectReports(token.agentId, token.companyId); + const agent = db.core.entity.getAgent(token.agentId); + const chainOfCommand = db.core.agents.getChainOfCommand(token.agentId); + const directReports = db.core.agents.getDirectReports(token.agentId, token.companyId); server.json(res, 200, { agent, chainOfCommand, directReports }); }); @@ -36,7 +36,7 @@ export function registerAgentRoutes(server: AgentApiServer) { // and the standup module is registered separately in api-server/index.ts. server.addRoute("GET", "/api/agents/me/metrics", (_req, res, _params, token) => { - const companyMetrics = db.company.getCompanyMetrics(token.companyId, 30); + const companyMetrics = db.core.company.getCompanyMetrics(token.companyId, 30); const agentMetric = companyMetrics.agentMetrics.find(m => m.agentId === token.agentId); if (!agentMetric) { server.json(res, 404, { error: "Metrics not found" }); @@ -47,12 +47,12 @@ export function registerAgentRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/agents/:agentId/metrics", (_req, res, params, token) => { const agentId = requireParam(params, "agentId"); - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (!agent || agent.companyId !== token.companyId) { server.json(res, 404, { error: "Agent not found" }); return; } - const companyMetrics = db.company.getCompanyMetrics(token.companyId, 30); + const companyMetrics = db.core.company.getCompanyMetrics(token.companyId, 30); const agentMetric = companyMetrics.agentMetrics.find((metric) => metric.agentId === agentId); if (!agentMetric) { server.json(res, 404, { error: "Metrics not found" }); @@ -64,7 +64,7 @@ export function registerAgentRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/agents/:agentId", (_req, res, params, token) => { let agent; try { - agent = db.entityCrud.getAgent(requireParam(params, "agentId")); + agent = db.core.entity.getAgent(requireParam(params, "agentId")); } catch (err) { log.warn(`[api/agents] getAgent failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Agent not found" }); @@ -74,12 +74,12 @@ export function registerAgentRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Agent not found" }); return; } - const directReports = db.agents.getDirectReports(agent.id, token.companyId); + const directReports = db.core.agents.getDirectReports(agent.id, token.companyId); const reportsTo = agent.reportsTo; const manager = reportsTo ? (() => { try { - return db.entityCrud.getAgent(reportsTo); + return db.core.entity.getAgent(reportsTo); } catch (error) { log.warn(`[api/agents] manager lookup failed for ${reportsTo}: ${error instanceof Error ? error.message : String(error)}`); return null; @@ -98,7 +98,7 @@ export function registerAgentRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const agents = db.agents.listAgentsForApi(params.companyId); + const agents = db.core.agents.listAgentsForApi(params.companyId); server.json(res, 200, agents); }); @@ -183,14 +183,14 @@ export function registerAgentRoutes(server: AgentApiServer) { resolvedReportsTo && resolvedReportsTo !== token.agentId && !canManageOrganization(db, companyId, token.agentId) && - !db.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId) + !db.core.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId) ) { server.json(res, 403, { error: "Requested hire must report to you or a manager in your reporting chain." }); return; } const connectorRecord = (() => { try { - return db.connectorsCrud.getConnector(input.connectorId); + return db.core.connectors.getConnector(input.connectorId); } catch (err) { log.warn(`[api/agents] getConnector failed for ${input.connectorId}:`, err instanceof Error ? err.message : String(err)); return null; @@ -207,7 +207,7 @@ export function registerAgentRoutes(server: AgentApiServer) { } // Prevent duplicate agents: reject if a non-terminated agent with the same name already exists - const existingAgents = db.agents.listExistingAgentsForHireCheck(companyId); + const existingAgents = db.core.agents.listExistingAgentsForHireCheck(companyId); const duplicateByName = existingAgents.find( (a) => a.name.toLowerCase() === input.name.toLowerCase(), ); @@ -216,7 +216,7 @@ export function registerAgentRoutes(server: AgentApiServer) { return; } // Also check pending hire approvals with the same name to prevent concurrent duplicate requests - const pendingHires = db.approvalsMod.listPendingHireApprovalPayloads(companyId); + const pendingHires = db.core.approvals.listPendingHireApprovalPayloads(companyId); for (const pending of pendingHires) { try { const payload = pending.payloadJson ? JSON.parse(pending.payloadJson) as Record : {}; @@ -234,7 +234,7 @@ export function registerAgentRoutes(server: AgentApiServer) { const managerLabel = resolvedReportsTo ? (() => { try { - return db.entityCrud.getAgent(resolvedReportsTo).name; + return db.core.entity.getAgent(resolvedReportsTo).name; } catch (err) { log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err)); return resolvedReportsTo; @@ -283,7 +283,7 @@ export function registerAgentRoutes(server: AgentApiServer) { return; } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "agent.hire_requested", diff --git a/src/main/api-server/routes-approvals.ts b/src/main/api-server/routes-approvals.ts index ab541de5..e884f3fa 100644 --- a/src/main/api-server/routes-approvals.ts +++ b/src/main/api-server/routes-approvals.ts @@ -74,7 +74,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { // Reject the request at the API boundary so the model cannot // circumvent the ordering by resubmitting. if (input.type === "approve_ceo_strategy") { - const companyHireApprovals = db.approvalsMod.listCompanyApprovalsByType(companyId, "hire_agent"); + const companyHireApprovals = db.core.approvals.listCompanyApprovalsByType(companyId, "hire_agent"); if (companyHireApprovals.length === 0) { server.json(res, 409, { error: "approve_ceo_strategy requires at least one hire_agent approval to exist first. Submit the intended hires, then file the strategy.", @@ -82,7 +82,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { }); return; } - const existingStrategy = db.approvalsMod.listCompanyApprovalsByType(companyId, "approve_ceo_strategy") + const existingStrategy = db.core.approvals.listCompanyApprovalsByType(companyId, "approve_ceo_strategy") .find((a) => a.state === "pending" || a.state === "approved"); if (existingStrategy) { server.json(res, 409, { @@ -117,7 +117,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { let id: string; try { - id = db.approvalsMod.requestApproval({ + id = db.core.approvals.requestApproval({ companyId, type: input.type, relatedTaskId: input.relatedTaskId ?? null, @@ -132,7 +132,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { return; } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "approval.created", @@ -186,7 +186,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { return; } const statuses = parsedQuery.data.status?.split(",").filter(Boolean) ?? null; - const approvals = db.approvalsMod.listApprovalsForApi(companyId, { + const approvals = db.core.approvals.listApprovalsForApi(companyId, { requestedByAgentId: parsedQuery.data.requestedByAgentId, relatedTaskId: parsedQuery.data.relatedTaskId, statuses, @@ -195,7 +195,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { }); server.addRoute("GET", "/api/approvals/:approvalId", (_req, res, params, token) => { - const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId")); + const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId")); if (!approval || approval.companyId !== token.companyId) { server.json(res, 404, { error: "Approval not found" }); return; @@ -209,28 +209,28 @@ export function registerApprovalRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/approvals/:approvalId/comments", (_req, res, params, token) => { const approvalId = requireParam(params, "approvalId"); - const approval = db.approvalsMod.findApprovalById(approvalId); + const approval = db.core.approvals.findApprovalById(approvalId); if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; } if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } - const comments = db.approvalsMod.listApprovalComments(approvalId, approval.companyId); + const comments = db.core.approvals.listApprovalComments(approvalId, approval.companyId); server.json(res, 200, comments); }); server.addRoute("POST", "/api/approvals/:approvalId/comments", (_req, res, params, token, body) => { - const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId")); + const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId")); if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; } if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } const data = body as Record; const commentBody = typeof data.body === "string" ? data.body.trim() : ""; if (!commentBody) { server.json(res, 400, { error: "Comment body is required" }); return; } - const commentId = db.approvalsMod.addApprovalComment({ + const commentId = db.core.approvals.addApprovalComment({ companyId: approval.companyId, approvalId: approval.id, authorAgentId: token.agentId ?? null, body: commentBody, }); // Wake mentioned agents in comment - const mentionedIds = db.agents.findMentionedAgentIds(approval.companyId, commentBody); + const mentionedIds = db.core.agents.findMentionedAgentIds(approval.companyId, commentBody); for (const mentionedId of mentionedIds) { if (mentionedId !== token.agentId) { onWakeAgent(mentionedId, approval.companyId, "approval_comment_mentioned"); @@ -241,7 +241,7 @@ export function registerApprovalRoutes(server: AgentApiServer) { }); server.addRoute("POST", "/api/approvals/:approvalId/resubmit", (_req, res, params, token) => { - const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId")); + const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId")); if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; } if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } if (approval.state !== "revision_requested") { @@ -252,34 +252,34 @@ export function registerApprovalRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Only the requesting agent can resubmit this approval" }); return; } - db.approvalsMod.resubmitApproval(approval.id, approval.companyId); + db.core.approvals.resubmitApproval(approval.id, approval.companyId); onDomainChanged(); server.json(res, 200, { resubmitted: true }); }); server.addRoute("GET", "/api/approvals/:approvalId/tasks", (_req, res, params, token) => { const approvalId = requireParam(params, "approvalId"); - const approval = db.approvalsMod.findApprovalById(approvalId); + const approval = db.core.approvals.findApprovalById(approvalId); if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; } if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } - const taskIds = db.approvalsMod.listTasksForApproval(approvalId); + const taskIds = db.core.approvals.listTasksForApproval(approvalId); server.json(res, 200, taskIds); }); server.addRoute("POST", "/api/approvals/:approvalId/tasks", (_req, res, params, token, body) => { const approvalId = requireParam(params, "approvalId"); - const approval = db.approvalsMod.findApprovalById(approvalId); + const approval = db.core.approvals.findApprovalById(approvalId); if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; } if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } const data = body as Record; const taskIds = Array.isArray(data.taskIds) ? data.taskIds.filter((id): id is string => typeof id === "string") : []; if (taskIds.length === 0) { server.json(res, 400, { error: "taskIds array is required" }); return; } for (const taskId of taskIds) { - if (!db.settings.belongsToCompany("tasks", taskId, token.companyId)) { + if (!db.core.settings.belongsToCompany("tasks", taskId, token.companyId)) { server.json(res, 400, { error: `Task ${taskId} does not belong to this company` }); return; } } - db.approvalsMod.linkManyTasksToApproval(approvalId, taskIds, token.companyId, token.agentId ?? null); + db.core.approvals.linkManyTasksToApproval(approvalId, taskIds, token.companyId, token.agentId ?? null); onDomainChanged(); server.json(res, 200, { linked: taskIds.length }); }); diff --git a/src/main/api-server/routes-files.ts b/src/main/api-server/routes-files.ts index 64389f40..eb3e5738 100644 --- a/src/main/api-server/routes-files.ts +++ b/src/main/api-server/routes-files.ts @@ -71,15 +71,15 @@ export function registerFileRoutes(server: AgentApiServer) { const reviewerAgentId = data.reviewerAgentId ?? null; const projectId = data.projectId ?? null; const goalId = data.goalId ?? null; - if (reviewerAgentId && !db.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) { + if (reviewerAgentId && !db.core.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) { server.json(res, 400, { error: "Reviewer agent does not belong to the current company" }); return; } - if (projectId && !db.settings.belongsToCompany("projects", projectId, token.companyId)) { + if (projectId && !db.core.settings.belongsToCompany("projects", projectId, token.companyId)) { server.json(res, 400, { error: "Project does not belong to the current company" }); return; } - if (goalId && !db.settings.belongsToCompany("goals", goalId, token.companyId)) { + if (goalId && !db.core.settings.belongsToCompany("goals", goalId, token.companyId)) { server.json(res, 400, { error: "Goal does not belong to the current company" }); return; } @@ -114,7 +114,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const docs = db.documentsMod.listDocuments(token.companyId); + const docs = db.core.documents.listDocuments(token.companyId); server.json(res, 200, docs); }); @@ -128,7 +128,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.json(res, 400, { error: "Missing documentId" }); return; } - const doc = db.documentsMod.getDocumentForUpdate(documentId, token.companyId); + const doc = db.core.documents.getDocumentForUpdate(documentId, token.companyId); if (!doc) { server.json(res, 404, { error: "Document not found in this company" }); return; @@ -146,7 +146,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.json(res, 400, { error: "Missing documentId" }); return; } - const doc = db.documentsMod.getDocumentForUpdate(documentId, token.companyId); + const doc = db.core.documents.getDocumentForUpdate(documentId, token.companyId); if (!doc) { server.json(res, 404, { error: "Document not found in this company" }); return; @@ -158,14 +158,14 @@ export function registerFileRoutes(server: AgentApiServer) { const reviewerAgentId = data.reviewerAgentId !== undefined ? (data.reviewerAgentId ?? null) : doc.reviewerAgentId; - if (data.reviewerAgentId !== undefined && reviewerAgentId && !db.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) { + if (data.reviewerAgentId !== undefined && reviewerAgentId && !db.core.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) { server.json(res, 400, { error: "Reviewer agent does not belong to the current company" }); return; } // Conflict detection: if baseRevisionId provided, verify it matches current if (data.baseRevisionId !== undefined) { - const currentRevId = db.documentsMod.getDocumentLatestRevisionId(documentId); + const currentRevId = db.core.documents.getDocumentLatestRevisionId(documentId); if (currentRevId && data.baseRevisionId !== currentRevId) { server.json(res, 409, { error: "Document was updated by someone else", currentRevisionId: currentRevId }); return; @@ -182,7 +182,7 @@ export function registerFileRoutes(server: AgentApiServer) { // Content update → create revision for audit trail let updatedVersion = doc.version; if (data.content !== undefined) { - const revision = db.documentsMod.createDocumentRevision({ + const revision = db.core.documents.createDocumentRevision({ documentId, companyId: token.companyId, body: data.content, @@ -194,9 +194,9 @@ export function registerFileRoutes(server: AgentApiServer) { // Field updates (title, reviewer, tags, etc.) if (data.status !== undefined) { - db.documentsMod.updateDocumentStatus(documentId, token.companyId, data.status); + db.core.documents.updateDocumentStatus(documentId, token.companyId, data.status); } - db.documentsMod.saveDocument({ + db.core.documents.saveDocument({ id: documentId, companyId: token.companyId, type: data.type ?? doc.type, @@ -213,14 +213,14 @@ export function registerFileRoutes(server: AgentApiServer) { }); onDomainChanged(); - server.json(res, 200, { updated: true, latestRevisionId: db.documentsMod.getDocumentLatestRevisionId(documentId) }); + server.json(res, 200, { updated: true, latestRevisionId: db.core.documents.getDocumentLatestRevisionId(documentId) }); }); server.addRoute("GET", "/api/companies/:companyId/documents/:documentId/revisions", (_req, res, params, token) => { if (params.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; } const documentId = params.documentId; if (!documentId) { server.json(res, 400, { error: "Missing documentId" }); return; } - const revisions = db.documentsMod.listDocumentRevisions(documentId, token.companyId); + const revisions = db.core.documents.listDocumentRevisions(documentId, token.companyId); server.json(res, 200, revisions); }); @@ -228,9 +228,9 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/issues/:issueId/work-products", (_req, res, params, token) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } - const products = db.documentsMod.listWorkProductsForTask(task.id, task.companyId); + const products = db.core.documents.listWorkProductsForTask(task.id, task.companyId); server.json(res, 200, products); } catch (error) { log.warn(`[api/work-products] list failed for issue ${params.issueId}: ${error instanceof Error ? error.message : String(error)}`); @@ -240,12 +240,12 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("POST", "/api/issues/:issueId/work-products", (_req, res, params, token, body) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } const parsed = createWorkProductSchema.safeParse(body); if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; } const data = parsed.data; - const id = db.documentsMod.createWorkProduct({ + const id = db.core.documents.createWorkProduct({ companyId: task.companyId, taskId: task.id, projectId: data.projectId ?? task.projectId, @@ -275,7 +275,7 @@ export function registerFileRoutes(server: AgentApiServer) { if (!wpId) { server.json(res, 400, { error: "Missing workProductId" }); return; } const parsed = updateWorkProductSchema.safeParse(body); if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; } - const changes = db.documentsMod.updateWorkProduct(wpId, token.companyId, parsed.data); + const changes = db.core.documents.updateWorkProduct(wpId, token.companyId, parsed.data); if (changes === 0) { server.json(res, 404, { error: "Work product not found" }); return; } onDomainChanged(); server.json(res, 200, { updated: true }); @@ -284,7 +284,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("DELETE", "/api/work-products/:workProductId", (_req, res, params, token) => { const wpId = params.workProductId; if (!wpId) { server.json(res, 400, { error: "Missing workProductId" }); return; } - const changes = db.documentsMod.deleteWorkProduct(wpId, token.companyId); + const changes = db.core.documents.deleteWorkProduct(wpId, token.companyId); if (changes === 0) { server.json(res, 404, { error: "Work product not found" }); return; } onDomainChanged(); server.json(res, 200, { deleted: true }); @@ -294,9 +294,9 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/issues/:issueId/documents", (_req, res, params, token) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } - const links = db.documentsMod.listDocumentsForTask(task.id, task.companyId); + const links = db.core.documents.listDocumentsForTask(task.id, task.companyId); server.json(res, 200, links); } catch (error) { log.warn(`[api/task-documents] list failed for issue ${params.issueId}: ${error instanceof Error ? error.message : String(error)}`); @@ -306,7 +306,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("PUT", "/api/issues/:issueId/documents/:key", (_req, res, params, token, body) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } const key = String(params.key ?? "").trim().toLowerCase(); if (!key || !/^[a-z0-9][a-z0-9_-]*$/.test(key) || key.length > 64) { @@ -315,10 +315,10 @@ export function registerFileRoutes(server: AgentApiServer) { const parsed = linkDocumentSchema.safeParse(body); if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; } const documentId = parsed.data.documentId; - if (!db.settings.belongsToCompany("documents", documentId, token.companyId)) { + if (!db.core.settings.belongsToCompany("documents", documentId, token.companyId)) { server.json(res, 400, { error: "Document does not belong to this company" }); return; } - const linkId = db.documentsMod.linkDocumentToTask(task.id, documentId, task.companyId, key); + const linkId = db.core.documents.linkDocumentToTask(task.id, documentId, task.companyId, key); onDomainChanged(); server.json(res, 200, { id: linkId, key }); } catch (error) { @@ -329,10 +329,10 @@ export function registerFileRoutes(server: AgentApiServer) { server.addRoute("DELETE", "/api/issues/:issueId/documents/:key", (_req, res, params, token) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } const key = String(params.key ?? "").trim().toLowerCase(); - db.documentsMod.unlinkDocumentFromTask(task.id, key, task.companyId); + db.core.documents.unlinkDocumentFromTask(task.id, key, task.companyId); onDomainChanged(); server.json(res, 200, { unlinked: true }); } catch (error) { diff --git a/src/main/api-server/routes-meetings.ts b/src/main/api-server/routes-meetings.ts index 1527512c..62b96430 100644 --- a/src/main/api-server/routes-meetings.ts +++ b/src/main/api-server/routes-meetings.ts @@ -33,7 +33,7 @@ export function registerMeetingRoutes(server: AgentApiServer) { try { const parsedParticipants = JSON.parse(input.participantAgentIds) as string[]; for (const agentId of parsedParticipants) { - if (typeof agentId !== "string" || !db.settings.belongsToCompany("agents", agentId, token.companyId)) { + if (typeof agentId !== "string" || !db.core.settings.belongsToCompany("agents", agentId, token.companyId)) { server.json(res, 400, { error: "Meeting participants must belong to the current company" }); return; } @@ -43,7 +43,7 @@ export function registerMeetingRoutes(server: AgentApiServer) { server.json(res, 400, { error: "participantAgentIds must be a JSON array of agent ids" }); return; } - const id = db.knowledgeMod.saveMeeting({ + const id = db.core.knowledge.saveMeeting({ companyId: token.companyId, type: input.type, title: input.title, @@ -63,13 +63,13 @@ export function registerMeetingRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const meetings = db.knowledgeMod.listMeetings(token.companyId); + const meetings = db.core.knowledge.listMeetings(token.companyId); server.json(res, 200, meetings); }); server.addRoute("PATCH", "/api/meetings/:meetingId", (_req, res, params, token, body) => { const meetingId = requireParam(params, "meetingId"); - const existing = db.knowledgeMod.getMeeting(meetingId, token.companyId); + const existing = db.core.knowledge.getMeeting(meetingId, token.companyId); if (!existing) { server.json(res, 404, { error: "Meeting not found" }); return; @@ -102,13 +102,13 @@ export function registerMeetingRoutes(server: AgentApiServer) { return; } for (const item of actionItems) { - if (item.assigneeAgentId && !db.settings.belongsToCompany("agents", item.assigneeAgentId, token.companyId)) { + if (item.assigneeAgentId && !db.core.settings.belongsToCompany("agents", item.assigneeAgentId, token.companyId)) { server.json(res, 400, { error: "Meeting action item assignees must belong to the current company" }); return; } } } - db.knowledgeMod.saveMeeting({ + db.core.knowledge.saveMeeting({ id: meetingId, companyId: token.companyId, type: data.type ?? "department_sync", @@ -133,7 +133,7 @@ export function registerMeetingRoutes(server: AgentApiServer) { ); continue; } - db.tasks.saveTask({ + db.core.tasks.saveTask({ companyId: token.companyId, title: item.title, description: item.description ?? `Action item from meeting: ${data.title ?? existing.title}`, diff --git a/src/main/api-server/routes-messages.ts b/src/main/api-server/routes-messages.ts index af00dce2..c0b9239f 100644 --- a/src/main/api-server/routes-messages.ts +++ b/src/main/api-server/routes-messages.ts @@ -20,13 +20,13 @@ export function registerMessageRoutes(server: AgentApiServer) { return; } try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; } - const agent = db.entityCrud.getAgent(token.agentId); - const comment = db.activityMod.addComment({ + const agent = db.core.entity.getAgent(token.agentId); + const comment = db.core.activity.addComment({ companyId: task.companyId, taskId: task.id, authorAgentId: token.agentId, @@ -44,14 +44,14 @@ export function registerMessageRoutes(server: AgentApiServer) { wokenForComment.add(task.assigneeAgentId); } - const chain = db.agents.getChainOfCommand(token.agentId); + const chain = db.core.agents.getChainOfCommand(token.agentId); if (chain.length > 0 && chain[0].id !== token.agentId && !wokenForComment.has(chain[0].id)) { onWakeAgent(chain[0].id, task.companyId, "comment"); wokenForComment.add(chain[0].id); } // @mention detection: wake any mentioned agents not already woken - const mentionedIds = db.agents.findMentionedAgentIds(task.companyId, input.body); + const mentionedIds = db.core.agents.findMentionedAgentIds(task.companyId, input.body); for (const mentionedId of mentionedIds) { if (mentionedId !== token.agentId && !wokenForComment.has(mentionedId)) { onWakeAgent(mentionedId, task.companyId, "comment_mentioned"); @@ -68,7 +68,7 @@ export function registerMessageRoutes(server: AgentApiServer) { server.addRoute("GET", "/api/issues/:issueId/comments", (_req, res, params, token) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; @@ -78,7 +78,7 @@ export function registerMessageRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Task not found" }); return; } - const comments = db.activityMod.listComments(requireParam(params, "issueId")); + const comments = db.core.activity.listComments(requireParam(params, "issueId")); server.json(res, 200, comments); }); @@ -142,7 +142,7 @@ export function registerMessageRoutes(server: AgentApiServer) { return; } - const messages = db.messages.listAgentMessages(companyId, { + const messages = db.core.messages.listAgentMessages(companyId, { agentId: token.agentId, channel: channelParam ?? undefined, channelTargetId: channelTargetId && channelTargetId.length > 0 ? channelTargetId : undefined, @@ -158,12 +158,12 @@ export function registerMessageRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const message = db.messages.getAgentMessage(requireParam(params, "messageId"), companyId); + const message = db.core.messages.getAgentMessage(requireParam(params, "messageId"), companyId); if (!message) { server.json(res, 404, { error: "Message not found" }); return; } - const visibleToAgent = db.messages.listAgentMessages(companyId, { + const visibleToAgent = db.core.messages.listAgentMessages(companyId, { agentId: token.agentId, messageId: message.id, limit: 1, @@ -172,7 +172,7 @@ export function registerMessageRoutes(server: AgentApiServer) { server.json(res, 403, { error: "This message is not visible to the current agent" }); return; } - db.messages.markMessageReadForReader(requireParam(params, "messageId"), companyId, token.agentId); + db.core.messages.markMessageReadForReader(requireParam(params, "messageId"), companyId, token.agentId); onDomainChanged(); server.json(res, 200, { ok: true }); }); diff --git a/src/main/api-server/routes-misc.ts b/src/main/api-server/routes-misc.ts index 59b3672f..c021a784 100644 --- a/src/main/api-server/routes-misc.ts +++ b/src/main/api-server/routes-misc.ts @@ -34,7 +34,7 @@ export function registerMiscRoutes(server: AgentApiServer) { const input = parsed.data; - const account = db.social.getSocialAccount(input.socialAccountId, companyId); + const account = db.core.social.getSocialAccount(input.socialAccountId, companyId); if (!account) { server.json(res, 404, { error: "Social account not found" }); return; @@ -42,7 +42,7 @@ export function registerMiscRoutes(server: AgentApiServer) { const postActions = ["post", "reply", "dm"]; if (account.requireApproval && postActions.includes(input.actionType)) { - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, type: "dangerous_command", relatedTaskId: null, @@ -51,7 +51,7 @@ export function registerMiscRoutes(server: AgentApiServer) { impactSummary: `Will ${input.actionType} on ${account.displayName || account.accountName}'s ${account.platform} account. Content will be publicly visible.`, }); - const actionId = db.social.saveBrowserAction({ + const actionId = db.core.social.saveBrowserAction({ companyId, socialAccountId: input.socialAccountId, agentId: token.agentId, @@ -61,7 +61,7 @@ export function registerMiscRoutes(server: AgentApiServer) { approvalId, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "browser.action_pending_approval", @@ -75,7 +75,7 @@ export function registerMiscRoutes(server: AgentApiServer) { return; } - const actionId = db.social.saveBrowserAction({ + const actionId = db.core.social.saveBrowserAction({ companyId, socialAccountId: input.socialAccountId, agentId: token.agentId, @@ -84,7 +84,7 @@ export function registerMiscRoutes(server: AgentApiServer) { status: "queued", }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "browser.action_queued", @@ -108,7 +108,7 @@ export function registerMiscRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const accounts = db.social.listSocialAccounts(requireParam(params, "companyId")); + const accounts = db.core.social.listSocialAccounts(requireParam(params, "companyId")); const sanitized = accounts.map(a => ({ id: a.id, platform: a.platform, @@ -134,7 +134,7 @@ export function registerMiscRoutes(server: AgentApiServer) { return; } - const account = db.social.getSocialAccount(parsed.data.socialAccountId, companyId); + const account = db.core.social.getSocialAccount(parsed.data.socialAccountId, companyId); if (!account) { server.json(res, 404, { error: "Social account not found" }); return; @@ -142,7 +142,7 @@ export function registerMiscRoutes(server: AgentApiServer) { const contentPreview = parsed.data.content.slice(0, 120) + (parsed.data.content.length > 120 ? "..." : ""); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, requestedByAgentId: token.agentId, type: "social_post", @@ -171,7 +171,7 @@ export function registerMiscRoutes(server: AgentApiServer) { const status = url.searchParams.get("status") ?? undefined; const limit = Number(url.searchParams.get("limit") || 50); - const actions = db.social.listBrowserActions(requireParam(params, "companyId"), { socialAccountId, status, limit }); + const actions = db.core.social.listBrowserActions(requireParam(params, "companyId"), { socialAccountId, status, limit }); server.json(res, 200, actions); }); @@ -198,7 +198,7 @@ export function registerMiscRoutes(server: AgentApiServer) { } const input = parsed.data; - const id = db.knowledgeMod.saveKnowledgeEntry({ + const id = db.core.knowledge.saveKnowledgeEntry({ companyId: token.companyId, category: input.category, topic: input.topic, @@ -219,7 +219,7 @@ export function registerMiscRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const entries = db.knowledgeMod.listKnowledgeEntries(token.companyId); + const entries = db.core.knowledge.listKnowledgeEntries(token.companyId); server.json(res, 200, entries); }); @@ -231,7 +231,7 @@ export function registerMiscRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const skills = db.skills.listCompanySkills(companyId); + const skills = db.core.skills.listCompanySkills(companyId); server.json(res, 200, skills.map((s) => ({ id: s.id, name: s.name, @@ -244,7 +244,7 @@ export function registerMiscRoutes(server: AgentApiServer) { }); server.addRoute("GET", "/api/skills/:skillId", (_req, res, params, token) => { - const skill = db.skills.getCompanySkill(token.companyId, requireParam(params, "skillId")); + const skill = db.core.skills.getCompanySkill(token.companyId, requireParam(params, "skillId")); if (!skill) { server.json(res, 404, { error: "Skill not found" }); return; diff --git a/src/main/api-server/routes-runs.ts b/src/main/api-server/routes-runs.ts index c13eb6e1..12149b34 100644 --- a/src/main/api-server/routes-runs.ts +++ b/src/main/api-server/routes-runs.ts @@ -16,7 +16,7 @@ function parsePositiveInt(value: string | null, fallback: number) { } function resolveAuthorizedRun(server: AgentApiServer, token: TokenPayload, runId: string) { - const runState = server.db.runs.getRunAuthState(runId); + const runState = server.db.core.runs.getRunAuthState(runId); if (!runState || runState.companyId !== token.companyId || runState.agentId !== token.agentId) { return null; } @@ -40,7 +40,7 @@ export function registerRunRoutes(server: AgentApiServer) { const url = new URL(req.url ?? "/", `http://127.0.0.1:${server.port}`); const offset = parsePositiveInt(url.searchParams.get("offset"), 0); const limit = Math.min(parsePositiveInt(url.searchParams.get("limit"), DEFAULT_EVENT_LIMIT), MAX_EVENT_LIMIT); - const events = server.db.runs.listRunEvents(runId, token.companyId, { offset, limit }); + const events = server.db.core.runs.listRunEvents(runId, token.companyId, { offset, limit }); server.json(res, 200, { events, nextOffset: offset + events.length }); }); @@ -66,7 +66,7 @@ export function registerRunRoutes(server: AgentApiServer) { const flushPendingEvents = () => { while (true) { - const events = server.db.runs.listRunEventsAfter(runId, token.companyId, cursor, SSE_BATCH_LIMIT); + const events = server.db.core.runs.listRunEventsAfter(runId, token.companyId, cursor, SSE_BATCH_LIMIT); if (events.length === 0) return; for (const event of events) { writeSse(res, "run-event", event); diff --git a/src/main/api-server/routes-tasks.ts b/src/main/api-server/routes-tasks.ts index a43f1f00..946f22a9 100644 --- a/src/main/api-server/routes-tasks.ts +++ b/src/main/api-server/routes-tasks.ts @@ -39,17 +39,17 @@ export function registerTaskRoutes(server: AgentApiServer) { const statuses = statusFilter ? statusFilter.split(",") : ["todo", "in_progress", "blocked"]; if (assigneeAgentId) { - const tasks = db.tasks.listAgentTasks(assigneeAgentId, companyId, statuses); + const tasks = db.core.tasks.listAgentTasks(assigneeAgentId, companyId, statuses); server.json(res, 200, tasks); } else { - const tasks = db.tasks.listTasksByStatuses(companyId, statuses); + const tasks = db.core.tasks.listTasksByStatuses(companyId, statuses); server.json(res, 200, tasks); } }); server.addRoute("GET", "/api/issues/:issueId", (_req, res, params, token) => { try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; @@ -74,7 +74,7 @@ export function registerTaskRoutes(server: AgentApiServer) { } let task; try { - task = db.entityCrud.getTask(requireParam(params, "issueId")); + task = db.core.entity.getTask(requireParam(params, "issueId")); } catch (err) { log.warn(`[api/tasks] getTask failed for checkout:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Task not found" }); @@ -89,7 +89,7 @@ export function registerTaskRoutes(server: AgentApiServer) { return; } const previousStatus = task.status; - const success = db.tasks.checkoutTask(task.id, token.runId); + const success = db.core.tasks.checkoutTask(task.id, token.runId); if (!success) { server.json(res, 409, { error: "Task cannot be checked out — already assigned or not in valid status" }); return; @@ -109,7 +109,7 @@ export function registerTaskRoutes(server: AgentApiServer) { const update = parsedUpdate.data; try { - const task = db.entityCrud.getTask(requireParam(params, "issueId")); + const task = db.core.entity.getTask(requireParam(params, "issueId")); if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; @@ -133,10 +133,10 @@ export function registerTaskRoutes(server: AgentApiServer) { return; } - const actorAgent = db.entityCrud.getAgent(token.agentId); + const actorAgent = db.core.entity.getAgent(token.agentId); let reviewRequest: { reviewerAgentName: string; documents: Array<{ id: string; title: string } & Record> } | null = null; if (effectiveStatus === "done" && task.requiresUserReview) { - const docs = db.documentsMod.listImportantDocsForReview({ + const docs = db.core.documents.listImportantDocsForReview({ companyId: task.companyId, types: IMPORTANT_DOC_TYPES, authorAgentId: task.assigneeAgentId ?? null, @@ -146,7 +146,7 @@ export function registerTaskRoutes(server: AgentApiServer) { reviewRequest = { reviewerAgentName: actorAgent.name, documents: docs }; } - const updateResult = db.tasks.updateTaskWithReviewSideEffects({ + const updateResult = db.core.tasks.updateTaskWithReviewSideEffects({ taskId: task.id, companyId: task.companyId, actorAgentId: token.agentId, @@ -183,7 +183,7 @@ export function registerTaskRoutes(server: AgentApiServer) { if (update.status && ["done", "in_review"].includes(newStatus)) { if (updateResult.parentId) { try { - const parentTask = db.entityCrud.getTask(updateResult.parentId); + const parentTask = db.core.entity.getTask(updateResult.parentId); if (parentTask.assigneeAgentId && parentTask.assigneeAgentId !== token.agentId) { onWakeAgent(parentTask.assigneeAgentId, task.companyId, "subtask_completed"); } @@ -197,7 +197,7 @@ export function registerTaskRoutes(server: AgentApiServer) { // This is not a subtask — use a distinct wake reason so the // reviewer prompt does not misframe the work as "a subtask // you delegated". - const chain = db.agents.getChainOfCommand(token.agentId); + const chain = db.core.agents.getChainOfCommand(token.agentId); if (chain.length > 0) { onWakeAgent(chain[0].id, task.companyId, "task_review_requested"); } @@ -205,7 +205,7 @@ export function registerTaskRoutes(server: AgentApiServer) { } if (update.status && newStatus === "blocked") { - const chain = db.agents.getChainOfCommand(token.agentId); + const chain = db.core.agents.getChainOfCommand(token.agentId); if (chain.length > 0) { onWakeAgent(chain[0].id, task.companyId, "report_blocked"); } @@ -225,7 +225,7 @@ export function registerTaskRoutes(server: AgentApiServer) { onWakeAgent(task.assigneeAgentId, task.companyId, "comment"); wokenForComment.add(task.assigneeAgentId); } - const chain = db.agents.getChainOfCommand(token.agentId); + const chain = db.core.agents.getChainOfCommand(token.agentId); if (chain.length > 0 && chain[0].id !== token.agentId && !wokenForComment.has(chain[0].id)) { onWakeAgent(chain[0].id, task.companyId, "comment"); } @@ -297,7 +297,7 @@ export function registerTaskRoutes(server: AgentApiServer) { let id: string; try { - id = db.tasks.saveTask({ + id = db.core.tasks.saveTask({ companyId, title: input.title, description: input.description ?? "", @@ -317,12 +317,12 @@ export function registerTaskRoutes(server: AgentApiServer) { // Auto-infer task_type from assignee's department if (input.assigneeAgentId) { - const assignee = db.entityCrud.getAgent(input.assigneeAgentId); + const assignee = db.core.entity.getAgent(input.assigneeAgentId); const inferredType = inferTaskType(assignee.department); - db.tasks.setTaskType(id, inferredType); + db.core.tasks.setTaskType(id, inferredType); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "task.created", diff --git a/src/main/api-server/routes-work-graph.ts b/src/main/api-server/routes-work-graph.ts index a976b820..7de1849a 100644 --- a/src/main/api-server/routes-work-graph.ts +++ b/src/main/api-server/routes-work-graph.ts @@ -35,7 +35,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { return; } - const id = db.entityCrud.saveProject({ + const id = db.core.entity.saveProject({ companyId, name: input.name, description: input.description ?? "", @@ -45,7 +45,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { targetDate: input.targetDate ?? null, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "project.created", @@ -79,7 +79,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { return; } - const existing = db.entityCrud.getProjectForUpdate(projectId, token.companyId); + const existing = db.core.entity.getProjectForUpdate(projectId, token.companyId); if (!existing) { server.json(res, 404, { error: "Project not found" }); return; @@ -91,7 +91,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { } try { - db.entityCrud.saveProject({ + db.core.entity.saveProject({ id: projectId, companyId: token.companyId, name: parsed.data.name ?? existing.name, @@ -135,8 +135,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { return; } - const id = db.entityCrud.saveGoal(input); - db.activityMod.addActivity({ + const id = db.core.entity.saveGoal(input); + db.core.activity.addActivity({ companyId, actor: token.agentId, action: "goal.created", @@ -167,7 +167,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { return; } - const existing = db.entityCrud.getGoalForUpdate(goalId, token.companyId); + const existing = db.core.entity.getGoalForUpdate(goalId, token.companyId); if (!existing) { server.json(res, 404, { error: "Goal not found" }); return; @@ -179,7 +179,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { } try { - db.entityCrud.saveGoal({ + db.core.entity.saveGoal({ id: goalId, companyId: token.companyId, title: parsed.data.title ?? existing.title, @@ -200,7 +200,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const goals = db.entityCrud.listGoals(params.companyId); + const goals = db.core.entity.listGoals(params.companyId); server.json(res, 200, goals); }); @@ -209,7 +209,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const projects = db.entityCrud.listProjects(params.companyId); + const projects = db.core.entity.listProjects(params.companyId); server.json(res, 200, projects); }); @@ -219,7 +219,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const sprints = db.knowledgeMod.listSprints(companyId); + const sprints = db.core.knowledge.listSprints(companyId); server.json(res, 200, sprints); }); @@ -231,7 +231,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { } const data = body as Record; try { - const id = db.knowledgeMod.saveSprint({ + const id = db.core.knowledge.saveSprint({ companyId, name: String(data.name ?? "Sprint"), goal: String(data.goal ?? ""), @@ -256,7 +256,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const { agents, tasks, recentActivity } = db.company.getDashboardSnapshot(companyId); + const { agents, tasks, recentActivity } = db.core.company.getDashboardSnapshot(companyId); const tasksByStatus: Record = {}; for (const t of tasks) { diff --git a/src/main/api-server/server-core.ts b/src/main/api-server/server-core.ts index 0c9f2602..7bf32146 100644 --- a/src/main/api-server/server-core.ts +++ b/src/main/api-server/server-core.ts @@ -230,7 +230,7 @@ export class AgentApiServer { if (typeof claimedRunId !== "string" || claimedRunId.trim() !== token.runId) { return { ok: false, status: 401, error: "Missing or mismatched X-Agent-Company-Run-Id header" }; } - const runState = this._db.runs.getRunAuthState(token.runId); + const runState = this._db.core.runs.getRunAuthState(token.runId); if (!runState || runState.companyId !== token.companyId || runState.agentId !== token.agentId) { return { ok: false, status: 403, error: "Token references an unknown or mismatched run" }; } @@ -362,7 +362,7 @@ export class AgentApiServer { companyId: string, refs: Array<{ id: string | null | undefined; table: "agents" | "goals" | "projects" | "tasks" | "workspaces" | "approvals"; label: string }>, ) { - const invalid = refs.find((ref) => ref.id && !this._db.settings.belongsToCompany(ref.table, ref.id, companyId)); + const invalid = refs.find((ref) => ref.id && !this._db.core.settings.belongsToCompany(ref.table, ref.id, companyId)); if (!invalid) return false; this.json(res, 422, { error: `${invalid.label} does not belong to the current company` }); return true; diff --git a/src/main/approval-policy.ts b/src/main/approval-policy.ts index f9a02433..06b0edd5 100644 --- a/src/main/approval-policy.ts +++ b/src/main/approval-policy.ts @@ -20,13 +20,13 @@ export function resolveApprovalDecision({ actor?: string; }) { const note = decisionNote ?? ""; - db.approvalsMod.decideApproval({ + db.core.approvals.decideApproval({ companyId, approvalId, state, decisionNote: note, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor, action: `approval.${state}`, diff --git a/src/main/automation/automation-action-handlers.ts b/src/main/automation/automation-action-handlers.ts index 168f1de5..7c6eb76c 100644 --- a/src/main/automation/automation-action-handlers.ts +++ b/src/main/automation/automation-action-handlers.ts @@ -58,7 +58,7 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_ const { db, logger, wakeAgentIfPossible } = deps; if (ctx.taskId) { - const assignedId = db.tasks.autoAssignTask(ctx.taskId, ctx.companyId); + const assignedId = db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId); if (assignedId) { wakeAgentIfPossible(assignedId, ctx.companyId, "assignment"); } @@ -67,14 +67,14 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_ if (!ctx.agentId) return; - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const unassigned = snapshot.tasks .filter((task) => task.companyId === ctx.companyId && !task.assigneeAgentId && ["backlog", "todo"].includes(task.status)) .sort((a, b) => (TASK_PRIORITY_SCORE[b.priority] ?? 0) - (TASK_PRIORITY_SCORE[a.priority] ?? 0)); if (unassigned.length === 0) return; const task = unassigned[0]!; - db.tasks.saveTask({ + db.core.tasks.saveTask({ id: task.id, companyId: task.companyId, projectId: task.projectId, @@ -106,7 +106,7 @@ function handleCreateTask({ deps, callbacks, ctx, config }: TypedAutomationActio return; } - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: ctx.companyId, projectId: ctx.projectId ?? config.projectId ?? null, title, @@ -140,7 +140,7 @@ function handleAgentNotification({ if (channel === "department" && !channelTargetId) { try { - const sender = db.entityCrud.getAgent(fromAgentId); + const sender = db.core.entity.getAgent(fromAgentId); channelTargetId = sender.department ?? null; } catch (err) { logger.warn(`[automation] getAgent failed for sender ${fromAgentId}; dropping department target:`, err instanceof Error ? err.message : String(err)); @@ -184,7 +184,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi if (!targetAgentId && ctx.agentId) { const triggerType = rule.trigger ?? ""; if (triggerType === "run_completed" || triggerType === "run_failed") { - const manager = db.agents.getManagerForAgent(ctx.agentId); + const manager = db.core.agents.getManagerForAgent(ctx.agentId); targetAgentId = manager?.id ?? ctx.agentId; } else { targetAgentId = ctx.agentId; @@ -197,7 +197,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi } function handleCreateApproval({ deps, ctx, config }: TypedAutomationActionExecution<"create_approval">): void { - deps.db.approvalsMod.requestApproval({ + deps.db.core.approvals.requestApproval({ companyId: ctx.companyId, relatedTaskId: ctx.taskId ?? null, requestedByAgentId: ctx.agentId ?? null, @@ -219,8 +219,8 @@ function handleUpdateTaskStatus({ const { db, publishDomainChanged } = deps; const { handleTaskStatusChange } = callbacks; const newStatus = config.status ?? "todo"; - const previousTask = db.entityCrud.getTask(ctx.taskId); - db.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule"); + const previousTask = db.core.entity.getTask(ctx.taskId); + db.core.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule"); publishDomainChanged(); if (previousTask.status !== newStatus && rule.trigger !== "task_status_changed") { @@ -240,19 +240,19 @@ function handleEscalateToManager({ let agentName = "an agent"; try { - agentName = db.entityCrud.getAgent(targetAgentId).name; + agentName = db.core.entity.getAgent(targetAgentId).name; } catch (err) { logger.warn(`[automation] getAgent failed for escalation target ${targetAgentId}; using generic label:`, err instanceof Error ? err.message : String(err)); } let taskTitle = ctx.taskId ?? "unknown"; try { - if (ctx.taskId) taskTitle = db.entityCrud.getTask(ctx.taskId).title; + if (ctx.taskId) taskTitle = db.core.entity.getTask(ctx.taskId).title; } catch (err) { logger.warn(`[automation] getTask failed for escalation task ${ctx.taskId}; using id as title:`, err instanceof Error ? err.message : String(err)); } - const manager = db.agents.getManagerForAgent(targetAgentId); + const manager = db.core.agents.getManagerForAgent(targetAgentId); const escalationBody = config.body ?? `Task "${taskTitle}" needs your attention. ${agentName} has flagged this as requiring manager intervention.`; const wakeTrigger = deriveEscalationWakeTrigger(ctx.newStatus ?? null, rule.trigger ?? null); @@ -291,7 +291,7 @@ function handleCrossDepartmentNotify({ const targetDepartment = rule.targetDepartment ?? config.department; if (!targetDepartment) return; - const departmentAgents = db.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment); + const departmentAgents = db.core.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment); const senderAgentId = ctx.agentId ?? departmentAgents[0]?.id; if (!senderAgentId) return; @@ -322,7 +322,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu const offsetMinutes = config.scheduleOffsetMinutes ?? 60; const scheduledAt = config.scheduledAt ?? new Date(Date.now() + offsetMinutes * 60_000).toISOString(); - deps.db.knowledgeMod.saveMeeting({ + deps.db.core.knowledge.saveMeeting({ companyId: ctx.companyId, type: meetingType, title, @@ -334,7 +334,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu } function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecution<"create_document">): void { - deps.db.documentsMod.saveDocument({ + deps.db.core.documents.saveDocument({ companyId: ctx.companyId, type: config.documentType ?? "status_report", title: config.title ?? "Auto-generated document", @@ -345,13 +345,13 @@ function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecut } function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecution<"auto_approve">): void { - const snapshot = deps.db.snap.listSnapshot(); + const snapshot = deps.db.projection.snapshot.listSnapshot(); const pendingApprovals = snapshot.approvals.filter( (approval) => approval.companyId === ctx.companyId && approval.state === "pending", ); for (const approval of pendingApprovals.slice(0, 5)) { - deps.db.approvalsMod.decideApproval({ + deps.db.core.approvals.decideApproval({ companyId: ctx.companyId, approvalId: approval.id, state: "approved", @@ -364,7 +364,7 @@ function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecut function handleReassignTask({ deps, ctx }: TypedAutomationActionExecution<"reassign_task">): void { if (!ctx.taskId) return; - const newAssignee = deps.db.tasks.autoAssignTask(ctx.taskId, ctx.companyId); + const newAssignee = deps.db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId); if (newAssignee) { deps.wakeAgentIfPossible(newAssignee, ctx.companyId, "assignment"); } @@ -374,7 +374,7 @@ function handleRunTask({ deps, ctx, config }: TypedAutomationActionExecution<"ru const targetTaskId = config.taskId ?? ctx.taskId; if (!targetTaskId) return; - const task = deps.db.entityCrud.getTask(targetTaskId); + const task = deps.db.core.entity.getTask(targetTaskId); if (!task.assigneeAgentId) return; deps.wakeAgentIfPossible(task.assigneeAgentId, ctx.companyId, "assignment"); } diff --git a/src/main/automation/automation-approval-handler.ts b/src/main/automation/automation-approval-handler.ts index b6849470..fc75c5d6 100644 --- a/src/main/automation/automation-approval-handler.ts +++ b/src/main/automation/automation-approval-handler.ts @@ -30,7 +30,7 @@ function recordReviewFeedbackKnowledge( if (!approval.relatedTaskId) return; if (decision === "approved") return; - const task = db.entityCrud.getTask(approval.relatedTaskId); + const task = db.core.entity.getTask(approval.relatedTaskId); const feedback = approval.decisionNote?.trim() || "Board requested revisions before approval."; const topic = `Review feedback: ${task.title}`; const content = [ @@ -39,7 +39,7 @@ function recordReviewFeedbackKnowledge( "Use this review guidance to improve future deliverables for similar work.", ].join("\n"); - db.knowledgeMod.saveKnowledgeEntry({ + db.core.knowledge.saveKnowledgeEntry({ companyId: approval.companyId, category: "lesson_learned", topic, @@ -84,7 +84,7 @@ export function handleApprovalResolved( const { db, logger, publishDomainChanged, notify, wakeAgentIfPossible, dispatchBrowserAction } = deps; const { dispatchAutomation, handleTaskStatusChange, runWorkflowPipeline, trackTimer } = callbacks; - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const approval = snapshot.approvals.find((entry) => entry.id === approvalId); if (!approval) return; @@ -95,7 +95,7 @@ export function handleApprovalResolved( }); if (approval.type === "hire_agent" && decision === "approved" && approval.relatedAgentId) { - const agent = db.entityCrud.getAgent(approval.relatedAgentId); + const agent = db.core.entity.getAgent(approval.relatedAgentId); if (agent.status === "idle") { // Deterministic default skill kit. The CEO's operating prompt @@ -111,7 +111,7 @@ export function handleApprovalResolved( department: agent.department, }); if (assignedSkillIds.length > 0) { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "agent.skills_defaulted", @@ -121,7 +121,7 @@ export function handleApprovalResolved( }); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "agent.hired", @@ -136,13 +136,13 @@ export function handleApprovalResolved( }); wakeAgentIfPossible(agent.id, companyId, "assignment"); - const ceo = db.agents.getCeoForCompany(companyId); + const ceo = db.core.agents.getCeoForCompany(companyId); if (ceo && ceo.id !== agent.id) { wakeAgentIfPossible(ceo.id, companyId, "assignment"); } publishDomainChanged(); - const hireLocale = db.snap.listSnapshot().locale; + const hireLocale = db.projection.snapshot.listSnapshot().locale; notify({ title: localizeNotification(hireLocale, "Agent Hired", "新员工入职"), body: localizeNotification(hireLocale, `${agent.name} joined as ${agent.role}`, `${agent.name} 已加入,职位:${agent.role}`), @@ -170,31 +170,31 @@ export function handleApprovalResolved( ? "draft" : "in_review"; for (const docId of documentIds) { - db.documentsMod.updateDocumentStatus(docId, companyId, docStatus); + db.core.documents.updateDocumentStatus(docId, companyId, docStatus); } if (approval.relatedTaskId) { - const task = db.entityCrud.getTask(approval.relatedTaskId); + const task = db.core.entity.getTask(approval.relatedTaskId); if (task.assigneeAgentId) { - const agent = db.entityCrud.getAgent(task.assigneeAgentId); + const agent = db.core.entity.getAgent(task.assigneeAgentId); if (!agent.heartbeatEnabled) { - db.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120); logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`); } } if (decision === "approved") { - db.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable."); + db.core.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable."); handleTaskStatusChange(task.id, companyId, task.status, "done"); - db.activityMod.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." }); + db.core.activity.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." }); } else if (decision === "rejected") { recordReviewFeedbackKnowledge(db, approval, decision); - db.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`); + db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`); handleTaskStatusChange(task.id, companyId, task.status, "in_progress"); if (task.assigneeAgentId) { wakeAgentIfPossible(task.assigneeAgentId, companyId, "revision_needed"); } } else if (decision === "revision_requested") { - db.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`); + db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`); handleTaskStatusChange(task.id, companyId, task.status, "in_progress"); recordReviewFeedbackKnowledge(db, approval, decision); if (task.assigneeAgentId) { @@ -202,9 +202,9 @@ export function handleApprovalResolved( } } } else if (approval.requestedByAgentId) { - const agent = db.entityCrud.getAgent(approval.requestedByAgentId); + const agent = db.core.entity.getAgent(approval.requestedByAgentId); if (!agent.heartbeatEnabled) { - db.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120); logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`); } if (decision !== "approved") { @@ -219,7 +219,7 @@ export function handleApprovalResolved( const documentIds = extractApprovalDocumentIds(approval.payloadJson); const docStatus = decision === "approved" ? "approved" : "draft"; for (const docId of documentIds) { - db.documentsMod.updateDocumentStatus(docId, companyId, docStatus); + db.core.documents.updateDocumentStatus(docId, companyId, docStatus); } publishDomainChanged(); } @@ -235,8 +235,8 @@ export function handleApprovalResolved( }; if (payload.policyId) { // Resolve the budget incident that triggered this approval - db.budget.resolveBudgetIncident(payload.policyId, companyId); - db.activityMod.addActivity({ + db.core.budget.resolveBudgetIncident(payload.policyId, companyId); + db.core.activity.addActivity({ companyId, actor: "board", action: "budget.override_approved", @@ -250,10 +250,10 @@ export function handleApprovalResolved( } } - const browserAction = db.social.getBrowserActionByApprovalId(approvalId, companyId); + const browserAction = db.core.social.getBrowserActionByApprovalId(approvalId, companyId); if (browserAction) { if (decision === "approved") { - db.social.updateBrowserAction(browserAction.id, { + db.core.social.updateBrowserAction(browserAction.id, { status: "queued", resultSummary: "Approval granted. Dispatching browser action.", errorMessage: null, @@ -263,7 +263,7 @@ export function handleApprovalResolved( logger.error(`[browser] Failed to dispatch approved browser action ${browserAction.id}: ${error instanceof Error ? error.message : String(error)}`); }); } else if (decision === "rejected") { - db.social.updateBrowserAction(browserAction.id, { + db.core.social.updateBrowserAction(browserAction.id, { status: "cancelled", resultSummary: "Browser action was rejected during approval.", errorMessage: "Approval rejected by board.", @@ -271,7 +271,7 @@ export function handleApprovalResolved( }); publishDomainChanged(); } else if (decision === "revision_requested") { - db.social.updateBrowserAction(browserAction.id, { + db.core.social.updateBrowserAction(browserAction.id, { status: "approval_required", resultSummary: "Board requested revisions before execution.", errorMessage: "Approval requires revisions before the browser action can run.", @@ -291,7 +291,7 @@ export function handleApprovalResolved( }; const dispatchSocialAction = () => { - const actionId = db.social.saveBrowserAction({ + const actionId = db.core.social.saveBrowserAction({ companyId: approval.companyId, socialAccountId: draft.socialAccountId, agentId: approval.requestedByAgentId ?? "", @@ -333,14 +333,14 @@ export function handleApprovalResolved( const payload = workflowApprovalPayloadSchema.safeParse(JSON.parse(approval.payloadJson)); if (payload.success && payload.data.workflowId && payload.data.stepId) { const { workflowId, stepId } = payload.data; - const workflow = db.automationMod.getWorkflowRecord(workflowId, companyId); + const workflow = db.automation.rules.getWorkflowRecord(workflowId, companyId); if (workflow) { const steps = normalizeWorkflowSteps(JSON.parse(workflow.stepsJson || "[]")); const stepIndex = steps.findIndex((step) => step.id === stepId); if (stepIndex >= 0) { if (decision === "approved") { steps[stepIndex]!.status = "pending"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflow.id, companyId, status: "paused", @@ -354,7 +354,7 @@ export function handleApprovalResolved( }); } else { steps[stepIndex]!.status = decision === "revision_requested" ? "pending" : "failed"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflow.id, companyId, status: decision === "revision_requested" ? "paused" : "failed", diff --git a/src/main/automation/automation-conditions.ts b/src/main/automation/automation-conditions.ts index 319ab038..632a0115 100644 --- a/src/main/automation/automation-conditions.ts +++ b/src/main/automation/automation-conditions.ts @@ -23,7 +23,7 @@ export function matchesEventConfig( if (conditions.previousStatus && ctx.previousStatus && conditions.previousStatus !== ctx.previousStatus) return false; if (conditions.priority) { try { - const task = ctx.taskId ? db.entityCrud.getTask(ctx.taskId) : null; + const task = ctx.taskId ? db.core.entity.getTask(ctx.taskId) : null; if (task && task.priority !== conditions.priority) return false; // If task was deleted, skip this condition instead of rejecting the rule. } catch (err) { @@ -32,7 +32,7 @@ export function matchesEventConfig( } if (typeof conditions.budgetThresholdPct === "number" && ctx.agentId) { try { - const agent = db.entityCrud.getAgent(ctx.agentId); + const agent = db.core.entity.getAgent(ctx.agentId); if (agent.budgetMonthlyUsd > 0) { const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd; if (utilization < (conditions.budgetThresholdPct as number) / 100) return false; diff --git a/src/main/automation/automation-handlers.ts b/src/main/automation/automation-handlers.ts index abcd08f3..7f31742d 100644 --- a/src/main/automation/automation-handlers.ts +++ b/src/main/automation/automation-handlers.ts @@ -42,9 +42,9 @@ export function handleTaskStatusChange( const { db, wakeAgentIfPossible } = deps; const { dispatchAutomation } = callbacks; - let task: ReturnType | null = null; + let task: ReturnType | null = null; try { - task = db.entityCrud.getTask(taskId); + task = db.core.entity.getTask(taskId); } catch (error) { logAppError(deps.logger, error, { kind: "recoverable", @@ -56,7 +56,7 @@ export function handleTaskStatusChange( } if (!task) return; - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: task.assigneeAgentId ?? "board", action: "task.status_changed", @@ -74,7 +74,7 @@ export function handleTaskStatusChange( }; try { if (task.assigneeAgentId) { - const agent = db.entityCrud.getAgent(task.assigneeAgentId); + const agent = db.core.entity.getAgent(task.assigneeAgentId); context.department = agent.department ?? undefined; } } catch (error) { @@ -91,7 +91,7 @@ export function handleTaskStatusChange( if (newStatus === "blocked") { dispatchAutomation("task_blocked", context); if (task.assigneeAgentId) { - const manager = db.agents.getManagerForAgent(task.assigneeAgentId); + const manager = db.core.agents.getManagerForAgent(task.assigneeAgentId); if (manager) { wakeAgentIfPossible(manager.id, companyId, "report_blocked"); } @@ -100,14 +100,14 @@ export function handleTaskStatusChange( if (newStatus === "done" && task.parentId) { try { - const siblings = db.tasks.listChildTasks(task.parentId, companyId); + const siblings = db.core.tasks.listChildTasks(task.parentId, companyId); const allDone = siblings.every((entry) => entry.id === taskId ? true : entry.status === "done"); if (allDone) { - const parent = db.entityCrud.getTask(task.parentId); + const parent = db.core.entity.getTask(task.parentId); if (parent.assigneeAgentId) { wakeAgentIfPossible(parent.assigneeAgentId, companyId, "subtask_completed"); } else { - const assignedId = db.tasks.autoAssignTask(parent.id, companyId); + const assignedId = db.core.tasks.autoAssignTask(parent.id, companyId); if (assignedId) { wakeAgentIfPossible(assignedId, companyId, "subtask_completed"); } @@ -134,9 +134,9 @@ export function handleTaskCreated( const { db, wakeAgentIfPossible } = deps; const { dispatchAutomation } = callbacks; - let task: ReturnType | null = null; + let task: ReturnType | null = null; try { - task = db.entityCrud.getTask(taskId); + task = db.core.entity.getTask(taskId); } catch (error) { logAppError(deps.logger, error, { kind: "recoverable", @@ -155,7 +155,7 @@ export function handleTaskCreated( }); if (!task.assigneeAgentId) { - const assignedId = db.tasks.autoAssignTask(taskId, companyId); + const assignedId = db.core.tasks.autoAssignTask(taskId, companyId); if (assignedId) { wakeAgentIfPossible(assignedId, companyId, "assignment"); } diff --git a/src/main/automation/automation-run-completion.ts b/src/main/automation/automation-run-completion.ts index 207939a6..94794944 100644 --- a/src/main/automation/automation-run-completion.ts +++ b/src/main/automation/automation-run-completion.ts @@ -151,7 +151,7 @@ function checkRunProducedDeliverable( taskId: string, taskType: string, ): boolean { - const signals = db.knowledgeMod.collectRunDeliverableSignals(runId, companyId, taskId); + const signals = db.core.knowledge.collectRunDeliverableSignals(runId, companyId, taskId); return hasValidDeliverableFor(taskType, signals); } @@ -181,7 +181,7 @@ function createDeliverableReviewApproval( ): string { const { db, logger, publishDomainChanged, notify } = deps; const docTitles = docs.map((d) => d.title).join(", "); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: agentId, @@ -190,10 +190,10 @@ function createDeliverableReviewApproval( impactSummary: reviewerNote ?? "Agent completed work with important deliverables. Awaiting your final review.", payloadJson: JSON.stringify({ documentIds: docs.map((d) => d.id), documents: docs }), }); - db.tasks.setTaskRequiresUserReview(taskId, false); - const agent = db.entityCrud.getAgent(agentId); + db.core.tasks.setTaskRequiresUserReview(taskId, false); + const agent = db.core.entity.getAgent(agentId); if (agent.heartbeatEnabled) { - db.agents.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120); logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — awaiting user review.`); } notify({ @@ -228,13 +228,13 @@ function createDeliverableReviewApproval( */ function decideTerminalStatus( db: RunCompletionDeps["db"], - agent: ReturnType, + agent: ReturnType, companyId: string, ): "in_review" | "done" { if (agent.reportsTo !== null) return "in_review"; - const mode = db.company.getCompanyExecutionMode(companyId); + const mode = db.core.company.getCompanyExecutionMode(companyId); if (mode !== "ai_ceo") return "in_review"; - const settings = db.company.getCompanyApprovalSettings(companyId); + const settings = db.core.company.getCompanyApprovalSettings(companyId); return settings?.reviewDeliverables ? "in_review" : "done"; } @@ -247,7 +247,7 @@ function advanceToDone( statusMessage: string, ): void { const { db, publishDomainChanged } = deps; - db.tasks.advanceTaskStatus(taskId, "done", statusMessage); + db.core.tasks.advanceTaskStatus(taskId, "done", statusMessage); callbacks.handleTaskStatusChange(taskId, run.companyId, "in_progress", "done"); publishDomainChanged(); } @@ -268,22 +268,22 @@ function advanceToInReview( // expected (`ai_cofounder` mode or `reviewDeliverables = true`), so // pre-flagging here is the right semantics — the human IS the // reviewer in those configurations. - const agent = db.entityCrud.getAgent(run.agentId); + const agent = db.core.entity.getAgent(run.agentId); if (agent.reportsTo === null) { - db.tasks.setTaskRequiresUserReview(taskId, true); + db.core.tasks.setTaskRequiresUserReview(taskId, true); } - db.tasks.advanceTaskStatus(taskId, "in_review", statusMessage); + db.core.tasks.advanceTaskStatus(taskId, "in_review", statusMessage); callbacks.handleTaskStatusChange(taskId, run.companyId, "in_progress", "in_review"); - const company = db.snap.listSnapshot().companies.find((c) => c.id === run.companyId); + const company = db.projection.snapshot.listSnapshot().companies.find((c) => c.id === run.companyId); if (company?.reviewDeliverables) { const importantDocs = findImportantDocsForRun(db, run.id, run.companyId); if (importantDocs.length > 0) { - db.tasks.setTaskRequiresUserReview(taskId, true); + db.core.tasks.setTaskRequiresUserReview(taskId, true); if (agent.heartbeatEnabled) { - db.agents.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120); logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — deliverable flagged for review.`); } - const chain = db.agents.getChainOfCommand(agent.id); + const chain = db.core.agents.getChainOfCommand(agent.id); if (chain.length === 0) { createDeliverableReviewApproval(deps, run.companyId, taskId, run.agentId, importantDocs); } @@ -305,7 +305,7 @@ function advanceSuccessfulRun( taskId: string, statusMessage: string, ): "in_review" | "done" { - const agent = deps.db.entityCrud.getAgent(run.agentId); + const agent = deps.db.core.entity.getAgent(run.agentId); const target = decideTerminalStatus(deps.db, agent, run.companyId); if (target === "done") { advanceToDone(deps, callbacks, run, taskId, statusMessage); @@ -327,9 +327,9 @@ export function processRunCompletion( const { dispatchAutomation, handleTaskStatusChange } = callbacks; if (!run.taskId) return; - let task: ReturnType | null = null; + let task: ReturnType | null = null; try { - task = db.entityCrud.getTask(run.taskId); + task = db.core.entity.getTask(run.taskId); } catch (error) { deps.logger.warn(`[automation] processRunCompletion: task ${run.taskId} not found for run ${run.id}:`, error); return; @@ -363,7 +363,7 @@ export function processRunCompletion( // the task in the manager's blocker queue with the diagnostic // comment attached; the manager patches it back to `todo` (or // `cancelled`) after deciding how to proceed. - db.tasks.advanceTaskStatus( + db.core.tasks.advanceTaskStatus( task.id, "blocked", `${taskType} task run completed but no deliverable artifact was detected. Flagging for manager review.`, @@ -371,8 +371,8 @@ export function processRunCompletion( handleTaskStatusChange(task.id, run.companyId, "in_progress", "blocked"); // Wake manager for review try { - const agent = db.entityCrud.getAgent(run.agentId); - const chain = db.agents.getChainOfCommand(agent.id); + const agent = db.core.entity.getAgent(run.agentId); + const chain = db.core.agents.getChainOfCommand(agent.id); if (chain.length > 0) { wakeAgentIfPossible(chain[0].id, run.companyId, "review_needed"); } @@ -414,7 +414,7 @@ export function processRunCompletion( runId: run.id, }; try { - const agent = db.entityCrud.getAgent(run.agentId); + const agent = db.core.entity.getAgent(run.agentId); automationContext.department = agent.department ?? undefined; } catch (err) { deps.logger.warn(`[automation] getAgent failed for ${run.agentId} during run completion; dropping department from context:`, err instanceof Error ? err.message : String(err)); @@ -424,14 +424,14 @@ export function processRunCompletion( dispatchAutomation("run_completed", automationContext); if (task.parentId) { try { - const siblings = db.tasks.listChildTasks(task.parentId, run.companyId); + const siblings = db.core.tasks.listChildTasks(task.parentId, run.companyId); const allSiblingsDoneOrReview = siblings.every((entry) => entry.status === "done" || entry.status === "in_review"); if (allSiblingsDoneOrReview) { - const parent = db.entityCrud.getTask(task.parentId); + const parent = db.core.entity.getTask(task.parentId); if (parent.assigneeAgentId) { wakeAgentIfPossible(parent.assigneeAgentId, run.companyId, "subtask_completed"); } else { - const assignedId = db.tasks.autoAssignTask(parent.id, run.companyId); + const assignedId = db.core.tasks.autoAssignTask(parent.id, run.companyId); if (assignedId) { wakeAgentIfPossible(assignedId, run.companyId, "subtask_completed"); } @@ -452,7 +452,7 @@ export function processRunCompletion( // just read the post-run spend and fire if it crosses 80%. if (isSuccess) { try { - const agent = db.entityCrud.getAgent(run.agentId); + const agent = db.core.entity.getAgent(run.agentId); if (agent.budgetMonthlyUsd > 0) { const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd; if (utilization >= 0.8) { @@ -462,7 +462,7 @@ export function processRunCompletion( }); } } - const budgetLocale = db.snap.listSnapshot().locale; + const budgetLocale = db.projection.snapshot.listSnapshot().locale; checkBudgetThreshold( agent, notify, diff --git a/src/main/automation/automation-workflow.ts b/src/main/automation/automation-workflow.ts index 176d4a85..c43f09bf 100644 --- a/src/main/automation/automation-workflow.ts +++ b/src/main/automation/automation-workflow.ts @@ -60,7 +60,7 @@ export function resolveWorkflowStepActor( companyId: string, ): AgentRecord | null { if (step.assigneeAgentId) { - const agent = db.entityCrud.getAgent(step.assigneeAgentId); + const agent = db.core.entity.getAgent(step.assigneeAgentId); if (agent.companyId !== companyId) { throw new Error(`Workflow step assignee ${step.assigneeAgentId} does not belong to company ${companyId}.`); } @@ -68,13 +68,13 @@ export function resolveWorkflowStepActor( } if (step.assigneeDepartment) { - const idleAgents = db.agents.getIdleAgentsForDepartment(companyId, step.assigneeDepartment); + const idleAgents = db.core.agents.getIdleAgentsForDepartment(companyId, step.assigneeDepartment); if (idleAgents.length > 0) { - return db.entityCrud.getAgent(idleAgents[0]!.id); + return db.core.entity.getAgent(idleAgents[0]!.id); } } - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); return snapshot.agents.find((agent) => agent.companyId === companyId && !agent.reportsTo && agent.status !== "terminated") ?? null; } @@ -113,7 +113,7 @@ export async function runWorkflowPipeline( ): Promise { const { db, publishDomainChanged } = deps; - const workflow = db.automationMod.getWorkflowRecord(workflowId, companyId); + const workflow = db.automation.rules.getWorkflowRecord(workflowId, companyId); if (!workflow) { throw new Error("Workflow not found."); } @@ -133,7 +133,7 @@ export async function runWorkflowPipeline( return step; }); - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflowId, companyId, status: "active", @@ -147,7 +147,7 @@ export async function runWorkflowPipeline( for (let index = resumeFrom; index < steps.length; index += 1) { const step = steps[index]!; step.status = "running"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflowId, companyId, status: "active", @@ -161,7 +161,7 @@ export async function runWorkflowPipeline( const { config, ctx } = buildWorkflowActionContext(companyId, step, actor); if (step.requiresApproval) { - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, requestedByAgentId: actor?.id ?? null, relatedTaskId: ctx.taskId ?? null, @@ -171,7 +171,7 @@ export async function runWorkflowPipeline( payloadJson: JSON.stringify({ workflowId, stepId: step.id, stepName: step.name }), }); step.status = "pending"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflowId, companyId, status: "paused", @@ -179,7 +179,7 @@ export async function runWorkflowPipeline( stepsJson: JSON.stringify(steps), lastRunAt: new Date().toISOString(), }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: actor?.id ?? "system", action: "workflow.paused_for_approval", @@ -204,7 +204,7 @@ export async function runWorkflowPipeline( ctx, ); step.status = "completed"; - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: actor?.id ?? "system", action: "workflow.step_completed", @@ -214,7 +214,7 @@ export async function runWorkflowPipeline( }); const isLastStep = index === steps.length - 1; const completionStatus = isLastStep ? "completed" : "active"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflowId, companyId, status: completionStatus, @@ -225,7 +225,7 @@ export async function runWorkflowPipeline( publishDomainChanged(); } catch (error) { step.status = "failed"; - db.automationMod.updateWorkflowExecution({ + db.automation.rules.updateWorkflowExecution({ id: workflowId, companyId, status: "failed", @@ -233,7 +233,7 @@ export async function runWorkflowPipeline( stepsJson: JSON.stringify(steps), lastRunAt: new Date().toISOString(), }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: actor?.id ?? "system", action: "workflow.step_failed", diff --git a/src/main/automation/index.ts b/src/main/automation/index.ts index 113bdaea..572cec9c 100644 --- a/src/main/automation/index.ts +++ b/src/main/automation/index.ts @@ -107,7 +107,7 @@ export function createAutomationService({ function evaluateAutomationRules(trigger: string, ctx: AutomationEventContext) { try { - const rules = db.automationMod.listActiveAutomationRules(ctx.companyId, trigger); + const rules = db.automation.rules.listActiveAutomationRules(ctx.companyId, trigger); for (const rule of rules) { if (!matchesConditions(db, rule, ctx, logger)) continue; const executionKey = JSON.stringify({ @@ -127,7 +127,7 @@ export function createAutomationService({ activeAutomationExecutions.add(executionKey); try { executeAutomationAction(actionDeps, actionCallbacks, rule, ctx); - db.automationMod.recordAutomationExecution( + db.automation.rules.recordAutomationExecution( rule.id, ctx.companyId, rule.name, @@ -145,7 +145,7 @@ export function createAutomationService({ logger.info(`[automation] Fired rule "${rule.name}" (${trigger} → ${rule.action})`); } catch (error) { logger.error(`[automation] Failed to execute action for rule "${rule.name}": ${error instanceof Error ? error.message : String(error)}`); - db.automationMod.recordAutomationExecution( + db.automation.rules.recordAutomationExecution( rule.id, ctx.companyId, rule.name, @@ -164,7 +164,7 @@ export function createAutomationService({ function evaluateWorkflowTriggers(trigger: string, ctx: AutomationEventContext) { try { - const workflows = db.automationMod.listTriggerableWorkflows(ctx.companyId, trigger); + const workflows = db.automation.rules.listTriggerableWorkflows(ctx.companyId, trigger); for (const workflow of workflows) { if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx, logger)) continue; const executionKey = JSON.stringify({ diff --git a/src/main/bootstrap/backfill-workspace-artifacts.ts b/src/main/bootstrap/backfill-workspace-artifacts.ts index 0bc844f6..b52252b3 100644 --- a/src/main/bootstrap/backfill-workspace-artifacts.ts +++ b/src/main/bootstrap/backfill-workspace-artifacts.ts @@ -97,7 +97,7 @@ export function backfillWorkspaceArtifacts( filesAlreadyTracked: 0, filesIgnored: 0, }; - const flag = deps.db.settings.getSetting(SETTINGS_FLAG_KEY); + const flag = deps.db.core.settings.getSetting(SETTINGS_FLAG_KEY); if (flag === "true") return empty; let filesScanned = 0; @@ -105,9 +105,9 @@ export function backfillWorkspaceArtifacts( let filesAlreadyTracked = 0; let filesIgnored = 0; - const snapshot = deps.db.snap.listSnapshot(); + const snapshot = deps.db.projection.snapshot.listSnapshot(); for (const company of snapshot.companies) { - const observation = deps.db.workspacesMod.getCompanyObservationWorkspace(company.id); + const observation = deps.db.core.workspaces.getCompanyObservationWorkspace(company.id); if (!observation) continue; const workspaceRoot = observation.localPath; if (!workspaceRoot || !existsSync(workspaceRoot)) continue; @@ -186,7 +186,7 @@ export function backfillWorkspaceArtifacts( } } - const existing = deps.db.documentsMod.findWorkProductByExternalId( + const existing = deps.db.core.documents.findWorkProductByExternalId( company.id, "filesystem", rel, @@ -197,7 +197,7 @@ export function backfillWorkspaceArtifacts( continue; } const title = lastSlash >= 0 ? rel.slice(lastSlash + 1) : rel; - deps.db.documentsMod.createWorkProduct({ + deps.db.core.documents.createWorkProduct({ companyId: company.id, taskId: null, workspaceId: observation.id, @@ -212,7 +212,7 @@ export function backfillWorkspaceArtifacts( } } - deps.db.settings.setSetting(SETTINGS_FLAG_KEY, "true"); + deps.db.core.settings.setSetting(SETTINGS_FLAG_KEY, "true"); deps.logger.info( `[backfill-workspace-artifacts] scanned=${filesScanned} inserted=${filesInserted} already-tracked=${filesAlreadyTracked} ignored=${filesIgnored}`, ); diff --git a/src/main/bootstrap/prepare-services.ts b/src/main/bootstrap/prepare-services.ts index a26362c2..d208f752 100644 --- a/src/main/bootstrap/prepare-services.ts +++ b/src/main/bootstrap/prepare-services.ts @@ -121,9 +121,9 @@ export function prepareServices(deps: PrepareServicesDeps): ServiceGraphHandles logger: deps.logger, getMainWindow: () => serviceGraph?.getDesktopShellService()?.getMainWindow() ?? null, getDb: () => ({ - hasCompany: (companyId: string) => deps.db.settings.hasCompany(companyId), + hasCompany: (companyId: string) => deps.db.core.settings.hasCompany(companyId), belongsToCompany: (table: string, id: string, companyId: string) => - deps.db.settings.belongsToCompany(table as Parameters[0], id, companyId), + deps.db.core.settings.belongsToCompany(table as Parameters[0], id, companyId), }), notificationService, }); diff --git a/src/main/bootstrap/recover-interrupted-tasks.ts b/src/main/bootstrap/recover-interrupted-tasks.ts index 10c08da7..ac1c11f4 100644 --- a/src/main/bootstrap/recover-interrupted-tasks.ts +++ b/src/main/bootstrap/recover-interrupted-tasks.ts @@ -21,7 +21,7 @@ export function emitCrashRecoveryNotification(deps: RecoverInterruptedTasksDeps) const interruptedCount = deps.db.lastInterruptedCount; if (interruptedCount <= 0) return; deps.logger.warn(`[bootstrap] Detected ${interruptedCount} interrupted runs from previous session. Sending recovery notification.`); - const interruptedRuns = deps.db.runs.getInterruptedRunsForRecovery(); + const interruptedRuns = deps.db.core.runs.getInterruptedRunsForRecovery(); if (interruptedRuns.length === 0) return; deps.emitEvent({ type: "recovery-needed", interruptedRuns }); deps.notify({ @@ -39,7 +39,7 @@ export function emitCrashRecoveryNotification(deps: RecoverInterruptedTasksDeps) */ export function recoverShutdownInterruptedTasks(deps: RecoverInterruptedTasksDeps): void { try { - const snapshot = deps.db.snap.listSnapshot(); + const snapshot = deps.db.projection.snapshot.listSnapshot(); const shutdownRuns = snapshot.runs.filter( (r) => r.status === "interrupted" && (r.summary === "Application shut down." || r.summary === "Worker process crashed."), @@ -49,10 +49,10 @@ export function recoverShutdownInterruptedTasks(deps: RecoverInterruptedTasksDep for (const run of shutdownRuns) { try { if (run.taskId) { - const task = deps.db.entityCrud.getTask(run.taskId); + const task = deps.db.core.entity.getTask(run.taskId); if (task.status === "in_progress" || task.status === "blocked") { - deps.db.tasks.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart."); - deps.db.tasks.releaseTaskLock(run.taskId, run.id); + deps.db.core.tasks.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart."); + deps.db.core.tasks.releaseTaskLock(run.taskId, run.id); } } if (!resumedAgents.has(run.agentId)) { diff --git a/src/main/bootstrap/recover-scheduled-posts.ts b/src/main/bootstrap/recover-scheduled-posts.ts index cd983af7..53821991 100644 --- a/src/main/bootstrap/recover-scheduled-posts.ts +++ b/src/main/bootstrap/recover-scheduled-posts.ts @@ -15,9 +15,9 @@ export interface RecoverScheduledPostsDeps { export function recoverScheduledPosts(deps: RecoverScheduledPostsDeps): Set> { const timers = new Set>(); try { - const snapshot = deps.db.snap.listSnapshot(); + const snapshot = deps.db.projection.snapshot.listSnapshot(); for (const company of snapshot.companies) { - const queuedActions = deps.db.social.listBrowserActions(company.id, { status: "queued" }); + const queuedActions = deps.db.core.social.listBrowserActions(company.id, { status: "queued" }); for (const action of queuedActions) { if (!action.payloadJson) continue; try { diff --git a/src/main/bootstrap/run-startup-health-checks.ts b/src/main/bootstrap/run-startup-health-checks.ts index 918ea487..6491ac75 100644 --- a/src/main/bootstrap/run-startup-health-checks.ts +++ b/src/main/bootstrap/run-startup-health-checks.ts @@ -14,7 +14,7 @@ export async function runStartupHealthChecks( logger: Logger, ): Promise { if (!connectorHealth) return; - const connectors = db.snap.listSnapshot().connectors; + const connectors = db.projection.snapshot.listSnapshot().connectors; const results = await Promise.allSettled( connectors.map((connector) => connectorHealth.checkConnector(connector.id)), ); diff --git a/src/main/bootstrap/wire-api-server.ts b/src/main/bootstrap/wire-api-server.ts index 60561c97..bdd64008 100644 --- a/src/main/bootstrap/wire-api-server.ts +++ b/src/main/bootstrap/wire-api-server.ts @@ -57,7 +57,7 @@ export function wireApiServer(deps: WireApiServerDeps): AgentApiServer { deps.threadIntegration.onTaskStatusChanged(taskId, companyId, prev, next); if (prev === "todo" && next === "in_progress") { try { - const task = deps.db.entityCrud.getTask(taskId); + const task = deps.db.core.entity.getTask(taskId); deps.checkpointEngine.onTaskStarted(taskId, companyId, task.assigneeAgentId); } catch (error) { log.warn( @@ -120,7 +120,7 @@ export function wireApiServer(deps: WireApiServerDeps): AgentApiServer { }, input, ), - onAutoAssignTask: (taskId, companyId) => deps.db.tasks.autoAssignTask(taskId, companyId), + onAutoAssignTask: (taskId, companyId) => deps.db.core.tasks.autoAssignTask(taskId, companyId), onHumanMessage: (companyId, toAgentId, threadId, waitStateId) => { deps.threadIntegration.onHumanMessage(companyId, toAgentId, threadId, waitStateId); deps.publishDomainChanged(); diff --git a/src/main/browser-action-service.ts b/src/main/browser-action-service.ts index f046ac9c..71cfb85a 100644 --- a/src/main/browser-action-service.ts +++ b/src/main/browser-action-service.ts @@ -27,9 +27,9 @@ export function createBrowserActionService({ * missing. Isolates the "approval row disappeared" case so the caller does * not need to nest a try/catch around the state machine. */ - function getApprovalOrNull(approvalId: string): ReturnType | null { + function getApprovalOrNull(approvalId: string): ReturnType | null { try { - return db.approvalsMod.getApprovalRecord(approvalId); + return db.core.approvals.getApprovalRecord(approvalId); } catch (err) { const classified = classifyUnknownError(err, { kind: "recoverable", @@ -43,7 +43,7 @@ export function createBrowserActionService({ } async function dispatchBrowserAction(actionId: string, companyId: string) { - const action = db.social.getBrowserAction(actionId, companyId); + const action = db.core.social.getBrowserAction(actionId, companyId); if (!action) { throw new Error(`Browser action not found: ${actionId}`); } @@ -56,7 +56,7 @@ export function createBrowserActionService({ throw new Error(`Browser action approval ${action.approvalId} does not belong to company ${companyId}.`); } if (approval.state !== "approved") { - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: "approval_required", resultSummary: "Browser action is still waiting for approval.", errorMessage: "Approval must be granted before execution.", @@ -66,9 +66,9 @@ export function createBrowserActionService({ } } - const account = db.social.getSocialAccount(action.socialAccountId, companyId); + const account = db.core.social.getSocialAccount(action.socialAccountId, companyId); if (!account) { - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: "failed", errorMessage: "Social account no longer exists.", finishedAt: new Date().toISOString(), @@ -88,7 +88,7 @@ export function createBrowserActionService({ context: { actionId }, }); log.warn(`[${classified.code}]`, classified.message, classified.context); - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: "failed", errorMessage: "Browser action payload is not valid JSON.", finishedAt: new Date().toISOString(), @@ -97,7 +97,7 @@ export function createBrowserActionService({ return; } - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: "running", startedAt: new Date().toISOString(), errorMessage: null, @@ -113,7 +113,7 @@ export function createBrowserActionService({ payload, }); - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: result.cancelled ? "cancelled" : result.success ? "succeeded" : "failed", resultSummary: result.summary, screenshotPath: result.screenshotPath, @@ -121,7 +121,7 @@ export function createBrowserActionService({ finishedAt: new Date().toISOString(), }); } catch (error) { - db.social.updateBrowserAction(actionId, { + db.core.social.updateBrowserAction(actionId, { status: "failed", errorMessage: error instanceof Error ? error.message : String(error), finishedAt: new Date().toISOString(), diff --git a/src/main/budget-enforcement-service.ts b/src/main/budget-enforcement-service.ts index 7f601017..df934143 100644 --- a/src/main/budget-enforcement-service.ts +++ b/src/main/budget-enforcement-service.ts @@ -31,7 +31,7 @@ function getScopeLabel( ): string { if (policy.scopeType === "agent") { try { - const agent = db.entityCrud.getAgent(policy.scopeId); + const agent = db.core.entity.getAgent(policy.scopeId); return `Agent "${agent.name}"`; } catch (err) { logger.warn(`[budget] getAgent failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err)); @@ -68,7 +68,7 @@ export function createBudgetEnforcementService({ agentId: string, projectId: string | null, ): BudgetGateResult { - const policies = db.budget.getActivePoliciesForRun(companyId, agentId, projectId); + const policies = db.core.budget.getActivePoliciesForRun(companyId, agentId, projectId); if (policies.length === 0) { return { allowed: true, blockedBy: [] }; } @@ -76,7 +76,7 @@ export function createBudgetEnforcementService({ const blockedBy: BudgetGateResult["blockedBy"] = []; for (const policy of policies) { if (!policy.hardStopEnabled) continue; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); if (spent >= policy.amount) { blockedBy.push({ policyId: policy.id, @@ -103,19 +103,19 @@ export function createBudgetEnforcementService({ agentId: string, projectId: string | null, ): void { - const policies = db.budget.getActivePoliciesForRun(companyId, agentId, projectId); + const policies = db.core.budget.getActivePoliciesForRun(companyId, agentId, projectId); for (const policy of policies) { // amount <= 0 disables the policy (and would otherwise divide by zero below). if (policy.amount <= 0) continue; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); const utilization = spent / policy.amount; const warnThreshold = policy.warnPercent / 100; const scopeLabel = getScopeLabel(db, policy, logger); if (utilization >= warnThreshold) { - db.budget.upsertBudgetIncident(policy, "warn_threshold", spent); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", spent); if (policy.notifyEnabled) { notify({ title: "Budget Warning", @@ -128,7 +128,7 @@ export function createBudgetEnforcementService({ } if (utilization >= 1.0 && policy.hardStopEnabled) { - db.budget.upsertBudgetIncident(policy, "hard_stop", spent); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", spent); if (policy.notifyEnabled) { notify({ title: "Budget Exhausted", @@ -151,22 +151,22 @@ export function createBudgetEnforcementService({ * Per D-06: fires auto_resolved_limit_increase status. */ function checkAutoResolve(companyId: string, policyId: string): void { - const policy = db.budget.getBudgetPolicy(policyId); + const policy = db.core.budget.getBudgetPolicy(policyId); if (!policy) return; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); let resolved = false; // If spend is below hard-stop threshold, resolve all open incidents if (spent < policy.amount) { - const count = db.budget.resolveIncidentsForPolicy(policyId, "auto_resolved_limit_increase"); + const count = db.core.budget.resolveIncidentsForPolicy(policyId, "auto_resolved_limit_increase"); if (count > 0) resolved = true; } // If spend is below warn threshold, resolve warn incidents specifically const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100); if (spent < warnAmount) { - const count = db.budget.resolveWarnIncidentsForPolicy(policyId, "auto_resolved_limit_increase"); + const count = db.core.budget.resolveWarnIncidentsForPolicy(policyId, "auto_resolved_limit_increase"); if (count > 0) resolved = true; } @@ -181,7 +181,7 @@ export function createBudgetEnforcementService({ * Per D-06: window rollover cleanup. */ function autoCloseExpiredIncidents(companyId: string): number { - const count = db.budget.autoCloseExpiredIncidents(companyId); + const count = db.core.budget.autoCloseExpiredIncidents(companyId); if (count > 0) { logger.info(`Budget auto-close: ${count} expired incident(s) for company ${companyId}`); publishDomainChanged(); @@ -193,12 +193,12 @@ export function createBudgetEnforcementService({ * Returns a summary of all budget policies and their current spend status. */ function getBudgetStatus(companyId: string): BudgetStatusSummary { - const policies = db.budget.listBudgetPolicies(companyId); - const openIncidents = db.budget.listBudgetIncidents(companyId, "open"); + const policies = db.core.budget.listBudgetPolicies(companyId); + const openIncidents = db.core.budget.listBudgetIncidents(companyId, "open"); return { policies: policies.map((policy) => { - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); const hasOpenIncident = openIncidents.some((i) => i.policyId === policy.id); return { policyId: policy.id, diff --git a/src/main/checkpoint-engine.ts b/src/main/checkpoint-engine.ts index 079a0c76..e49c9277 100644 --- a/src/main/checkpoint-engine.ts +++ b/src/main/checkpoint-engine.ts @@ -27,7 +27,7 @@ interface CheckpointDeps { } export function getCompanyExecutionMode(db: AppDatabase, companyId: string): ExecutionMode { - return db.company.getCompanyExecutionMode(companyId) as ExecutionMode; + return db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode; } export function createCheckpointEngine(deps: CheckpointDeps) { @@ -48,8 +48,8 @@ export function createCheckpointEngine(deps: CheckpointDeps) { const mode = getCompanyExecutionMode(db, companyId); if (mode !== "ai_cofounder") return null; - const task = db.entityCrud.getTask(taskId); - const taskType = db.tasks.getTaskType(taskId); + const task = db.core.entity.getTask(taskId); + const taskType = db.core.tasks.getTaskType(taskId); // Only create plan checkpoints for non-code task types if (!taskType || !CHECKPOINT_TASK_TYPES.has(taskType)) return null; @@ -60,7 +60,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) { // the moment delegation starts. if (task.parentId) return null; - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: agentId, @@ -107,7 +107,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) { ? "strategy_review" : "draft_review"; - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: authorAgentId, @@ -154,7 +154,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) { } // Check for pending approvals related to this agent's tasks - return db.approvalsMod.hasPendingReviewApprovalForAgent(companyId, agentId); + return db.core.approvals.hasPendingReviewApprovalForAgent(companyId, agentId); } catch (err) { logger.error(`[checkpoint] shouldPauseHeartbeat failed: ${err instanceof Error ? err.message : String(err)}`); return false; diff --git a/src/main/connector-health-service.ts b/src/main/connector-health-service.ts index 73fd9830..26228fcf 100644 --- a/src/main/connector-health-service.ts +++ b/src/main/connector-health-service.ts @@ -27,7 +27,7 @@ export function createConnectorHealthService({ credentialStore, }: ConnectorHealthDependencies) { function randomFallbackCompanyId() { - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); return snapshot.companies[0]?.id ?? "00000000-0000-0000-0000-000000000000"; } @@ -57,7 +57,7 @@ export function createConnectorHealthService({ // Re-parse envBindingText on every probe: it is user-editable and must // not be cached into credentialStore. An explicit binding for the // connector's declared envKey (non-empty value) counts as ready. - const connector = db.connectorsCrud.findConnector(id); + const connector = db.core.connectors.findConnector(id); const boundValue = connector ? parseEnvBindingText(connector.envBindingText)[definition.auth.envKey] : undefined; @@ -113,12 +113,12 @@ export function createConnectorHealthService({ } async function checkConnector(id: ConnectorId) { - const companyId = db.settings.getCurrentCompanyId() ?? randomFallbackCompanyId(); + const companyId = db.core.settings.getCurrentCompanyId() ?? randomFallbackCompanyId(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id, status: "detected", - authState: db.connectorsCrud.getConnector(id).authState, + authState: db.core.connectors.getConnector(id).authState, version: null, lastError: null, }); @@ -126,14 +126,14 @@ export function createConnectorHealthService({ try { const result = evaluateCredential(id); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id, status: result.status, authState: result.authState, version: null, lastError: result.lastError, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "connector.tested", @@ -145,7 +145,7 @@ export function createConnectorHealthService({ } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`[connector] Health check failed for ${id}: ${errorMessage}`); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id, status: "error", authState: "unknown", diff --git a/src/main/connector-model-policy.ts b/src/main/connector-model-policy.ts index a943db1c..669b13d7 100644 --- a/src/main/connector-model-policy.ts +++ b/src/main/connector-model-policy.ts @@ -14,12 +14,14 @@ interface ConnectorModelPolicyDeps { * domain modules so we do not repeat the closure literal at every site. */ export function connectorPolicyDepsFromDb(db: { - connectorsCrud: { getConnector: (id: ConnectorId) => { id: ConnectorId; label?: string | null; model: string | null } }; - settings: { getDisabledModels: (companyId: string, connectorId: string) => Array<{ id: string; label: string }> }; + core: { + connectors: { getConnector: (id: ConnectorId) => { id: ConnectorId; label?: string | null; model: string | null } }; + settings: { getDisabledModels: (companyId: string, connectorId: string) => Array<{ id: string; label: string }> }; + }; }): ConnectorModelPolicyDeps { return { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId), }; } diff --git a/src/main/credentials/credential-store.ts b/src/main/credentials/credential-store.ts index 32c12da3..92f0f0b2 100644 --- a/src/main/credentials/credential-store.ts +++ b/src/main/credentials/credential-store.ts @@ -50,7 +50,7 @@ export class CredentialStore { constructor(private readonly deps: CredentialStoreDeps) {} list(): CredentialSummary[] { - return this.deps.db.credentialsTable.list().map((row) => ({ + return this.deps.db.core.credentials.list().map((row) => ({ connectorId: row.connectorId, kind: row.kind as CredentialKind, displayHint: row.displayHint, @@ -66,7 +66,7 @@ export class CredentialStore { save(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null = null): CredentialSummary { const json = JSON.stringify(payload.value); const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(json); - const stored = this.deps.db.credentialsTable.upsert({ + const stored = this.deps.db.core.credentials.upsert({ connectorId, kind: payload.kind, backend: backend as CredentialBackend, @@ -91,7 +91,7 @@ export class CredentialStore { /** Decrypts and returns the typed credential payload. Returns null when missing. */ read(connectorId: string): StoredCredentialPayload | null { - const row = this.deps.db.credentialsTable.get(connectorId); + const row = this.deps.db.core.credentials.get(connectorId); if (!row) return null; const json = this.deps.db.vault.decrypt( row.backend, @@ -102,17 +102,17 @@ export class CredentialStore { } remove(connectorId: string): void { - this.deps.db.credentialsTable.remove(connectorId); + this.deps.db.core.credentials.remove(connectorId); } setStatus(connectorId: string, status: CredentialStatus, lastError: string | null = null): void { - this.deps.db.credentialsTable.setStatus(connectorId, status, lastError); + this.deps.db.core.credentials.setStatus(connectorId, status, lastError); } /** Replaces the OAuth ciphertext after a successful refresh. */ saveRefreshedOAuth(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null): void { const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(JSON.stringify(payload.value)); - this.deps.db.credentialsTable.markRefreshed( + this.deps.db.core.credentials.markRefreshed( connectorId, backend as CredentialBackend, ciphertext, diff --git a/src/main/database-base.ts b/src/main/database-base.ts index a747b41e..ea9f7067 100644 --- a/src/main/database-base.ts +++ b/src/main/database-base.ts @@ -31,12 +31,47 @@ import { buildModelCapabilityMethods } from "./database/model-capabilities-crud" import { MODEL_CAPABILITY_SEED } from "./model-capability-seed"; import { initializeDatabase } from "./database/schema"; import { SecretVault } from "./vault"; -import type { AgentStatus, ConnectorId, ProfileSnapshot, RunStatus } from "@shared/types"; import { STALE_LOCK_TIMEOUT_MS } from "./constants"; function nowIso() { return new Date().toISOString(); } + +export interface AppDatabaseCoreModules { + threads: ReturnType; + company: ReturnType; + agents: ReturnType; + tasks: ReturnType; + messages: ReturnType; + runs: ReturnType; + settings: ReturnType; + connectors: ReturnType; + workspaces: ReturnType; + approvals: ReturnType; + social: ReturnType; + activity: ReturnType; + skills: ReturnType; + budget: ReturnType; + entity: ReturnType; + documents: ReturnType; + knowledge: ReturnType; + backup: ReturnType; + credentials: ReturnType; + modelCapabilities: ReturnType; +} + +export interface AppDatabaseAutomationModules { + rules: ReturnType; + wakeup: ReturnType; +} + +export interface AppDatabaseProjectionModules { + snapshot: ReturnType; + sessionEntries: ReturnType; + inbox: ReturnType; + documentLifecycleEvents: ReturnType; +} + export class AppDatabaseBase { readonly profileDir: string; readonly dbPath: string; @@ -47,36 +82,9 @@ export class AppDatabaseBase { readonly vault: SecretVault; protected db: DatabaseSync; - // Domain modules. The canonical surface — call these directly from - // every consumer (`db.tasks.saveTask(...)`, not `db.saveTask(...)`). - // The ESLint rule in eslint.config.mjs blocks new top-level methods - // on AppDatabase so the old pass-through layer cannot grow back. - public readonly threads: ReturnType; - public readonly company: ReturnType; - public readonly agents: ReturnType; - public readonly tasks: ReturnType; - public readonly messages: ReturnType; - public readonly runs: ReturnType; - public readonly snap: ReturnType; - public readonly settings: ReturnType; - public readonly connectorsCrud: ReturnType; - public readonly workspacesMod: ReturnType; - public readonly approvalsMod: ReturnType; - public readonly social: ReturnType; - public readonly activityMod: ReturnType; - public readonly skills: ReturnType; - public readonly budget: ReturnType; - public readonly entityCrud: ReturnType; - public readonly documentsMod: ReturnType; - public readonly automationMod: ReturnType; - public readonly knowledgeMod: ReturnType; - public readonly wakeup: ReturnType; - public readonly backupMod: ReturnType; - public readonly sessionEntries: ReturnType; - public readonly inboxProjections: ReturnType; - public readonly documentLifecycleEvents: ReturnType; - public readonly credentialsTable: ReturnType; - public readonly modelCapabilities: ReturnType; + public readonly core: AppDatabaseCoreModules; + public readonly automation: AppDatabaseAutomationModules; + public readonly projection: AppDatabaseProjectionModules; constructor(rootDir: string, options: { isPackaged?: boolean; appVersion?: string } = {}) { this.profileDir = rootDir; @@ -99,70 +107,70 @@ export class AppDatabaseBase { const ra = (sql: string, ...params: SQLInputValue[]) => this.readAll(sql, ...params); const tx = (op: () => T) => this.transaction(op); - this.activityMod = buildActivityMethods(getDb, ra); - this.settings = buildSettingsMethods(getDb, ra, { + const activity = buildActivityMethods(getDb, ra); + const settings = buildSettingsMethods(getDb, ra, { profileDir: this.profileDir, logsDir: this.logsDir, artifactsDir: this.artifactsDir, backupsDir: this.backupsDir, }); - this.connectorsCrud = buildConnectorMethods(getDb, ra); - this.snap = buildSnapshotMethods(getDb, ra, { - getCurrentCompanyId: () => this.settings.getCurrentCompanyId(), + const connectors = buildConnectorMethods(getDb, ra); + const snapshot = buildSnapshotMethods(getDb, ra, { + getCurrentCompanyId: () => settings.getCurrentCompanyId(), getSetting: (key) => this.getSetting(key), - readConnectors: () => this.connectorsCrud.readConnectors(), + readConnectors: () => connectors.readConnectors(), profileDir: this.profileDir, vaultBackendLabel: () => this.vault.getBackendLabel(), }); - this.threads = buildThreadMethods(getDb, ra); - this.company = buildCompanyMethods(getDb, ra, tx, { - getSetting: (key) => this.getSetting(key), - setSetting: (key, value) => this.setSetting(key, value), - listSnapshot: (options) => this.snap.listSnapshot(options), - addComment: (input) => this.activityMod.addComment(input), - }); - this.agents = buildAgentMethods(getDb, ra, tx, { - getAgent: (id) => this.entityCrud.getAgent(id), - wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo), - recordConfigRevision: (input) => this.documentsMod.recordConfigRevision(input), - cancelRunForDeletion: (runId, taskId, workspaceId, summary) => this.runs.cancelRunForDeletion(runId, taskId, workspaceId, summary), - addActivity: (input) => this.activityMod.addActivity(input), - }); - this.tasks = buildTaskMethods(getDb, ra, tx, { - addComment: (input) => this.activityMod.addComment(input), - addActivity: (input) => this.activityMod.addActivity(input), - requestApproval: (input) => this.approvalsMod.requestApproval(input), - setHeartbeat: (agentId, enabled, intervalSec) => this.agents.setHeartbeat(agentId, enabled, intervalSec), - }); - this.messages = buildMessageMethods(getDb, ra, { - getAgent: (id) => this.entityCrud.getAgent(id), - addActivity: (input) => this.activityMod.addActivity(input), - transaction: tx, - }); - this.runs = buildRunMethods(getDb, ra, tx, { logsDir: this.logsDir }); - this.workspacesMod = buildWorkspaceMethods(getDb, ra, tx); - this.approvalsMod = buildApprovalMethods(getDb, ra, tx, { - getAgent: (id) => this.entityCrud.getAgent(id), - }); - this.social = buildSocialMethods(getDb, ra, tx); - this.skills = buildSkillMethods(getDb, ra); - this.budget = buildBudgetMethods(getDb, ra, tx); - this.entityCrud = buildEntityCrudMethods(getDb, ra, tx, { - addActivity: (input) => this.activityMod.addActivity(input), + const threads = buildThreadMethods(getDb, ra); + const entity = buildEntityCrudMethods(getDb, ra, tx, { + addActivity: (input) => activity.addActivity(input), vault: this.vault, listInvalidEnvBindingKeys, parseEnvBindingText, }); - this.documentsMod = buildDocumentMethods(getDb, ra, tx); - this.automationMod = buildAutomationMethods(getDb, ra, { - addActivity: (input) => this.activityMod.addActivity(input), + const documents = buildDocumentMethods(getDb, ra, tx); + const runs = buildRunMethods(getDb, ra, tx, { logsDir: this.logsDir }); + const company = buildCompanyMethods(getDb, ra, tx, { + getSetting: (key) => this.getSetting(key), + setSetting: (key, value) => this.setSetting(key, value), + listSnapshot: (options) => snapshot.listSnapshot(options), + addComment: (input) => activity.addComment(input), }); - this.knowledgeMod = buildKnowledgeMethods(getDb, ra); - this.wakeup = buildWakeupMethods(getDb, ra, tx); - this.sessionEntries = buildSessionEntryMethods(getDb, ra, tx); - this.inboxProjections = buildInboxProjectionMethods(getDb); - this.documentLifecycleEvents = buildDocumentLifecycleEventMethods(getDb, ra); - this.credentialsTable = buildCredentialMethods(getDb, ra, tx); - this.modelCapabilities = buildModelCapabilityMethods(getDb, ra, tx); - this.backupMod = buildBackupMethods({ + const agents = buildAgentMethods(getDb, ra, tx, { + getAgent: (id) => entity.getAgent(id), + wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo), + recordConfigRevision: (input) => documents.recordConfigRevision(input), + cancelRunForDeletion: (runId, taskId, workspaceId, summary) => runs.cancelRunForDeletion(runId, taskId, workspaceId, summary), + addActivity: (input) => activity.addActivity(input), + }); + const approvals = buildApprovalMethods(getDb, ra, tx, { + getAgent: (id) => entity.getAgent(id), + }); + const tasks = buildTaskMethods(getDb, ra, tx, { + addComment: (input) => activity.addComment(input), + addActivity: (input) => activity.addActivity(input), + requestApproval: (input) => approvals.requestApproval(input), + setHeartbeat: (agentId, enabled, intervalSec) => agents.setHeartbeat(agentId, enabled, intervalSec), + }); + const messages = buildMessageMethods(getDb, ra, { + getAgent: (id) => entity.getAgent(id), + addActivity: (input) => activity.addActivity(input), + transaction: tx, + }); + const workspaces = buildWorkspaceMethods(getDb, ra, tx); + const social = buildSocialMethods(getDb, ra, tx); + const skills = buildSkillMethods(getDb, ra); + const budget = buildBudgetMethods(getDb, ra, tx); + const automationRules = buildAutomationMethods(getDb, ra, { + addActivity: (input) => activity.addActivity(input), + }); + const knowledge = buildKnowledgeMethods(getDb, ra); + const wakeup = buildWakeupMethods(getDb, ra, tx); + const sessionEntries = buildSessionEntryMethods(getDb, ra, tx); + const inbox = buildInboxProjectionMethods(getDb); + const documentLifecycleEvents = buildDocumentLifecycleEventMethods(getDb, ra); + const credentials = buildCredentialMethods(getDb, ra, tx); + const modelCapabilities = buildModelCapabilityMethods(getDb, ra, tx); + const backup = buildBackupMethods({ getDb, setDb: (db) => { this.db = db; }, openConnection: () => this.openConnection(), init: () => this.init(), @@ -172,6 +180,31 @@ export class AppDatabaseBase { backupsDir: this.backupsDir, appVersion: this.appVersion, }); + + this.core = { + threads, + company, + agents, + tasks, + messages, + runs, + settings, + connectors, + workspaces, + approvals, + social, + activity, + skills, + budget, + entity, + documents, + knowledge, + backup, + credentials, + modelCapabilities, + }; + this.automation = { rules: automationRules, wakeup }; + this.projection = { snapshot, sessionEntries, inbox, documentLifecycleEvents }; } // ─── Lifecycle ───────────────────────────────────────────────────────────── @@ -179,9 +212,9 @@ export class AppDatabaseBase { init() { const ra = (sql: string, ...params: SQLInputValue[]) => this.readAll(sql, ...params); const tx = (op: () => T) => this.transaction(op); - initializeDatabase(this.db, ra, tx, (reason) => this.backupMod.createAutomaticBackup(reason)); - this.connectorsCrud.seedConnectors(); - this.modelCapabilities.seedModelCapabilities(MODEL_CAPABILITY_SEED); + initializeDatabase(this.db, ra, tx, (reason) => this.core.backup.createAutomaticBackup(reason)); + this.core.connectors.seedConnectors(); + this.core.modelCapabilities.seedModelCapabilities(MODEL_CAPABILITY_SEED); // Crash-recovery: a run still at `running` on boot means the worker // died mid-turn. Flip it to `interrupted`, surface the related task // as `blocked` for operator attention, and release workspace locks. @@ -189,15 +222,15 @@ export class AppDatabaseBase { // all other "data-repair" passes have moved to explicit runtime // state-machine guards (see `tasks-status.ts`) or maintenance IPC. this._lastInterruptedCount = this.interruptStaleRuns(); - this.settings.purgeExpiredRunLogs(); - this.wakeup.purgeExpiredWakeupRequests(); + this.core.settings.purgeExpiredRunLogs(); + this.automation.wakeup.purgeExpiredWakeupRequests(); } close() { this.db.close(); } // ─── Internal helper: relock releaseAllWorkspaceLocks is used by // crash-recovery below; callers should prefer - // `db.workspacesMod.releaseAllWorkspaceLocks()` directly. + // `db.core.workspaces.releaseAllWorkspaceLocks()` directly. // // ─── Internal infrastructure ─────────────────────────────────────────────── protected _lastInterruptedCount = 0; @@ -222,7 +255,7 @@ export class AppDatabaseBase { // so a boot that crashed while workers were already idle can // still reset agent rows parked at `status = 'running'`. this.db.prepare("update agents set status = 'idle', updated_at = ? where status = 'running'").run(ts); - this.workspacesMod.releaseAllWorkspaceLocks(); + this.core.workspaces.releaseAllWorkspaceLocks(); return; } const runIds = staleRuns.map((r) => r.id); @@ -244,7 +277,7 @@ export class AppDatabaseBase { ) .run(...runIds); this.db.prepare("update agents set status = 'idle', updated_at = ? where status = 'running'").run(ts); - this.workspacesMod.releaseAllWorkspaceLocks(); + this.core.workspaces.releaseAllWorkspaceLocks(); }); return interruptedCount; } @@ -267,7 +300,7 @@ export class AppDatabaseBase { } protected incrementActiveSprintVelocity(companyId: string, delta: number) { - this.entityCrud.incrementActiveSprintVelocity(companyId, delta); + this.core.entity.incrementActiveSprintVelocity(companyId, delta); } protected getSetting(key: string): string | null { diff --git a/src/main/database.ts b/src/main/database.ts index 1c448717..2ddc73ed 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -4,10 +4,10 @@ import { AppDatabaseBase } from "./database-base"; export { STALE_LOCK_TIMEOUT_MS } from "./constants"; /** - * AppDatabase — concrete persistence surface wired to the twenty-six - * domain modules declared on AppDatabaseBase. New code must call the - * owning module directly (e.g. `db.tasks.saveTask(...)`, not - * `db.saveTask(...)`); the ESLint rule in .eslintrc enforces this. + * AppDatabase — concrete persistence surface wired to the grouped + * modules declared on AppDatabaseBase. New code must call the owning + * module directly (e.g. `db.core.tasks.saveTask(...)`); the ESLint + * rule in .eslintrc enforces this. * * The only members living here are cross-cutting composites whose * single-module home does not exist yet (createHireRequest, @@ -17,28 +17,28 @@ export { STALE_LOCK_TIMEOUT_MS } from "./constants"; export class AppDatabase extends AppDatabaseBase { /** * Create a hire request by persisting the provisional agent row and - * its approval atomically. The composition spans `agents` and - * `approvalsMod`, so it lives here until a dedicated hiring module + * its approval atomically. The composition spans `core.agents` and + * `core.approvals`, so it lives here until a dedicated hiring module * owns both sides. */ - createHireRequest(input: Parameters[0]) { - return this.entityCrud.createHireRequest(input, { - saveAgent: (agentInput) => this.agents.saveAgent(agentInput), - requestApproval: (approvalInput) => this.approvalsMod.requestApproval(approvalInput), + createHireRequest(input: Parameters[0]) { + return this.core.entity.createHireRequest(input, { + saveAgent: (agentInput) => this.core.agents.saveAgent(agentInput), + requestApproval: (approvalInput) => this.core.approvals.requestApproval(approvalInput), }); } /** * Roll back an agent configuration revision. Spans documents - * (revision store), entityCrud (agent lookup), and a recorded + * (revision store), core.entity (agent lookup), and a recorded * rollback entry — again a multi-module composite the subclass * carries until a dedicated agent-config module owns the flow. */ rollbackConfigRevision(agentId: string, revisionId: string, companyId: string) { - return this.documentsMod.rollbackConfigRevision(agentId, revisionId, companyId, { - getConfigRevision: (rid, cid) => this.documentsMod.getConfigRevision(rid, cid), - getAgent: (id) => this.entityCrud.getAgent(id), - recordConfigRevision: (input) => this.documentsMod.recordConfigRevision(input), + return this.core.documents.rollbackConfigRevision(agentId, revisionId, companyId, { + getConfigRevision: (rid, cid) => this.core.documents.getConfigRevision(rid, cid), + getAgent: (id) => this.core.entity.getAgent(id), + recordConfigRevision: (input) => this.core.documents.recordConfigRevision(input), }); } diff --git a/src/main/document-lifecycle-processor.ts b/src/main/document-lifecycle-processor.ts index 76dce85d..b48e1f56 100644 --- a/src/main/document-lifecycle-processor.ts +++ b/src/main/document-lifecycle-processor.ts @@ -82,7 +82,7 @@ export class DocumentLifecycleProcessor { /** Immediate drain scoped to one document, used right after commit. */ drainForDocument(documentId: string): void { - const batch = this.options.db.documentLifecycleEvents.claimPendingBatch({ + const batch = this.options.db.projection.documentLifecycleEvents.claimPendingBatch({ limit: this.options.batchSize, maxAttempts: this.options.maxAttempts, documentId, @@ -92,7 +92,7 @@ export class DocumentLifecycleProcessor { /** Periodic sweep: rescues stragglers across all documents. */ processBatch(): number { - const batch = this.options.db.documentLifecycleEvents.claimPendingBatch({ + const batch = this.options.db.projection.documentLifecycleEvents.claimPendingBatch({ limit: this.options.batchSize, maxAttempts: this.options.maxAttempts, }); @@ -103,7 +103,7 @@ export class DocumentLifecycleProcessor { /** Move rows stuck in `running` back to `pending`. */ reclaimStale(): number { const cutoff = new Date(Date.now() - this.options.staleRunningMs).toISOString(); - const reclaimed = this.options.db.documentLifecycleEvents.reclaimStaleRunning(cutoff); + const reclaimed = this.options.db.projection.documentLifecycleEvents.reclaimStaleRunning(cutoff); if (reclaimed > 0) { this.options.logger.info( `[document-lifecycle] reclaimed ${reclaimed} stale running row(s) past ${this.options.staleRunningMs}ms cutoff`, @@ -155,7 +155,7 @@ export class DocumentLifecycleProcessor { const handler = this.handlers.get(row.hookType); if (!handler) { const reason = `No handler registered for hook type "${row.hookType}"`; - this.options.db.documentLifecycleEvents.markFailed(row.id, reason); + this.options.db.projection.documentLifecycleEvents.markFailed(row.id, reason); this.options.logger.error(`[document-lifecycle] ${reason} (event ${row.id})`); return; } @@ -165,7 +165,7 @@ export class DocumentLifecycleProcessor { payload = row.payloadJson ? (JSON.parse(row.payloadJson) as Record) : {}; } catch (error) { const reason = `payload_json parse failed: ${describeError(error)}`; - this.options.db.documentLifecycleEvents.markFailed(row.id, reason); + this.options.db.projection.documentLifecycleEvents.markFailed(row.id, reason); this.options.logger.error(`[document-lifecycle] ${reason} (event ${row.id})`); return; } @@ -191,21 +191,21 @@ export class DocumentLifecycleProcessor { private recordOutcome(row: DocumentLifecycleEventRow, outcome: LifecycleHookOutcome): void { try { if (outcome.kind === "succeeded") { - this.options.db.documentLifecycleEvents.markSucceeded(row.id); + this.options.db.projection.documentLifecycleEvents.markSucceeded(row.id); return; } if (outcome.kind === "failed") { - this.options.db.documentLifecycleEvents.markFailed(row.id, outcome.reason); + this.options.db.projection.documentLifecycleEvents.markFailed(row.id, outcome.reason); return; } if (row.attempts >= this.options.maxAttempts) { - this.options.db.documentLifecycleEvents.markFailed( + this.options.db.projection.documentLifecycleEvents.markFailed( row.id, `Retry budget exhausted (${row.attempts}/${this.options.maxAttempts}): ${outcome.reason}`, ); return; } - this.options.db.documentLifecycleEvents.markRetryable(row.id, outcome.reason); + this.options.db.projection.documentLifecycleEvents.markRetryable(row.id, outcome.reason); } catch (error) { this.options.logger.warn( `[document-lifecycle] recordOutcome failed for ${row.id}: ${describeError(error)}`, diff --git a/src/main/document-lifecycle.ts b/src/main/document-lifecycle.ts index 52e8feee..c60b1639 100644 --- a/src/main/document-lifecycle.ts +++ b/src/main/document-lifecycle.ts @@ -84,7 +84,7 @@ export function createDocumentWithLifecycle( ): DocumentLifecycleOutcome { const outcome = deps.db.withTransaction(() => { const isNew = !input.id; - const id = deps.db.documentsMod.saveDocument(input); + const id = deps.db.core.documents.saveDocument(input); const taskId = deriveTaskIdFromRun(deps.db, input.sourceRunId, input.companyId, deps.logger); let deliverableReviewApprovalId: string | null = null; @@ -114,7 +114,7 @@ export function createDocumentWithLifecycle( // memoised closure — so a crash between this commit and the first // handler attempt is self-healing: the periodic sweeper picks the // row up on its next tick. - deps.db.documentLifecycleEvents.enqueue({ + deps.db.projection.documentLifecycleEvents.enqueue({ companyId: input.companyId, documentId: id, hookType: "automation_dispatch", @@ -126,7 +126,7 @@ export function createDocumentWithLifecycle( }); if (input.reviewerAgentId && input.reviewerAgentId !== input.authorAgentId) { - deps.db.documentLifecycleEvents.enqueue({ + deps.db.projection.documentLifecycleEvents.enqueue({ companyId: input.companyId, documentId: id, hookType: "reviewer_wake", @@ -164,7 +164,7 @@ function deriveTaskIdFromRun( ): string | null { if (!runId) return null; try { - const run = db.runs.getRun(runId); + const run = db.core.runs.getRun(runId); if (run.companyId !== companyId) return null; return run.taskId ?? null; } catch (error) { @@ -187,12 +187,12 @@ function maybeRaiseDeliverableReviewApproval( documentId: string, taskId: string | null, ): string | null { - const companySettings = deps.db.company.getCompanyApprovalSettings(input.companyId); + const companySettings = deps.db.core.company.getCompanyApprovalSettings(input.companyId); if (!companySettings?.reviewDeliverables) return null; - if (deps.db.company.getCompanyExecutionMode(input.companyId) === "ai_cofounder") return null; - if (deps.db.tasks.hasPendingDeliverableReviews(input.companyId)) return null; + if (deps.db.core.company.getCompanyExecutionMode(input.companyId) === "ai_cofounder") return null; + if (deps.db.core.tasks.hasPendingDeliverableReviews(input.companyId)) return null; - const approvalId = deps.db.approvalsMod.requestApproval({ + const approvalId = deps.db.core.approvals.requestApproval({ companyId: input.companyId, relatedTaskId: taskId, requestedByAgentId: input.authorAgentId ?? "", @@ -207,9 +207,9 @@ function maybeRaiseDeliverableReviewApproval( if (input.authorAgentId) { try { - const agent = deps.db.entityCrud.getAgent(input.authorAgentId); + const agent = deps.db.core.entity.getAgent(input.authorAgentId); if (agent.heartbeatEnabled) { - deps.db.agents.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120); + deps.db.core.agents.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120); } } catch (error) { deps.logger.warn( diff --git a/src/main/execution-mode-reconciler.ts b/src/main/execution-mode-reconciler.ts index c6fb5118..31fdc0c9 100644 --- a/src/main/execution-mode-reconciler.ts +++ b/src/main/execution-mode-reconciler.ts @@ -27,12 +27,12 @@ const REVIEW_WAIT_STATE_TYPES = new Set(["user_reply", "user_review", "user_appr function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: string): Set { const agentIds = new Set(); - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); for (const waitState of waitStates) { - const thread = db.threads.getThread(waitState.threadId, companyId); + const thread = db.core.threads.getThread(waitState.threadId, companyId); if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue; try { - const task = db.entityCrud.getTask(thread.relatedEntityId); + const task = db.core.entity.getTask(thread.relatedEntityId); if (task.assigneeAgentId) { agentIds.add(task.assigneeAgentId); } @@ -69,7 +69,7 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri } if (!approval.relatedTaskId) continue; try { - const task = db.entityCrud.getTask(approval.relatedTaskId); + const task = db.core.entity.getTask(approval.relatedTaskId); if (task.assigneeAgentId) { agentIds.add(task.assigneeAgentId); } @@ -84,11 +84,11 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri function resumeAgents(db: AppDatabase, companyId: string, agentIds: Iterable): void { for (const agentId of agentIds) { try { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.companyId !== companyId) continue; if (["terminated", "pending_approval"].includes(agent.status)) continue; if (!agent.heartbeatEnabled) { - db.agents.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120); } } catch (err) { log.warn(`[execution-mode-reconciler] resumeAgents failed for ${agentId}:`, err instanceof Error ? err.message : String(err)); @@ -143,10 +143,10 @@ export function reconcileExecutionModeChange({ }); } - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); for (const waitState of waitStates) { if (!REVIEW_WAIT_STATE_TYPES.has(waitState.waitingFor)) continue; - db.threads.resolveThreadWaitState(waitState.id, companyId); + db.core.threads.resolveThreadWaitState(waitState.id, companyId); } } @@ -156,7 +156,7 @@ export function reconcileExecutionModeChange({ companyId, ); for (const task of tasks) { - db.tasks.setTaskRequiresUserReview(task.id, false); + db.core.tasks.setTaskRequiresUserReview(task.id, false); } } diff --git a/src/main/execution-policy.ts b/src/main/execution-policy.ts index 781afcc4..0d0bbcd2 100644 --- a/src/main/execution-policy.ts +++ b/src/main/execution-policy.ts @@ -75,7 +75,7 @@ export function shouldAutoApprove( companyId: string, approvalType: string, ): boolean { - const rawMode = db.company.getCompanyExecutionMode(companyId); + const rawMode = db.core.company.getCompanyExecutionMode(companyId); assertExecutionMode(rawMode); const mode = rawMode; @@ -89,7 +89,7 @@ export function shouldAutoApprove( return false; } - const settings = db.company.getCompanyApprovalSettings(companyId); + const settings = db.core.company.getCompanyApprovalSettings(companyId); if (!settings?.autoApproveHires) return false; // When reviewDeliverables is ON, CEO strategy needs human review diff --git a/src/main/hire-hook.ts b/src/main/hire-hook.ts index 064e8bdf..7bb15547 100644 --- a/src/main/hire-hook.ts +++ b/src/main/hire-hook.ts @@ -36,7 +36,7 @@ export async function notifyHireApproved( try { let agent; try { - agent = db.entityCrud.getAgent(input.agentId); + agent = db.core.entity.getAgent(input.agentId); } catch (err) { const classified = classifyUnknownError(err, { kind: "recoverable", @@ -77,7 +77,7 @@ export async function notifyHireApproved( }; log.info(`[hire-hook] Hire approved for agent ${agent.name} (${input.agentId}) on connector ${connectorDef.id}`); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: input.companyId, actor: "system", action: "hire_hook.success", @@ -95,7 +95,7 @@ export async function notifyHireApproved( const message = error instanceof Error ? error.message : String(error); log.error(`[${classified.code}] ${classified.message}: ${message}`); try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: input.companyId, actor: "system", action: "hire_hook.exception", diff --git a/src/main/ipc-agent-coordination-handlers.ts b/src/main/ipc-agent-coordination-handlers.ts index 8b7e2824..11a52ce8 100644 --- a/src/main/ipc-agent-coordination-handlers.ts +++ b/src/main/ipc-agent-coordination-handlers.ts @@ -56,15 +56,15 @@ export function registerAgentCoordinationHandlers({ const parsed = markMessageReadSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const message = db.messages.getAgentMessage(parsed.messageId, parsed.companyId); + const message = db.core.messages.getAgentMessage(parsed.messageId, parsed.companyId); if (!message) { return fail("MESSAGE_NOT_FOUND", "Message not found in the current company."); } - if (parsed.readerAgentId && !db.settings.belongsToCompany("agents", parsed.readerAgentId, parsed.companyId)) { + if (parsed.readerAgentId && !db.core.settings.belongsToCompany("agents", parsed.readerAgentId, parsed.companyId)) { return fail("INVALID_COMPANY_REFERENCE", "The selected message reader does not belong to the current company."); } if (parsed.readerAgentId) { - const visibleToReader = db.messages + const visibleToReader = db.core.messages .listAgentMessages(parsed.companyId, { agentId: parsed.readerAgentId, messageId: parsed.messageId, @@ -75,7 +75,7 @@ export function registerAgentCoordinationHandlers({ return fail("MESSAGE_NOT_VISIBLE", "This message is not visible to the selected reader."); } } - db.messages.markMessageReadForReader(parsed.messageId, parsed.companyId, parsed.readerAgentId ?? null); + db.core.messages.markMessageReadForReader(parsed.messageId, parsed.companyId, parsed.readerAgentId ?? null); publishDomainChanged(); return ok(true); }); @@ -84,7 +84,7 @@ export function registerAgentCoordinationHandlers({ const parsed = searchMessagesSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - return ok(db.messages.searchMessages(parsed.companyId, parsed.query, { + return ok(db.core.messages.searchMessages(parsed.companyId, parsed.query, { channel: parsed.channel, channelTargetId: parsed.channelTargetId, limit: parsed.limit, diff --git a/src/main/ipc-automation-operations-handlers.ts b/src/main/ipc-automation-operations-handlers.ts index f0ffe957..e8382702 100644 --- a/src/main/ipc-automation-operations-handlers.ts +++ b/src/main/ipc-automation-operations-handlers.ts @@ -29,7 +29,7 @@ export function registerAutomationOperationsHandlers({ const parsed = automationRuleInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const id = db.automationMod.saveAutomationRule(parsed); + const id = db.automation.rules.saveAutomationRule(parsed); publishDomainChanged(); return ok(id); }); @@ -38,10 +38,10 @@ export function registerAutomationOperationsHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Automation rule not found in the current company."); } - db.automationMod.deleteAutomationRule(parsed.id, parsed.companyId); + db.automation.rules.deleteAutomationRule(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); @@ -50,10 +50,10 @@ export function registerAutomationOperationsHandlers({ const parsed = toggleAutomationRuleSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Automation rule not found in the current company."); } - db.automationMod.toggleAutomationRule(parsed.id, parsed.companyId, parsed.status); + db.automation.rules.toggleAutomationRule(parsed.id, parsed.companyId, parsed.status); publishDomainChanged(); return ok(true); }); @@ -62,14 +62,14 @@ export function registerAutomationOperationsHandlers({ const parsed = companyIdSchema.parse(companyId); const companyError = ensureCompanyExists(parsed); if (companyError) return companyError; - return ok(db.automationMod.getAutomationLog(parsed)); + return ok(db.automation.rules.getAutomationLog(parsed)); }); registerHandle(IPC_CHANNELS.saveWorkflow, async (_event, payload) => { const parsed = workflowInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const id = db.automationMod.saveWorkflow(parsed); + const id = db.automation.rules.saveWorkflow(parsed); publishDomainChanged(); return ok(id); }); @@ -78,10 +78,10 @@ export function registerAutomationOperationsHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Workflow not found in the current company."); } - db.automationMod.deleteWorkflow(parsed.id, parsed.companyId); + db.automation.rules.deleteWorkflow(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); @@ -90,7 +90,7 @@ export function registerAutomationOperationsHandlers({ const parsed = runWorkflowSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Workflow not found in the current company."); } await runWorkflowPipeline(parsed.id, parsed.companyId); diff --git a/src/main/ipc-browser-handlers.ts b/src/main/ipc-browser-handlers.ts index d028acad..7d764b7a 100644 --- a/src/main/ipc-browser-handlers.ts +++ b/src/main/ipc-browser-handlers.ts @@ -30,7 +30,7 @@ export function registerBrowserHandlers({ const input = socialAccountInputSchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; - const id = db.social.saveSocialAccount(input); + const id = db.core.social.saveSocialAccount(input); publishDomainChanged(); return ok(id); }); @@ -39,12 +39,12 @@ export function registerBrowserHandlers({ const input = deleteEntitySchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; - const browserActions = db.social.listBrowserActions(input.companyId, { socialAccountId: input.id, limit: 500 }); + const browserActions = db.core.social.listBrowserActions(input.companyId, { socialAccountId: input.id, limit: 500 }); await getBrowserActions()?.clearSocialAccountData( input.id, browserActions.flatMap((action: { screenshotPath: string | null }) => (action.screenshotPath ? [action.screenshotPath] : [])), ); - db.social.deleteSocialAccount(input.id, input.companyId); + db.core.social.deleteSocialAccount(input.id, input.companyId); publishDomainChanged(); return ok(true); }); @@ -53,7 +53,7 @@ export function registerBrowserHandlers({ const input = browserActionQuerySchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; - return ok(db.social.listBrowserActions(input.companyId, { + return ok(db.core.social.listBrowserActions(input.companyId, { socialAccountId: input.socialAccountId, status: input.status, limit: input.limit, @@ -62,12 +62,12 @@ export function registerBrowserHandlers({ registerHandle(IPC_CHANNELS.cancelBrowserAction, (_event, payload) => { const input = cancelBrowserActionSchema.parse(payload); - const action = db.social.getBrowserAction(input.actionId, input.companyId); + const action = db.core.social.getBrowserAction(input.actionId, input.companyId); if (!action) { return fail("BROWSER_ACTION_NOT_FOUND", "Browser action not found."); } getBrowserActions()?.cancelAction(input.actionId); - db.social.updateBrowserAction(input.actionId, { + db.core.social.updateBrowserAction(input.actionId, { status: "cancelled", resultSummary: "Browser action cancelled from the desktop control surface.", errorMessage: "Action cancelled.", @@ -86,7 +86,7 @@ export function registerBrowserHandlers({ return fail("BROWSER_NOT_READY", "Browser manager not initialized"); } - const account = db.social.getSocialAccount(input.socialAccountId, input.companyId); + const account = db.core.social.getSocialAccount(input.socialAccountId, input.companyId); if (!account) { return fail("ACCOUNT_NOT_FOUND", "Social account not found"); } @@ -97,7 +97,7 @@ export function registerBrowserHandlers({ inflightBrowserLogins.add(input.socialAccountId); browserActions.launchLoginBrowser(input.socialAccountId, account.platform).then(async (result) => { if (result.success) { - const latestAccount = db.social.getSocialAccount(input.socialAccountId, account.companyId); + const latestAccount = db.core.social.getSocialAccount(input.socialAccountId, account.companyId); if (!latestAccount) { return; } @@ -110,7 +110,7 @@ export function registerBrowserHandlers({ } catch (err) { logger.warn(`[ipc/browser] extractProfileFromSession failed for social account ${input.socialAccountId}:`, err instanceof Error ? err.message : String(err)); } - db.social.saveSocialAccount({ + db.core.social.saveSocialAccount({ id: latestAccount.id, companyId: latestAccount.companyId, platform: latestAccount.platform, diff --git a/src/main/ipc-budget-handlers.ts b/src/main/ipc-budget-handlers.ts index 4dcd7045..2af66d6e 100644 --- a/src/main/ipc-budget-handlers.ts +++ b/src/main/ipc-budget-handlers.ts @@ -32,7 +32,7 @@ export function registerBudgetHandlers({ const parsed = budgetPolicyInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const id = db.budget.saveBudgetPolicy(parsed); + const id = db.core.budget.saveBudgetPolicy(parsed); // D-06: Auto-resolve incidents if limit was increased above current spend if (budgetService) { try { @@ -50,7 +50,7 @@ export function registerBudgetHandlers({ const parsed = deleteBudgetPolicySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - db.budget.deleteBudgetPolicy(parsed.companyId, parsed.policyId); + db.core.budget.deleteBudgetPolicy(parsed.companyId, parsed.policyId); publishDomainChanged(); return ok(true); }); @@ -60,7 +60,7 @@ export function registerBudgetHandlers({ const parsed = listBudgetPoliciesSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - return ok(db.budget.listBudgetPolicies(parsed.companyId)); + return ok(db.core.budget.listBudgetPolicies(parsed.companyId)); }); // ---- List budget incidents ---- @@ -68,7 +68,7 @@ export function registerBudgetHandlers({ const parsed = listBudgetIncidentsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - return ok(db.budget.listBudgetIncidents(parsed.companyId, parsed.status)); + return ok(db.core.budget.listBudgetIncidents(parsed.companyId, parsed.status)); }); // ---- Resolve a budget incident manually ---- @@ -76,7 +76,7 @@ export function registerBudgetHandlers({ const parsed = resolveBudgetIncidentSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - db.budget.resolveBudgetIncident(parsed.incidentId, parsed.companyId); + db.core.budget.resolveBudgetIncident(parsed.incidentId, parsed.companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-company-operations/document-handlers.ts b/src/main/ipc-company-operations/document-handlers.ts index 6005b2d1..27c79839 100644 --- a/src/main/ipc-company-operations/document-handlers.ts +++ b/src/main/ipc-company-operations/document-handlers.ts @@ -63,10 +63,10 @@ export function registerDocumentHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("documents", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("documents", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Document not found in the current company."); } - db.documentsMod.deleteDocument(parsed.id, parsed.companyId); + db.core.documents.deleteDocument(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-company-operations/knowledge-entry-handlers.ts b/src/main/ipc-company-operations/knowledge-entry-handlers.ts index 202d448a..5a28fae7 100644 --- a/src/main/ipc-company-operations/knowledge-entry-handlers.ts +++ b/src/main/ipc-company-operations/knowledge-entry-handlers.ts @@ -28,7 +28,7 @@ export function registerKnowledgeEntryHandlers({ return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const id = db.knowledgeMod.saveKnowledgeEntry(parsed); + const id = db.core.knowledge.saveKnowledgeEntry(parsed); publishDomainChanged(); return ok(id); }); @@ -37,10 +37,10 @@ export function registerKnowledgeEntryHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("knowledge_base", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("knowledge_base", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Knowledge entry not found in the current company."); } - db.knowledgeMod.deleteKnowledgeEntry(parsed.id, parsed.companyId); + db.core.knowledge.deleteKnowledgeEntry(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-company-operations/meeting-handlers.ts b/src/main/ipc-company-operations/meeting-handlers.ts index 3fa11fd6..17018455 100644 --- a/src/main/ipc-company-operations/meeting-handlers.ts +++ b/src/main/ipc-company-operations/meeting-handlers.ts @@ -34,7 +34,7 @@ export function registerMeetingHandlers({ return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const id = db.knowledgeMod.saveMeeting(parsed); + const id = db.core.knowledge.saveMeeting(parsed); publishDomainChanged(); return ok(id); }); @@ -43,10 +43,10 @@ export function registerMeetingHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("meetings", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("meetings", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Meeting not found in the current company."); } - db.knowledgeMod.deleteMeeting(parsed.id, parsed.companyId); + db.core.knowledge.deleteMeeting(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-company-operations/sprint-handlers.ts b/src/main/ipc-company-operations/sprint-handlers.ts index 100d7b33..63ab9857 100644 --- a/src/main/ipc-company-operations/sprint-handlers.ts +++ b/src/main/ipc-company-operations/sprint-handlers.ts @@ -18,14 +18,14 @@ export function registerSprintHandlers({ let previousSprintStatus: string | null = null; if (parsed.id) { try { - previousSprintStatus = db.snap.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null; + previousSprintStatus = db.projection.snapshot.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null; } catch (err) { logger.warn(`[ipc/sprints] listSnapshot failed while resolving sprint ${parsed.id} previous status:`, err instanceof Error ? err.message : String(err)); previousSprintStatus = null; } } - const id = db.knowledgeMod.saveSprint(parsed); + const id = db.core.knowledge.saveSprint(parsed); if (parsed.status === "active" && previousSprintStatus !== "active") { dispatchAutomation("sprint_started", { companyId: parsed.companyId }); } @@ -40,10 +40,10 @@ export function registerSprintHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("sprints", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("sprints", parsed.id, parsed.companyId)) { return fail("ENTITY_NOT_FOUND", "Sprint not found in the current company."); } - db.knowledgeMod.deleteSprint(parsed.id, parsed.companyId); + db.core.knowledge.deleteSprint(parsed.id, parsed.companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-core-admin-handlers.ts b/src/main/ipc-core-admin-handlers.ts index c64e8e2e..fdd4bde6 100644 --- a/src/main/ipc-core-admin-handlers.ts +++ b/src/main/ipc-core-admin-handlers.ts @@ -46,11 +46,11 @@ export function registerCoreAdminHandlers({ >) { registerHandle(IPC_CHANNELS.saveCompany, async (_event, payload) => { const parsed = companyInputSchema.parse(payload); - const previousMode = parsed.id ? db.company.getCompanyExecutionMode(parsed.id) : parsed.executionMode; + const previousMode = parsed.id ? db.core.company.getCompanyExecutionMode(parsed.id) : parsed.executionMode; const previousApprovalSettings = parsed.id - ? db.company.getCompanyApprovalSettings(parsed.id) + ? db.core.company.getCompanyApprovalSettings(parsed.id) : { autoApproveHires: parsed.autoApproveHires, reviewDeliverables: parsed.reviewDeliverables }; - const id = db.company.saveCompany(parsed); + const id = db.core.company.saveCompany(parsed); reconcileExecutionModeChange({ db, companyId: id, @@ -60,7 +60,7 @@ export function registerCoreAdminHandlers({ nextReviewDeliverables: parsed.reviewDeliverables, handleApprovalResolved, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: id, actor: "board", action: parsed.id ? "company.updated" : "company.created", @@ -76,10 +76,10 @@ export function registerCoreAdminHandlers({ if (typeof companyId !== "string" || !companyId) { return fail("INVALID_COMPANY_ID", "A valid company id is required."); } - if (!db.settings.hasCompany(companyId)) { + if (!db.core.settings.hasCompany(companyId)) { return fail("COMPANY_NOT_FOUND", "The selected company does not exist."); } - db.settings.setCurrentCompany(companyId); + db.core.settings.setCurrentCompany(companyId); publishDomainChanged(); return ok(true); }); @@ -93,12 +93,12 @@ export function registerCoreAdminHandlers({ // The per-company disabled-models pool is not consulted here — // connector config is a global hint, and pool enforcement // happens at run dispatch time where the company is known. - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ ...parsed, model: parsed.model ?? null, }); - db.activityMod.addActivity({ - companyId: db.settings.getCurrentCompanyId() ?? "00000000-0000-0000-0000-000000000000", + db.core.activity.addActivity({ + companyId: db.core.settings.getCurrentCompanyId() ?? "00000000-0000-0000-0000-000000000000", actor: "board", action: "connector.updated", entityType: "connector", @@ -127,7 +127,7 @@ export function registerCoreAdminHandlers({ registerHandle(IPC_CHANNELS.setConnectorDisabledModels, async (_event, payload) => { const parsed = connectorDisabledModelsSchema.parse(payload); - db.settings.setDisabledModels(parsed.companyId, parsed.connectorId, parsed.disabledModels); + db.core.settings.setDisabledModels(parsed.companyId, parsed.connectorId, parsed.disabledModels); publishDomainChanged(); return ok(true); }); @@ -136,11 +136,11 @@ export function registerCoreAdminHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("secrets", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("secrets", parsed.id, parsed.companyId)) { return fail("SECRET_NOT_FOUND", "Secret not found in the current company."); } - db.entityCrud.deleteSecret(parsed.id, parsed.companyId); - db.activityMod.addActivity({ + db.core.entity.deleteSecret(parsed.id, parsed.companyId); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: "secret.deleted", @@ -156,10 +156,10 @@ export function registerCoreAdminHandlers({ if (typeof companyId !== "string" || companyId.length === 0) { return fail("INVALID_INPUT", "Company ID is required."); } - if (!db.settings.hasCompany(companyId)) { + if (!db.core.settings.hasCompany(companyId)) { return fail("COMPANY_NOT_FOUND", "Company not found."); } - db.company.deleteCompany(companyId); + db.core.company.deleteCompany(companyId); publishDomainChanged(); return ok(true); }); diff --git a/src/main/ipc-core-agent-graph-handlers.ts b/src/main/ipc-core-agent-graph-handlers.ts index 608335e4..d900283c 100644 --- a/src/main/ipc-core-agent-graph-handlers.ts +++ b/src/main/ipc-core-agent-graph-handlers.ts @@ -40,8 +40,8 @@ export function registerCoreAgentGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const id = db.workspacesMod.saveWorkspace(parsed); - db.activityMod.addActivity({ + const id = db.core.workspaces.saveWorkspace(parsed); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.id ? "workspace.updated" : "workspace.created", @@ -64,7 +64,7 @@ export function registerCoreAgentGraphHandlers({ "Create new agents through the hiring workflow so approval, onboarding, and org state stay synchronized.", ); } - const existingAgent = parsed.id ? db.entityCrud.findAgent(parsed.id) : null; + const existingAgent = parsed.id ? db.core.entity.findAgent(parsed.id) : null; if (!existingAgent || existingAgent.companyId !== parsed.companyId) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } @@ -87,7 +87,7 @@ export function registerCoreAgentGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const connectorRecord = db.connectorsCrud.findConnector(parsed.connectorId); + const connectorRecord = db.core.connectors.findConnector(parsed.connectorId); if (!connectorRecord) { return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist."); } @@ -101,7 +101,7 @@ export function registerCoreAgentGraphHandlers({ } let id: string; try { - id = db.agents.saveAgent({ + id = db.core.agents.saveAgent({ id: parsed.id ?? null, companyId: parsed.companyId, name: parsed.name, @@ -124,7 +124,7 @@ export function registerCoreAgentGraphHandlers({ } catch (error) { return fail("INVALID_AGENT_HIERARCHY", error instanceof Error ? error.message : "Agent could not be saved."); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.id ? "agent.updated" : "agent.created", @@ -155,7 +155,7 @@ export function registerCoreAgentGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const connectorRecord = db.connectorsCrud.findConnector(parsed.connectorId); + const connectorRecord = db.core.connectors.findConnector(parsed.connectorId); if (!connectorRecord) { return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist."); } @@ -188,7 +188,7 @@ export function registerCoreAgentGraphHandlers({ const managerLabel = resolvedReportsTo ? (() => { try { - return db.entityCrud.getAgent(resolvedReportsTo).name; + return db.core.entity.getAgent(resolvedReportsTo).name; } catch (err) { logger.warn(`[ipc/agent-graph] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err)); return resolvedReportsTo; @@ -237,7 +237,7 @@ export function registerCoreAgentGraphHandlers({ return fail("HIRE_REQUEST_FAILED", error instanceof Error ? error.message : "Hire request could not be created."); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: parsed.requestedByAgentId ?? "board", action: "agent.hire_requested", @@ -268,18 +268,18 @@ export function registerCoreAgentGraphHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("agents", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("agents", parsed.id, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } - const agent = db.entityCrud.getAgent(parsed.id); + const agent = db.core.entity.getAgent(parsed.id); if (agent.status === "pending_approval") { return fail( "AGENT_APPROVAL_REQUIRED", "Pending hires cannot be deleted from the agent editor. Reject the hire request from Hiring or Approvals instead.", ); } - db.agents.deleteAgent(parsed.id, parsed.companyId); - db.activityMod.addActivity({ + db.core.agents.deleteAgent(parsed.id, parsed.companyId); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: "agent.deleted", @@ -295,18 +295,18 @@ export function registerCoreAgentGraphHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("approvals", parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("approvals", parsed.id, parsed.companyId)) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } - const approval = db.approvalsMod.getApprovalRecord(parsed.id); + const approval = db.core.approvals.getApprovalRecord(parsed.id); if (approval.state === "pending" && approval.type === "hire_agent") { return fail( "APPROVAL_DELETE_BLOCKED", "Pending hire approvals cannot be deleted. Reject the hire request instead so the linked agent record stays consistent.", ); } - db.approvalsMod.deleteApproval(parsed.id, parsed.companyId); - db.activityMod.addActivity({ + db.core.approvals.deleteApproval(parsed.id, parsed.companyId); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: "approval.deleted", diff --git a/src/main/ipc-core-operations-handlers.ts b/src/main/ipc-core-operations-handlers.ts index d976283a..b52290a5 100644 --- a/src/main/ipc-core-operations-handlers.ts +++ b/src/main/ipc-core-operations-handlers.ts @@ -48,19 +48,19 @@ export function registerCoreOperationHandlers({ const parsed = approvalInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const invalidTask = parsed.relatedTaskId && !db.settings.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId); - const invalidAgent = parsed.requestedByAgentId && !db.settings.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId); - const invalidRelatedAgent = parsed.relatedAgentId && !db.settings.belongsToCompany("agents", parsed.relatedAgentId, parsed.companyId); + const invalidTask = parsed.relatedTaskId && !db.core.settings.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId); + const invalidAgent = parsed.requestedByAgentId && !db.core.settings.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId); + const invalidRelatedAgent = parsed.relatedAgentId && !db.core.settings.belongsToCompany("agents", parsed.relatedAgentId, parsed.companyId); if (invalidTask || invalidAgent || invalidRelatedAgent) { return fail("INVALID_COMPANY_REFERENCE", "The approval references data outside the current company."); } let id: string; try { - id = db.approvalsMod.requestApproval(parsed); + id = db.core.approvals.requestApproval(parsed); } catch (error) { return fail("APPROVAL_INVALID", error instanceof Error ? error.message : "Approval could not be created."); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: parsed.requestedByAgentId ?? "board", action: "approval.created", @@ -106,10 +106,10 @@ export function registerCoreOperationHandlers({ const parsed = approvalDecisionSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } - const approval = db.approvalsMod.getApprovalRecord(parsed.approvalId); + const approval = db.core.approvals.getApprovalRecord(parsed.approvalId); if (!approval || approval.companyId !== parsed.companyId) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } @@ -130,7 +130,7 @@ export function registerCoreOperationHandlers({ const parsed = startRunSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { return fail("TASK_NOT_FOUND", "Task not found in the current company."); } return queueTaskRun(parsed.taskId, "manual", "board"); @@ -140,7 +140,7 @@ export function registerCoreOperationHandlers({ const parsed = cancelRunSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } getWorkerProcess()?.postMessage({ type: "cancel-run", runId: parsed.runId }); @@ -151,34 +151,34 @@ export function registerCoreOperationHandlers({ const parsed = runLogSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } - return ok(db.runs.readRunLog(parsed.runId)); + return ok(db.core.runs.readRunLog(parsed.runId)); }); registerHandle(IPC_CHANNELS.getRunLogChunk, (_event, payload) => { const parsed = runLogChunkSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } - return ok(db.runs.readRunLogChunk(parsed.runId, parsed.offset, parsed.limit)); + return ok(db.core.runs.readRunLogChunk(parsed.runId, parsed.offset, parsed.limit)); }); registerHandle(IPC_CHANNELS.addComment, async (_event, payload) => { const parsed = commentInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - const comment = db.activityMod.addComment({ + const comment = db.core.activity.addComment({ companyId: parsed.companyId, taskId: parsed.taskId, authorAgentId: parsed.authorAgentId ?? null, authorName: parsed.authorName, body: parsed.body, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: parsed.authorAgentId ?? "board", action: "comment.created", @@ -189,7 +189,7 @@ export function registerCoreOperationHandlers({ emitEvent({ type: "agent-message", taskId: parsed.taskId, comment }); emitEvent({ type: "task-updated", taskId: parsed.taskId, action: "updated" }); try { - const task = db.entityCrud.getTask(parsed.taskId); + const task = db.core.entity.getTask(parsed.taskId); if (task.assigneeAgentId) { wakeAgentIfPossible(task.assigneeAgentId, task.companyId, "comment"); } @@ -208,11 +208,11 @@ export function registerCoreOperationHandlers({ const parsed = heartbeatSettingsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } - db.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec); - db.activityMod.addActivity({ + db.core.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.enabled ? "heartbeat.enabled" : "heartbeat.disabled", @@ -229,7 +229,7 @@ export function registerCoreOperationHandlers({ const parsed = triggerHeartbeatSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } const runId = wakeAgentIfPossible(parsed.agentId, parsed.companyId, parsed.trigger); diff --git a/src/main/ipc-core-read-handlers.ts b/src/main/ipc-core-read-handlers.ts index 990f6e13..346cdd03 100644 --- a/src/main/ipc-core-read-handlers.ts +++ b/src/main/ipc-core-read-handlers.ts @@ -32,7 +32,7 @@ export function registerCoreReadHandlers({ | "listConnectorModels" | "logger" >) { - registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.snap.listRendererSnapshot())); + registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.projection.snapshot.listRendererSnapshot())); registerHandle(IPC_CHANNELS.pickDirectory, async () => { const result = await dialog.showOpenDialog({ @@ -45,7 +45,7 @@ export function registerCoreReadHandlers({ if (typeof targetPath !== "string" || targetPath.length === 0) { return fail("INVALID_PATH", "A valid path is required."); } - if (!db.settings.canOpenPath(targetPath)) { + if (!db.core.settings.canOpenPath(targetPath)) { return fail("OPEN_PATH_FORBIDDEN", "The requested path is outside the managed desktop workspace."); } const error = await shell.openPath(targetPath); @@ -68,7 +68,7 @@ export function registerCoreReadHandlers({ if (!parsed.success) return fail("INVALID_PAYLOAD", "openWorkProduct requires { companyId, id }."); const companyError = ensureCompanyExists(parsed.data.companyId); if (companyError) return companyError; - const target = db.documentsMod.resolveWorkProductTarget(parsed.data.id, parsed.data.companyId); + const target = db.core.documents.resolveWorkProductTarget(parsed.data.id, parsed.data.companyId); if (target.kind === "error") { const codeByReason = { not_found: "WORK_PRODUCT_NOT_FOUND", @@ -83,7 +83,7 @@ export function registerCoreReadHandlers({ const error = await shell.openExternal(target.value).then(() => "").catch((err: unknown) => err instanceof Error ? err.message : String(err)); return error ? fail("OPEN_PATH_FAILED", error) : ok(true); } - if (!db.settings.canOpenPath(target.value)) { + if (!db.core.settings.canOpenPath(target.value)) { return fail("OPEN_PATH_FORBIDDEN", "The requested path is outside the managed desktop workspace."); } const error = await shell.openPath(target.value); @@ -98,12 +98,12 @@ export function registerCoreReadHandlers({ return ok(null); } const backupPath = join(result.filePaths[0]!, `agentcompany-backup-${Date.now()}`); - await db.backupMod.exportBackup(backupPath); + await db.core.backup.exportBackup(backupPath); return ok(backupPath); }); registerHandle(IPC_CHANNELS.restoreProfile, async () => { - const hasActiveRuns = db.snap.listSnapshot().runs.some((run: { status: string }) => run.status === "queued" || run.status === "running"); + const hasActiveRuns = db.projection.snapshot.listSnapshot().runs.some((run: { status: string }) => run.status === "queued" || run.status === "running"); if (hasActiveRuns) { return fail("RESTORE_BLOCKED", "Stop all active runs before restoring a profile backup."); } @@ -113,7 +113,7 @@ export function registerCoreReadHandlers({ if (result.canceled || result.filePaths.length === 0) { return ok(false); } - await db.backupMod.restoreFromBackup(result.filePaths[0]!); + await db.core.backup.restoreFromBackup(result.filePaths[0]!); resetAfterRestore(); publishDomainChanged(); return ok(true); @@ -121,7 +121,7 @@ export function registerCoreReadHandlers({ registerHandle(IPC_CHANNELS.updateSettings, async (_event, payload) => { const parsed = settingsSchema.parse(payload); - db.settings.updateSettings(parsed); + db.core.settings.updateSettings(parsed); if (parsed.theme) { nativeTheme.themeSource = parsed.theme; } @@ -153,7 +153,7 @@ export function registerCoreReadHandlers({ registerHandle(IPC_CHANNELS.getConnectorDisabledModels, (_event, payload) => { try { const parsed = connectorModelPoolQuerySchema.parse(payload); - return ok(db.settings.getDisabledModels(parsed.companyId, parsed.connectorId)); + return ok(db.core.settings.getDisabledModels(parsed.companyId, parsed.connectorId)); } catch (err) { logAppError(logger, err, { kind: "user_input", @@ -168,14 +168,14 @@ export function registerCoreReadHandlers({ const input = metricsQuerySchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; - return ok(db.company.getCompanyMetrics(input.companyId, input.days)); + return ok(db.core.company.getCompanyMetrics(input.companyId, input.days)); }); registerHandle(IPC_CHANNELS.getStandupReport, (_event, payload) => { const companyId = z.string().uuid().parse(payload); const err = ensureCompanyExists(companyId); if (err) return err; - return ok(db.company.getStandupReport(companyId)); + return ok(db.core.company.getStandupReport(companyId)); }); // --- Entity getters for granular IPC updates --- @@ -185,7 +185,7 @@ export function registerCoreReadHandlers({ const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { - const task = db.entityCrud.getTask(parsed.id); + const task = db.core.entity.getTask(parsed.id); if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company."); return ok(task); } catch (err) { @@ -204,7 +204,7 @@ export function registerCoreReadHandlers({ const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { - const agent = db.entityCrud.getAgent(parsed.id); + const agent = db.core.entity.getAgent(parsed.id); if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company."); return ok(agent); } catch (err) { @@ -223,7 +223,7 @@ export function registerCoreReadHandlers({ const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { - const run = db.runs.getRun(parsed.id); + const run = db.core.runs.getRun(parsed.id); if (run.companyId !== parsed.companyId) return fail("RUN_NOT_FOUND", "Run not found in current company."); return ok(run); } catch (err) { @@ -242,7 +242,7 @@ export function registerCoreReadHandlers({ const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { - const approval = db.approvalsMod.getApprovalRecord(parsed.id); + const approval = db.core.approvals.getApprovalRecord(parsed.id); if (approval.companyId !== parsed.companyId) return fail("APPROVAL_NOT_FOUND", "Approval not found in current company."); return ok(approval); } catch (err) { diff --git a/src/main/ipc-core-task-graph-handlers.ts b/src/main/ipc-core-task-graph-handlers.ts index fecf63fa..c11938e4 100644 --- a/src/main/ipc-core-task-graph-handlers.ts +++ b/src/main/ipc-core-task-graph-handlers.ts @@ -43,8 +43,8 @@ export function registerCoreTaskGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const id = db.entityCrud.saveGoal(parsed); - db.activityMod.addActivity({ + const id = db.core.entity.saveGoal(parsed); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.id ? "goal.updated" : "goal.created", @@ -70,8 +70,8 @@ export function registerCoreTaskGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const id = db.entityCrud.saveProject(parsed); - db.activityMod.addActivity({ + const id = db.core.entity.saveProject(parsed); + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.id ? "project.updated" : "project.created", @@ -113,7 +113,7 @@ export function registerCoreTaskGraphHandlers({ const taskId = parsed.id; if (!taskId) return fail("INVALID_TASK_STATE", "Task ID required for updates."); try { - const existingTask = db.entityCrud.getTask(taskId); + const existingTask = db.core.entity.getTask(taskId); previousStatus = existingTask.status; previousAssigneeAgentId = existingTask.assigneeAgentId; } catch (err) { @@ -124,11 +124,11 @@ export function registerCoreTaskGraphHandlers({ } let id: string; try { - id = db.tasks.saveTask(parsed); + id = db.core.tasks.saveTask(parsed); } catch (error) { return fail("INVALID_TASK_STATE", error instanceof Error ? error.message : "Task could not be saved."); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: isNew ? "task.created" : "task.updated", @@ -145,7 +145,7 @@ export function registerCoreTaskGraphHandlers({ // User (board) manually advancing a flagged task to done: clear the review flag if (parsed.status === "done") { try { - db.tasks.setTaskRequiresUserReview(id, false); + db.core.tasks.setTaskRequiresUserReview(id, false); } catch (error) { logger.warn( `[ipc-task] setTaskRequiresUserReview(false) failed for task ${id}: ${error instanceof Error ? error.message : String(error)}`, @@ -175,10 +175,10 @@ export function registerCoreTaskGraphHandlers({ }); const deleteHandlers = [ - { channel: IPC_CHANNELS.deleteTask, table: "tasks", entity: "task", deleteFn: (id: string, companyId: string) => db.tasks.deleteTask(id, companyId) }, - { channel: IPC_CHANNELS.deleteGoal, table: "goals", entity: "goal", deleteFn: (id: string, companyId: string) => db.entityCrud.deleteGoal(id, companyId) }, - { channel: IPC_CHANNELS.deleteProject, table: "projects", entity: "project", deleteFn: (id: string, companyId: string) => db.entityCrud.deleteProject(id, companyId) }, - { channel: IPC_CHANNELS.deleteWorkspace, table: "workspaces", entity: "workspace", deleteFn: (id: string, companyId: string) => db.workspacesMod.deleteWorkspace(id, companyId) }, + { channel: IPC_CHANNELS.deleteTask, table: "tasks", entity: "task", deleteFn: (id: string, companyId: string) => db.core.tasks.deleteTask(id, companyId) }, + { channel: IPC_CHANNELS.deleteGoal, table: "goals", entity: "goal", deleteFn: (id: string, companyId: string) => db.core.entity.deleteGoal(id, companyId) }, + { channel: IPC_CHANNELS.deleteProject, table: "projects", entity: "project", deleteFn: (id: string, companyId: string) => db.core.entity.deleteProject(id, companyId) }, + { channel: IPC_CHANNELS.deleteWorkspace, table: "workspaces", entity: "workspace", deleteFn: (id: string, companyId: string) => db.core.workspaces.deleteWorkspace(id, companyId) }, ] as const; for (const { channel, table, entity, deleteFn } of deleteHandlers) { @@ -186,11 +186,11 @@ export function registerCoreTaskGraphHandlers({ const parsed = deleteEntitySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany(table, parsed.id, parsed.companyId)) { + if (!db.core.settings.belongsToCompany(table, parsed.id, parsed.companyId)) { return fail(`${entity.toUpperCase()}_NOT_FOUND`, `${entity.charAt(0).toUpperCase() + entity.slice(1)} not found in the current company.`); } deleteFn(parsed.id, parsed.companyId); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: parsed.companyId, actor: "board", action: `${entity}.deleted`, diff --git a/src/main/ipc-recovery-handlers.ts b/src/main/ipc-recovery-handlers.ts index 88c6b89b..d5158f3d 100644 --- a/src/main/ipc-recovery-handlers.ts +++ b/src/main/ipc-recovery-handlers.ts @@ -33,10 +33,10 @@ export function registerRecoveryHandlers({ const parsed = recoveryRetrySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { return fail("TASK_NOT_FOUND", "Task not found in the current company."); } - db.tasks.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId); + db.core.tasks.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId); try { wakeAgentIfPossible(parsed.agentId, parsed.companyId, "retry_interrupted"); } catch (err) { @@ -50,10 +50,10 @@ export function registerRecoveryHandlers({ const parsed = recoveryTaskSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { + if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { return fail("TASK_NOT_FOUND", "Task not found in the current company."); } - db.tasks.markInterruptedTaskFailed(parsed.taskId); + db.core.tasks.markInterruptedTaskFailed(parsed.taskId); publishDomainChanged(); return ok(null); }); diff --git a/src/main/ipc-skill-handlers.ts b/src/main/ipc-skill-handlers.ts index 4e4f4a30..c3b96b3e 100644 --- a/src/main/ipc-skill-handlers.ts +++ b/src/main/ipc-skill-handlers.ts @@ -168,7 +168,7 @@ export function registerSkillHandlers({ const parsed = listSkillAgentsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - return ok(db.skills.listSkillAgentAssignments(parsed.skillId)); + return ok(db.core.skills.listSkillAgentAssignments(parsed.skillId)); }); // ---- List skill IDs assigned to an agent ---- @@ -176,6 +176,6 @@ export function registerSkillHandlers({ const parsed = listAgentSkillsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; - return ok(db.skills.listAgentSkillAssignments(parsed.agentId)); + return ok(db.core.skills.listAgentSkillAssignments(parsed.agentId)); }); } diff --git a/src/main/ipc-thread-handlers.ts b/src/main/ipc-thread-handlers.ts index a6c2efd9..2359af3e 100644 --- a/src/main/ipc-thread-handlers.ts +++ b/src/main/ipc-thread-handlers.ts @@ -13,7 +13,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists const input = threadGetterSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError; - const thread = db.threads.getThread(input.threadId, input.companyId); + const thread = db.core.threads.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found"); return ok(thread); }); @@ -22,7 +22,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists const input = listThreadEventsSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError; - const events = db.threads.listThreadEvents(input.threadId, input.companyId, { + const events = db.core.threads.listThreadEvents(input.threadId, input.companyId, { limit: input.limit, offset: input.offset, }); @@ -33,7 +33,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists const input = listThreadWaitStatesSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError; - const waitStates = db.threads.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly }); + const waitStates = db.core.threads.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly }); return ok(waitStates); }); @@ -41,9 +41,9 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists const input = listThreadParticipantsSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError; - const thread = db.threads.getThread(input.threadId, input.companyId); + const thread = db.core.threads.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found"); - const participants = db.threads.listParticipants(input.threadId); + const participants = db.core.threads.listParticipants(input.threadId); return ok(participants); }); } diff --git a/src/main/main-runtime-support.ts b/src/main/main-runtime-support.ts index d3251bb0..9251767d 100644 --- a/src/main/main-runtime-support.ts +++ b/src/main/main-runtime-support.ts @@ -140,8 +140,8 @@ export function createMainRuntimeSupport(state: MainRuntimeState) { function seedDefaultAutomationRules(companyId: string) { const db = requireDb(); seedDefaultAutomationRulesImpl(companyId, { - listAutomationRuleNames: (targetCompanyId) => db.automationMod.listAutomationRuleNames(targetCompanyId), - saveAutomationRule: (input) => db.automationMod.saveAutomationRule(input), + listAutomationRuleNames: (targetCompanyId) => db.automation.rules.listAutomationRuleNames(targetCompanyId), + saveAutomationRule: (input) => db.automation.rules.saveAutomationRule(input), }); } diff --git a/src/main/messaging/dispatch.ts b/src/main/messaging/dispatch.ts index a606ccc0..025aeadb 100644 --- a/src/main/messaging/dispatch.ts +++ b/src/main/messaging/dispatch.ts @@ -134,7 +134,7 @@ export function validateAgentMessageInput( input: NormalizedAgentMessageInput, ): MessageValidationError | null { // Human sender (BOARD_READER_ID) is always a valid sender — skip agent ownership check. - if (!input.fromHumanUser && !db.settings.belongsToCompany("agents", input.fromAgentId, input.companyId)) { + if (!input.fromHumanUser && !db.core.settings.belongsToCompany("agents", input.fromAgentId, input.companyId)) { return { code: "INVALID_COMPANY_REFERENCE", message: "The message sender does not belong to the current company.", @@ -154,7 +154,7 @@ export function validateAgentMessageInput( }; } - if (input.toAgentId && input.toAgentId !== HUMAN_SENDER_ID && !db.settings.belongsToCompany("agents", input.toAgentId, input.companyId)) { + if (input.toAgentId && input.toAgentId !== HUMAN_SENDER_ID && !db.core.settings.belongsToCompany("agents", input.toAgentId, input.companyId)) { return { code: "INVALID_COMPANY_REFERENCE", message: "The message recipient does not belong to the current company.", @@ -168,7 +168,7 @@ export function validateAgentMessageInput( message: "Project messages require a project channel target.", }; } - if (!db.settings.belongsToCompany("projects", input.channelTargetId, input.companyId)) { + if (!db.core.settings.belongsToCompany("projects", input.channelTargetId, input.companyId)) { return { code: "INVALID_COMPANY_REFERENCE", message: "The selected project channel does not belong to the current company.", @@ -184,7 +184,7 @@ export function validateAgentMessageInput( } if (input.parentMessageId) { - const parentMessage = db.messages.getAgentMessage(input.parentMessageId, input.companyId); + const parentMessage = db.core.messages.getAgentMessage(input.parentMessageId, input.companyId); if (!parentMessage) { return { code: "PARENT_MESSAGE_NOT_FOUND", @@ -201,17 +201,17 @@ function resolveProjectWakeTargets( input: NormalizedAgentMessageInput, ): string[] { if (!input.channelTargetId) return []; - const project = db.entityCrud.getProjectForUpdate(input.channelTargetId, input.companyId); + const project = db.core.entity.getProjectForUpdate(input.channelTargetId, input.companyId); const participantIds = new Set(); if (project?.leadAgentId) participantIds.add(project.leadAgentId); - for (const assigneeId of db.tasks.listProjectTaskAssigneeIds(input.channelTargetId, input.companyId)) { + for (const assigneeId of db.core.tasks.listProjectTaskAssigneeIds(input.channelTargetId, input.companyId)) { participantIds.add(assigneeId); } if (participantIds.size === 0) return []; const ids = Array.from(participantIds).filter((id) => id !== input.fromAgentId); if (ids.length === 0) return []; - return db.agents.listActiveAgentIdsFromIds(input.companyId, ids); + return db.core.agents.listActiveAgentIdsFromIds(input.companyId, ids); } /** Resolve the agents mentioned in a message body, stripped of the @@ -222,7 +222,7 @@ export function resolveMentionedAgentIds( db: AppDatabase, input: Pick, ): string[] { - const mentioned = db.agents.findMentionedAgentIds(input.companyId, input.body); + const mentioned = db.core.agents.findMentionedAgentIds(input.companyId, input.body); const filtered: string[] = []; for (const agentId of mentioned) { if (!agentId || agentId === input.fromAgentId || agentId === HUMAN_SENDER_ID) continue; @@ -255,7 +255,7 @@ export function resolveMessageWakeTargets( } if (input.channel === "department" && input.channelTargetId) { - for (const agentId of db.agents.listDepartmentAgentIds(input.companyId, input.channelTargetId)) { + for (const agentId of db.core.agents.listDepartmentAgentIds(input.companyId, input.channelTargetId)) { if (agentId !== input.fromAgentId) wakeTargets.add(agentId); } } @@ -265,7 +265,7 @@ export function resolveMessageWakeTargets( // workspace is a listener. Board-only fan-outs use the dedicated // `incident` channel; the previous top-level-only fan-out made the // "company" label a misnomer that silently dropped ICs. - for (const agentId of db.agents.listCompanyBroadcastAgentIds(input.companyId)) { + for (const agentId of db.core.agents.listCompanyBroadcastAgentIds(input.companyId)) { if (agentId !== input.fromAgentId) wakeTargets.add(agentId); } } @@ -277,7 +277,7 @@ export function resolveMessageWakeTargets( } if (input.channel === "incident") { - for (const agentId of db.agents.listIncidentPingableAgentIds(input.companyId)) { + for (const agentId of db.core.agents.listIncidentPingableAgentIds(input.companyId)) { if (agentId !== input.fromAgentId) wakeTargets.add(agentId); } } @@ -288,7 +288,7 @@ export function resolveMessageWakeTargets( // The human participant (`__board__`) is a reader, not a wake-able // agent: it has no runtime, no heartbeat, no DB row. Leaving it in the - // set makes `wakeAgentIfPossible` call `db.entityCrud.getAgent("__board__")`, + // set makes `wakeAgentIfPossible` call `db.core.entity.getAgent("__board__")`, // which throws "Agent not found" and spams the log. Filter once, at // the point of resolution, so every downstream consumer is clean. wakeTargets.delete(HUMAN_SENDER_ID); @@ -317,8 +317,8 @@ export function dispatchAgentMessage( const persistedMessage = normalized.channel === "direct" ? { ...normalized, senderKind, mentionedAgentIds } : { ...normalized, toAgentId: null, senderKind, mentionedAgentIds }; - const id = deps.db.messages.sendAgentMessage(persistedMessage); - const message = deps.db.messages.getAgentMessage(id, normalized.companyId); + const id = deps.db.core.messages.sendAgentMessage(persistedMessage); + const message = deps.db.core.messages.getAgentMessage(id, normalized.companyId); if (message && deps.emitEvent) { deps.emitEvent({ type: "new-message", message }); } @@ -330,7 +330,7 @@ export function dispatchAgentMessage( !normalized.fromHumanUser && (message.channel !== "direct" || message.toAgentId === HUMAN_SENDER_ID) ) { - const senderName = deps.db.agents.findAgentName(message.fromAgentId) ?? "Agent"; + const senderName = deps.db.core.agents.findAgentName(message.fromAgentId) ?? "Agent"; deps.notify({ title: truncateForNotification(`${senderName} · ${message.subject}`, 80), body: truncateForNotification(message.body, 140), @@ -350,7 +350,7 @@ export function dispatchAgentMessage( const mentionResults: MentionResult[] = []; if (deps.wakeAgentIfPossible) { - const agentNameRows = deps.db.agents.listAgentNamesByIds(normalized.companyId, wakeTargets); + const agentNameRows = deps.db.core.agents.listAgentNamesByIds(normalized.companyId, wakeTargets); const agentNameMap = new Map(agentNameRows.map((a) => [a.id, a.name])); for (const wakeTarget of wakeTargets) { const agentName = agentNameMap.get(wakeTarget) ?? wakeTarget; @@ -367,7 +367,7 @@ export function dispatchAgentMessage( continue; } const runId = deps.wakeAgentIfPossible(wakeTarget, normalized.companyId, wakeTrigger); - const hasQueuedWake = !runId && deps.db.wakeup.listWakeupRequests(normalized.companyId, { + const hasQueuedWake = !runId && deps.db.automation.wakeup.listWakeupRequests(normalized.companyId, { agentId: wakeTarget, limit: 10, }).some((request) => request.status === "queued" || request.status === "deferred"); diff --git a/src/main/onboarding-service.ts b/src/main/onboarding-service.ts index eb8dbb19..c8ed97c7 100644 --- a/src/main/onboarding-service.ts +++ b/src/main/onboarding-service.ts @@ -85,9 +85,9 @@ export function createOnboardingService({ }: OnboardingDependencies) { function updateAgentWorkspaceBinding(agentId: string, workspaceId: string | null) { if (!workspaceId) return; - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.workspaceId === workspaceId) return; - db.agents.saveAgent({ + db.core.agents.saveAgent({ id: agent.id, companyId: agent.companyId, name: agent.name, @@ -113,7 +113,7 @@ export function createOnboardingService({ const trimmedPath = workspacePath.trim(); if (!trimmedPath) return null; const wsLocale = getLocale(); - return db.workspacesMod.saveWorkspace({ + return db.core.workspaces.saveWorkspace({ companyId, name: wsLocale === "zh" ? "默认工作区" : "Default Workspace", localPath: trimmedPath, @@ -158,7 +158,7 @@ export function createOnboardingService({ ? `Turn the founding goal "${activeGoal.title}" into an execution plan, delegation map, and the first wave of real work.` : "Establish the first operating plan, assign work, and start the company execution loop."; - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId: activeGoal?.id ?? null, name: kickoffProjectName, @@ -187,7 +187,7 @@ export function createOnboardingService({ `Review the current company state, create the first real tasks, delegate initial work, and report progress through tasks, approvals, and messages.`, ].join("\n\n"); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId: activeGoal?.id ?? null, @@ -222,10 +222,10 @@ export function createOnboardingService({ } function bootstrapOnboarding(input: BootstrapInput): OnboardingBootstrapSummary { - const connectors = db.snap.listSnapshot({ companyId: null }).connectors; + const connectors = db.projection.snapshot.listSnapshot({ companyId: null }).connectors; const selectedConnectorId = resolveOnboardingConnector(connectors, input.connectorId); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: input.companyName.trim(), description: input.companyDescription.trim(), status: "active", @@ -239,7 +239,7 @@ export function createOnboardingService({ // pre-commit; the pool settings arrive in the input payload and // land in the DB only once a company exists to scope them. for (const pool of input.connectorPoolSelections ?? []) { - db.settings.setDisabledModels(companyId, pool.connectorId, pool.disabledModels); + db.core.settings.setDisabledModels(companyId, pool.connectorId, pool.disabledModels); } // Validate the CEO model selection against the now-persisted pool @@ -254,7 +254,7 @@ export function createOnboardingService({ const goalTitle = input.goalTitle.trim(); if (goalTitle) { - db.entityCrud.saveGoal({ + db.core.entity.saveGoal({ companyId, title: goalTitle, description: input.goalDescription.trim() || `Achieve: ${goalTitle}`, @@ -265,7 +265,7 @@ export function createOnboardingService({ } const ceoLocale = getLocale(); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -284,8 +284,8 @@ export function createOnboardingService({ modelStrategy: "explicit", roleLevel: "c_suite", }); - db.agents.setHeartbeat(ceoId, true, 120); - db.settings.setCurrentCompany(companyId); + db.core.agents.setHeartbeat(ceoId, true, 120); + db.core.settings.setCurrentCompany(companyId); seedDefaultAutomationRules(companyId); if (scanAndAssignSkills) { @@ -297,7 +297,7 @@ export function createOnboardingService({ const kickoff = createInitialExecutionLoop(companyId, workspaceId); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "board", action: "company.bootstrapped", diff --git a/src/main/orchestration/heartbeat.ts b/src/main/orchestration/heartbeat.ts index 18fd1677..cc771832 100644 --- a/src/main/orchestration/heartbeat.ts +++ b/src/main/orchestration/heartbeat.ts @@ -58,9 +58,9 @@ export function createHeartbeat( } = deps; function ensureAgentHeartbeatEnabled(agentId: string) { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (!agent.heartbeatEnabled) { - db.agents.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120); + db.core.agents.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120); } } @@ -69,22 +69,22 @@ export function createHeartbeat( const isMessageWake = trigger === "message"; const isCommunicationWake = isMessageWake || trigger === "peer_request"; if (!hasWorkerReady()) return SKIPPED("worker_not_ready"); - const companyStatus = db.company.getCompanyStatus(companyId); + const companyStatus = db.core.company.getCompanyStatus(companyId); if (companyStatus !== "active") return SKIPPED("company_inactive"); - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.companyId !== companyId) { logger.warn(`[heartbeat] Refusing cross-company run for agent ${agentId}: ${agent.companyId} !== ${companyId}`); return SKIPPED("cross_company"); } const pick = pickModelForAgent(agent, { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (id) => db.settings.getDisabledModels(agent.companyId, id), - listModelCapabilities: () => db.modelCapabilities.listModelCapabilities(), - readConnectors: () => db.connectorsCrud.readConnectors(), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (id) => db.core.settings.getDisabledModels(agent.companyId, id), + listModelCapabilities: () => db.core.modelCapabilities.listModelCapabilities(), + readConnectors: () => db.core.connectors.readConnectors(), }); if (!pick.ok) { logger.warn(`[heartbeat] Model pick failed for agent ${agent.name}: ${pick.message}`); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "heartbeat.model_pick_failed", @@ -95,7 +95,7 @@ export function createHeartbeat( return SKIPPED("model_pick_failed"); } const assignment = pick.assignment; - const connector = db.connectorsCrud.getConnector(assignment.connectorId); + const connector = db.core.connectors.getConnector(assignment.connectorId); const effectiveAgentForEligibility: AgentRecord = { ...agent, model: assignment.modelId }; const eligibilityFailure = getRunEligibilityFailure(effectiveAgentForEligibility, connector, null); if (eligibilityFailure) { @@ -115,9 +115,9 @@ export function createHeartbeat( // Let getHeartbeatPolicy throw — a missing row signals data corruption // and silently falling back hides the gate that decides whether the // agent runs at all. - const heartbeatPolicy = db.agents.getHeartbeatPolicy(agent.id); + const heartbeatPolicy = db.core.agents.getHeartbeatPolicy(agent.id); - const activeRunCount = db.agents.countActiveRunsForAgent(agent.id, companyId); + const activeRunCount = db.core.agents.countActiveRunsForAgent(agent.id, companyId); const runAttemptDecision = decideRunAttempt({ kind: "heartbeat", trigger, @@ -136,10 +136,10 @@ export function createHeartbeat( if (!heartbeatPolicy.wakeOnDemand && trigger !== "timer" && trigger !== "bootstrap") { return SKIPPED("wake_on_demand_disabled"); } - if (db.agents.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval"); + if (db.core.agents.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval"); - const companyMode = db.company.getCompanyExecutionMode(companyId); - const approvalSettings = db.company.getCompanyApprovalSettings(companyId); + const companyMode = db.core.company.getCompanyExecutionMode(companyId); + const approvalSettings = db.core.company.getCompanyApprovalSettings(companyId); const shouldEnforceHumanReview = companyMode === "ai_cofounder" || approvalSettings?.reviewDeliverables === true; @@ -147,7 +147,7 @@ export function createHeartbeat( if ( shouldEnforceHumanReview && !isMessageWake && - (db.tasks.hasAgentPendingDeliverableReview(agentId, companyId) || db.tasks.hasAgentTaskAwaitingReview(agentId, companyId)) + (db.core.tasks.hasAgentPendingDeliverableReview(agentId, companyId) || db.core.tasks.hasAgentTaskAwaitingReview(agentId, companyId)) ) { return SKIPPED("awaiting_user_review"); } @@ -157,22 +157,22 @@ export function createHeartbeat( shouldEnforceHumanReview && !isMessageWake && !agent.reportsTo && - db.tasks.hasPendingDeliverableReviews(companyId) + db.core.tasks.hasPendingDeliverableReviews(companyId) ) { return SKIPPED("pending_deliverable_review"); } const assignedTasks = isCommunicationWake ? [] - : db.tasks.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]); + : db.core.tasks.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]); const isTimer = trigger === "timer"; const candidateTasks = assignedTasks; const candidateTask = candidateTasks.find((entry) => !entry.activeRunId) ?? null; - let task: ReturnType | null = null; + let task: ReturnType | null = null; if (candidateTask) { - task = db.entityCrud.getTask(candidateTask.id); + task = db.core.entity.getTask(candidateTask.id); } else { const hasActiveAssignedRun = candidateTasks.some( (entry) => entry.activeRunId && hasActiveRun({ taskId: entry.id, companyId }), @@ -180,9 +180,9 @@ export function createHeartbeat( if (hasActiveAssignedRun) return SKIPPED("max_concurrent_runs"); const isCeo = !agent.reportsTo; - const isManager = db.agents.getDirectReports(agent.id, companyId).length > 0; - const goalCount = db.entityCrud.countActiveGoalsForCompany(companyId); - const taskCount = db.tasks.countActiveTasksForCompany(companyId); + const isManager = db.core.agents.getDirectReports(agent.id, companyId).length > 0; + const goalCount = db.core.entity.countActiveGoalsForCompany(companyId); + const taskCount = db.core.tasks.countActiveTasksForCompany(companyId); const companyNeedsBootstrap = goalCount > 0 && taskCount < goalCount; if (isTimer && !isCeo && !isManager && !companyNeedsBootstrap) { @@ -200,7 +200,7 @@ export function createHeartbeat( workspaceResolution = resolveWorkspace(task, agent, companyId); } catch (error) { if (error instanceof WorkspaceConfigurationError) { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "heartbeat.workspace_unresolved", @@ -219,14 +219,14 @@ export function createHeartbeat( ); const run = task - ? db.runs.createTaskRun({ + ? db.core.runs.createTaskRun({ companyId, taskId: task.id, agentId: agent.id, workspaceId: workspaceResolution.lockMode === "exclusive" ? (workspace?.id ?? null) : null, connectorId: connector.id, }) - : db.runs.createRun({ + : db.core.runs.createRun({ companyId, taskId: null, agentId: agent.id, @@ -237,7 +237,7 @@ export function createHeartbeat( if (!task) { throw new Error("Taskless heartbeat run creation returned null."); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "execution.checkout_rejected", @@ -248,7 +248,7 @@ export function createHeartbeat( return SKIPPED("checkout_race_lost"); } - const companyBasic = db.company.getCompanyBasic(companyId); + const companyBasic = db.core.company.getCompanyBasic(companyId); dispatchRunToWorker({ runId: run.id, companyId, @@ -260,25 +260,25 @@ export function createHeartbeat( observationPath: workspaceResolution.observationPath, companyName: companyBasic?.name ?? "Unknown", companyDescription: companyBasic?.description ?? "", - snapshot: db.snap.listSnapshot(), + snapshot: db.projection.snapshot.listSnapshot(), trigger, resolvedModel: assignment.modelId, }); - db.agents.markHeartbeatAt(agentId); + db.core.agents.markHeartbeatAt(agentId); // Actor audit: record what triggered this run try { const actorType = trigger === "timer" || trigger === "bootstrap" ? "system" : "agent"; const actorId = trigger === "timer" || trigger === "bootstrap" ? null : trigger; - db.runs.updateRunActor(run.id, actorType, actorId); + db.core.runs.updateRunActor(run.id, actorType, actorId); } catch (error) { logger.warn( `[heartbeat] updateRunActor failed for run ${run.id}: ${error instanceof Error ? error.message : String(error)}`, ); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "heartbeat.triggered", @@ -299,7 +299,7 @@ export function createHeartbeat( // raw) from spamming the log with "Agent not found: __board__". if (agentId === HUMAN_SENDER_ID) return null; try { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.companyId !== companyId) { logger.warn(`[wake] Refusing cross-company wake for agent ${agentId}: ${agent.companyId} !== ${companyId}`); return null; @@ -317,16 +317,16 @@ export function createHeartbeat( const agentBusy = hasActiveRun({ agentId, companyId }); if (agentBusy) { - db.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent: ${trigger}`, context); + db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent: ${trigger}`, context); return null; } const result = startAgentHeartbeatRun(agentId, companyId, trigger); if (result.status === "skipped") { - db.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent (deferred:${result.reason}): ${trigger}`, context); + db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent (deferred:${result.reason}): ${trigger}`, context); return null; } - db.wakeup.completePendingWakeupRequests(agentId, companyId, result.runId); + db.automation.wakeup.completePendingWakeupRequests(agentId, companyId, result.runId); return result.runId; } diff --git a/src/main/orchestration/index.ts b/src/main/orchestration/index.ts index 87620e91..aae01e4b 100644 --- a/src/main/orchestration/index.ts +++ b/src/main/orchestration/index.ts @@ -8,7 +8,7 @@ export function createOrchestrationService(deps: OrchestrationDependencies) { const { db } = deps; function hasActiveRun(filter: { taskId?: string; agentId?: string; companyId: string }) { - return db.runs.hasActiveRun(filter); + return db.core.runs.hasActiveRun(filter); } const runDispatch = createRunDispatch(deps); diff --git a/src/main/orchestration/initial-user-turn.ts b/src/main/orchestration/initial-user-turn.ts index af9a9b76..69a419fa 100644 --- a/src/main/orchestration/initial-user-turn.ts +++ b/src/main/orchestration/initial-user-turn.ts @@ -32,7 +32,7 @@ const SUBJECT_PREVIEW_LEN = 80; export function resolveInitialUserTurn( params: { trigger: string | null; - task: ReturnType | null; + task: ReturnType | null; companyId: string; agentId: string; }, @@ -46,7 +46,7 @@ export function resolveInitialUserTurn( } if (trigger === "message" || trigger === "peer_request") { - const unread = deps.db.messages.listAgentInboxUnread(companyId, agentId); + const unread = deps.db.core.messages.listAgentInboxUnread(companyId, agentId); if (unread.length > 0) { return buildCommunicationTurn(trigger, unread, deps.db); } @@ -81,9 +81,9 @@ function tryResolveCeoTeamBuildingCue(params: { }): string | null { const { agentId, companyId, db } = params; try { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.reportsTo) return null; - const activeReports = db.agents + const activeReports = db.core.agents .getDirectReports(agentId, companyId) .filter((r) => r.status !== "pending_approval" && r.status !== "terminated"); if (activeReports.length > 0) return null; @@ -167,7 +167,7 @@ function buildGenericWakeTurn(trigger: string | null): string { function resolveAgentLabel(db: AppDatabase, agentId: string): string { try { - const name = db.agents.findAgentName(agentId); + const name = db.core.agents.findAgentName(agentId); if (name && name.length > 0) return name; } catch { // fall through diff --git a/src/main/orchestration/internal-tool-selector.ts b/src/main/orchestration/internal-tool-selector.ts index 635dbebf..fac47cb6 100644 --- a/src/main/orchestration/internal-tool-selector.ts +++ b/src/main/orchestration/internal-tool-selector.ts @@ -44,15 +44,15 @@ export function resolveInternalToolsForAgent( function isInitialTeamBuildingPhase(db: AppDatabase, agent: AgentRecord): boolean { if (!canFileCeoStrategy(db, agent.companyId, agent.id)) return false; - const directReports = db.agents.getDirectReports(agent.id, agent.companyId); + const directReports = db.core.agents.getDirectReports(agent.id, agent.companyId); const activeReports = directReports.filter( (report) => report.status !== "pending_approval" && report.status !== "terminated", ); const pendingReportRows = directReports.filter((report) => report.status === "pending_approval"); - const pendingHireApprovals = db.approvalsMod + const pendingHireApprovals = db.core.approvals .listCompanyApprovalsByType(agent.companyId, "hire_agent") .filter((approval) => approval.state === "pending"); - const pendingStrategyApprovals = db.approvalsMod + const pendingStrategyApprovals = db.core.approvals .listCompanyApprovalsByType(agent.companyId, "approve_ceo_strategy") .filter((approval) => approval.state === "pending"); diff --git a/src/main/orchestration/resolve-workspace.ts b/src/main/orchestration/resolve-workspace.ts index d31dc361..658d73e6 100644 --- a/src/main/orchestration/resolve-workspace.ts +++ b/src/main/orchestration/resolve-workspace.ts @@ -136,7 +136,7 @@ function inferLockMode( return { lockMode: "exclusive", workspaceId: agent.workspaceId, source: "agent" }; } - const projectWorkspace = db.workspacesMod.getProjectPrimaryWorkspace( + const projectWorkspace = db.core.workspaces.getProjectPrimaryWorkspace( task?.projectId ?? null, companyId, ); @@ -179,7 +179,7 @@ export function resolveWorkspaceWithLockMode( // `getCompanyObservationWorkspace` returns the primary or oldest // workspace; null only when the company has no workspace row at // all, in which case there is genuinely nothing to observe. - const observation = db.workspacesMod.getCompanyObservationWorkspace(companyId); + const observation = db.core.workspaces.getCompanyObservationWorkspace(companyId); return { workspace: null, lockMode: "none", @@ -194,7 +194,7 @@ export function resolveWorkspaceWithLockMode( ); } - const workspace = db.workspacesMod.getWorkspace(decision.workspaceId); + const workspace = db.core.workspaces.getWorkspace(decision.workspaceId); if (!workspace) { throw new WorkspaceConfigurationError( `Resolved workspace ${decision.workspaceId} does not exist (companyId=${companyId}, source=${decision.source}).`, diff --git a/src/main/orchestration/run-dispatch.ts b/src/main/orchestration/run-dispatch.ts index 9baa9681..c3c7b31b 100644 --- a/src/main/orchestration/run-dispatch.ts +++ b/src/main/orchestration/run-dispatch.ts @@ -57,7 +57,7 @@ export function createRunDispatch(deps: OrchestrationDependencies) { ): void { const errorMessage = formatDispatchError(error); logger.error(`[run] Dispatch failed before worker enqueue for ${params.runId}: ${errorMessage}`); - db.runs.finishRun({ + db.core.runs.finishRun({ runId: params.runId, status: "failed", summary: "Run failed before worker enqueue.", @@ -68,9 +68,9 @@ export function createRunDispatch(deps: OrchestrationDependencies) { unattributedCost: true, }); if (workspaceLockClaimed && params.workspaceMode === "exclusive" && params.workspace) { - db.workspacesMod.releaseWorkspaceLock(params.workspace.id, params.runId); + db.core.workspaces.releaseWorkspaceLock(params.workspace.id, params.runId); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: params.companyId, actor: params.agent.id, action: "run.failed", @@ -102,8 +102,8 @@ export function createRunDispatch(deps: OrchestrationDependencies) { } const modelSelection = validateConnectorModelSelection( { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId), }, agent.companyId, connector.id, @@ -142,8 +142,8 @@ export function createRunDispatch(deps: OrchestrationDependencies) { // `todo` via the `finishRun` never-started branch, so heartbeat picks // it up on the next cycle with no manual intervention. if (params.workspaceMode === "exclusive" && params.workspace) { - if (!db.workspacesMod.claimWorkspaceLock(params.workspace.id, params.runId)) { - db.runs.finishRun({ + if (!db.core.workspaces.claimWorkspaceLock(params.workspace.id, params.runId)) { + db.core.runs.finishRun({ runId: params.runId, status: "cancelled", summary: null, diff --git a/src/main/orchestration/scheduler-heartbeat.ts b/src/main/orchestration/scheduler-heartbeat.ts index 099e6d7e..c91bdd5d 100644 --- a/src/main/orchestration/scheduler-heartbeat.ts +++ b/src/main/orchestration/scheduler-heartbeat.ts @@ -51,8 +51,8 @@ export function createHeartbeatScheduler( lastTickAt: string, ) { try { - const existing = db.agents.getRuntimeState(agentId) as Record; - db.agents.setRuntimeState(agentId, companyId, { + const existing = db.core.agents.getRuntimeState(agentId) as Record; + db.core.agents.setRuntimeState(agentId, companyId, { ...existing, heartbeatSkip: { reason, @@ -73,11 +73,11 @@ export function createHeartbeatScheduler( function clearSkip(agentId: string, companyId: string) { try { - const existing = db.agents.getRuntimeState(agentId) as Record; + const existing = db.core.agents.getRuntimeState(agentId) as Record; if (!("heartbeatSkip" in existing)) return; const next = { ...existing }; delete next.heartbeatSkip; - db.agents.setRuntimeState(agentId, companyId, next); + db.core.agents.setRuntimeState(agentId, companyId, next); } catch (error) { logAppError(logger, error, { kind: "recoverable", @@ -123,7 +123,7 @@ export function createHeartbeatScheduler( : null; if (action) { try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action, @@ -154,7 +154,7 @@ export function createHeartbeatScheduler( if (!previous) return; skipStreakByAgent.delete(agentId); try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: "system", action: "heartbeat.skip_cleared", @@ -196,12 +196,12 @@ export function createHeartbeatScheduler( try { if (isQuitting() || !hasWorkerReady()) return; - const activeCompanies = db.company.listActiveCompanyIds(); + const activeCompanies = db.core.company.listActiveCompanyIds(); const now = Date.now(); for (const company of activeCompanies) { try { - const eligibleAgents = db.agents.listHeartbeatEligibleAgents(company.id); + const eligibleAgents = db.core.agents.listHeartbeatEligibleAgents(company.id); for (const agent of eligibleAgents) { if (hasActiveRun({ agentId: agent.id, companyId: company.id })) continue; @@ -223,7 +223,7 @@ export function createHeartbeatScheduler( tryWake(agent.id, company.id, agent.name, "timer"); } - const goalCount = db.entityCrud.countActiveGoalsForCompany(company.id); + const goalCount = db.core.entity.countActiveGoalsForCompany(company.id); // Bootstrap when the company has active goals but no work is // actually advancing. The previous gate ("taskCount === 0") // missed the case where every task is in_review or blocked — @@ -232,9 +232,9 @@ export function createHeartbeatScheduler( // regular timer interval. countAdvancingTasksForCompany // counts only todo + in_progress so bootstrapping still // fires once the team stalls. - const advancingCount = db.tasks.countAdvancingTasksForCompany(company.id); + const advancingCount = db.core.tasks.countAdvancingTasksForCompany(company.id); if (goalCount > 0 && advancingCount === 0) { - const ceo = db.agents.getCeoForCompany(company.id); + const ceo = db.core.agents.getCeoForCompany(company.id); const ceoLastWake = ceo?.lastHeartbeatAt ? new Date(ceo.lastHeartbeatAt).getTime() : 0; const ceoLastAttempt = ceo ? attemptedAtByAgent.get(ceo.id) ?? 0 : 0; const ceoLastTouch = Math.max(ceoLastWake, ceoLastAttempt); diff --git a/src/main/orchestration/scheduler-housekeeping.ts b/src/main/orchestration/scheduler-housekeeping.ts index d9f847c1..804b54b7 100644 --- a/src/main/orchestration/scheduler-housekeeping.ts +++ b/src/main/orchestration/scheduler-housekeeping.ts @@ -28,15 +28,15 @@ export function createHousekeepingScheduler( try { if (isQuitting() || !hasWorkerReady()) return; - const activeCompanies = db.company.listActiveCompanyIds(); + const activeCompanies = db.core.company.listActiveCompanyIds(); for (const company of activeCompanies) { try { const currentMonth = new Date().toISOString().slice(0, 7); - const lastResetMonth = db.settings.getLastBudgetResetMonth(company.id) ?? ""; + const lastResetMonth = db.core.settings.getLastBudgetResetMonth(company.id) ?? ""; if (currentMonth !== lastResetMonth) { - db.agents.resetMonthlyBudgets(company.id); - db.settings.setLastBudgetResetMonth(company.id, currentMonth); + db.core.agents.resetMonthlyBudgets(company.id); + db.core.settings.setLastBudgetResetMonth(company.id, currentMonth); logger.info(`[housekeeping] Reset monthly budgets for company ${company.name} (${currentMonth})`); } @@ -53,7 +53,7 @@ export function createHousekeepingScheduler( dispatchAgentMessage, resolveApprovalDecision, notify, - getCompanyExecutionMode: (cId: string) => db.company.getCompanyExecutionMode(cId), + getCompanyExecutionMode: (cId: string) => db.core.company.getCompanyExecutionMode(cId), handleTaskStatusChange: (taskId: string, cId: string, previousStatus: string, newStatus: string) => { deps.dispatchAutomation("task_status_changed", { companyId: cId, taskId, previousStatus, newStatus }); publishDomainChanged(); @@ -92,7 +92,7 @@ export function createHousekeepingScheduler( } try { - const staleWakeups = db.wakeup.purgeExpiredWakeupRequests(); + const staleWakeups = db.automation.wakeup.purgeExpiredWakeupRequests(); if (staleWakeups > 0) { logger.info(`[housekeeping] Purged ${staleWakeups} expired wakeup request(s)`); } @@ -112,7 +112,7 @@ export function createHousekeepingScheduler( // so an active rotation window cannot strip the ledger out // from under a live run. const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); - const purgedProjections = db.inboxProjections.purgeOrphanProjections(cutoff); + const purgedProjections = db.projection.inbox.purgeOrphanProjections(cutoff); if (purgedProjections > 0) { logger.info(`[housekeeping] Purged ${purgedProjections} orphan inbox projection ledger row(s)`); } diff --git a/src/main/orchestration/scheduler-idle-automation.ts b/src/main/orchestration/scheduler-idle-automation.ts index 4cb4db83..1ddd4f3b 100644 --- a/src/main/orchestration/scheduler-idle-automation.ts +++ b/src/main/orchestration/scheduler-idle-automation.ts @@ -13,14 +13,14 @@ export function createIdleAutomationScheduler(deps: OrchestrationDependencies) { try { if (isQuitting() || !hasWorkerReady()) return; - const activeCompanies = db.company.listActiveCompanyIds(); + const activeCompanies = db.core.company.listActiveCompanyIds(); const now = Date.now(); for (const company of activeCompanies) { try { - const companyAgents = db.agents.listActiveAgentsForCompany(company.id); + const companyAgents = db.core.agents.listActiveAgentsForCompany(company.id); for (const agent of companyAgents) { - if (!db.agents.hasAgentAssignedTasks(agent.id, company.id)) { + if (!db.core.agents.hasAgentAssignedTasks(agent.id, company.id)) { const lastActive = agent.lastHeartbeatAt ? new Date(agent.lastHeartbeatAt).getTime() : 0; const idleThreshold = 20 * 60 * 1000; if (now - lastActive > idleThreshold) { diff --git a/src/main/orchestration/scheduler-lock-recovery.ts b/src/main/orchestration/scheduler-lock-recovery.ts index 7c7b2296..da3d3a5d 100644 --- a/src/main/orchestration/scheduler-lock-recovery.ts +++ b/src/main/orchestration/scheduler-lock-recovery.ts @@ -17,7 +17,7 @@ export function createLockRecoveryScheduler(deps: OrchestrationDependencies) { try { const recovered = db.recoverStaleLocks(); for (const task of recovered) { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: task.companyId, actor: "system", action: "execution.lock_recovered", @@ -36,7 +36,7 @@ export function createLockRecoveryScheduler(deps: OrchestrationDependencies) { } try { - const staleReleased = db.workspacesMod.releaseStaleWorkspaceLocks(); + const staleReleased = db.core.workspaces.releaseStaleWorkspaceLocks(); if (staleReleased > 0) { logger.info(`[lock-recovery] Released ${staleReleased} stale workspace lock(s)`); publishDomainChanged(); diff --git a/src/main/orchestration/task-runner.ts b/src/main/orchestration/task-runner.ts index 3863cdb2..767be72a 100644 --- a/src/main/orchestration/task-runner.ts +++ b/src/main/orchestration/task-runner.ts @@ -37,27 +37,27 @@ export function createTaskRunner( logger.warn(`[run] Worker not ready — cannot queue run for task ${taskId} (trigger: ${trigger})`); return fail("WORKER_UNAVAILABLE", "Orchestration worker is not ready."); } - const task = db.entityCrud.getTask(taskId); - const companyStatus = db.company.getCompanyStatus(task.companyId); + const task = db.core.entity.getTask(taskId); + const companyStatus = db.core.company.getCompanyStatus(task.companyId); if (companyStatus !== "active") { return fail("COMPANY_NOT_ACTIVE", "Runs can only start while the company is active."); } if (!task.assigneeAgentId) return fail("TASK_UNASSIGNED", "Assign an agent before starting a run."); - const agent = db.entityCrud.getAgent(task.assigneeAgentId); + const agent = db.core.entity.getAgent(task.assigneeAgentId); if (agent.companyId !== task.companyId) { return fail("AGENT_COMPANY_MISMATCH", "The assigned agent does not belong to the task company."); } const pick = pickModelForAgent(agent, { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (id) => db.settings.getDisabledModels(agent.companyId, id), - listModelCapabilities: () => db.modelCapabilities.listModelCapabilities(), - readConnectors: () => db.connectorsCrud.readConnectors(), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (id) => db.core.settings.getDisabledModels(agent.companyId, id), + listModelCapabilities: () => db.core.modelCapabilities.listModelCapabilities(), + readConnectors: () => db.core.connectors.readConnectors(), }); if (!pick.ok) { return fail("MODEL_PICK_FAILED", pick.message); } const assignment = pick.assignment; - const connector = db.connectorsCrud.getConnector(assignment.connectorId); + const connector = db.core.connectors.getConnector(assignment.connectorId); const effectiveAgentForEligibility: AgentRecord = { ...agent, model: assignment.modelId }; const eligibilityFailure = getRunEligibilityFailure(effectiveAgentForEligibility, connector, task.projectId ?? null); if (eligibilityFailure === "AGENT_NOT_RUNNABLE") { @@ -75,10 +75,10 @@ export function createTaskRunner( if (hasActiveRun({ agentId: agent.id, companyId: task.companyId })) { return fail("AGENT_ALREADY_RUNNING", "The assigned agent already has an active run."); } - if (db.agents.isAgentPendingApproval(agent.id)) { + if (db.core.agents.isAgentPendingApproval(agent.id)) { return fail("AGENT_PENDING_APPROVAL", "This agent is pending hire approval and cannot run tasks."); } - const blockingApproval = db.approvalsMod.getBlockingApproval(task.companyId, agent.id, task.id); + const blockingApproval = db.core.approvals.getBlockingApproval(task.companyId, agent.id, task.id); if (blockingApproval) { return fail( "APPROVAL_GATE_BLOCKED", @@ -100,7 +100,7 @@ export function createTaskRunner( return fail("WORKSPACE_COMPANY_MISMATCH", "The selected workspace does not belong to the task company."); } - const run = db.runs.createTaskRun({ + const run = db.core.runs.createTaskRun({ companyId: task.companyId, taskId: task.id, agentId: agent.id, @@ -111,7 +111,7 @@ export function createTaskRunner( return fail("CHECKOUT_RACE_LOST", "Another run claimed this task. Refresh and try again."); } - const companyBasic = db.company.getCompanyBasic(task.companyId); + const companyBasic = db.core.company.getCompanyBasic(task.companyId); dispatchRunToWorker({ runId: run.id, companyId: task.companyId, @@ -123,13 +123,13 @@ export function createTaskRunner( observationPath: workspaceResolution.observationPath, companyName: companyBasic?.name ?? "Unknown", companyDescription: companyBasic?.description ?? "", - snapshot: db.snap.listSnapshot(), + snapshot: db.projection.snapshot.listSnapshot(), trigger, resolvedModel: assignment.modelId, }); - const postDispatchRun = db.runs.getRun(run.id); + const postDispatchRun = db.core.runs.getRun(run.id); if (postDispatchRun.status === "queued" || postDispatchRun.status === "running") { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: task.companyId, actor, action: "run.queued", @@ -147,7 +147,7 @@ export function createTaskRunner( try { const recovered = db.recoverStaleLocks(); for (const task of recovered) { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: task.companyId, actor: "system", action: "execution.lock_recovered", @@ -167,10 +167,10 @@ export function createTaskRunner( if (isQuitting() || !hasWorkerReady()) { return; } - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const companiesById = new Map(snapshot.companies.map((company) => [company.id, company])); - for (const run of db.runs.listQueuedRuns()) { + for (const run of db.core.runs.listQueuedRuns()) { const company = companiesById.get(run.companyId); if (!company || company.status !== "active") { continue; @@ -187,13 +187,13 @@ export function createTaskRunner( } try { - const task = run.taskId ? db.entityCrud.getTask(run.taskId) : null; - const agent = db.entityCrud.getAgent(run.agentId); - const connector = db.connectorsCrud.getConnector(run.connectorId); + const task = run.taskId ? db.core.entity.getTask(run.taskId) : null; + const agent = db.core.entity.getAgent(run.agentId); + const connector = db.core.connectors.getConnector(run.connectorId); const eligibilityFailure = getRunEligibilityFailure(agent, connector, task?.projectId ?? null); if (eligibilityFailure) { if (eligibilityFailure === "CONNECTOR_MODEL_NOT_ALLOWED") { - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "failed", summary: "Queued run rejected during recovery because the agent model is disabled or invalid for its connector.", @@ -203,7 +203,7 @@ export function createTaskRunner( model: null, unattributedCost: true, }); - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: run.companyId, actor: "system", action: "run.recovery_blocked", diff --git a/src/main/orchestration/turn-context.ts b/src/main/orchestration/turn-context.ts index d7b27f76..7ad58ef5 100644 --- a/src/main/orchestration/turn-context.ts +++ b/src/main/orchestration/turn-context.ts @@ -20,13 +20,13 @@ import { logAppError } from "../errors/index.js"; export interface RunDispatchParams { runId: string; companyId: string; - agent: ReturnType; + agent: ReturnType; // `task` is null for heartbeat runs (agent wake-ups with no assigned task). // Downstream code (prompt builder, worker payload, session pointer) // branches on this — when null, skip task-scoped state. - task: ReturnType | null; - connector: ReturnType; - workspace: ReturnType; + task: ReturnType | null; + connector: ReturnType; + workspace: ReturnType; workspaceMode: WorkspaceLockMode; /** * Filesystem root the artifact projector observes for files this run @@ -40,7 +40,7 @@ export interface RunDispatchParams { observationPath?: string | null; companyName: string; companyDescription: string; - snapshot: ReturnType; + snapshot: ReturnType; trigger: string; /** * Model id resolved by `pickModelForAgent` at dispatch time. When the @@ -75,7 +75,7 @@ function assembleApiEnv( trigger: string, deps: Pick, ) { - const env = db.entityCrud.resolveSecretEnv(connector.envBindingText, companyId); + const env = db.core.entity.resolveSecretEnv(connector.envBindingText, companyId); const apiPort = deps.getApiPort(); const apiUrl = apiPort ? `http://127.0.0.1:${apiPort}` : ""; const apiKey = apiUrl ? deps.createRunToken(agentId, companyId, runId) : ""; @@ -93,8 +93,8 @@ function assembleApiEnv( } function assembleRecentComments( - assignedTasks: ReturnType, - reviewTasks: ReturnType, + assignedTasks: ReturnType, + reviewTasks: ReturnType, snapshot: RunDispatchParams["snapshot"], ) { const relevantTaskIds = new Set([ @@ -160,8 +160,8 @@ function resolveEnabledConnectorModels( ): string[] { return getConnectorModelPolicy( { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId), }, companyId, connector.id as ConnectorId, @@ -179,7 +179,7 @@ function resolveSession( // Per-agent session: one ongoing transcript per agent; switching tasks // (or running taskless heartbeats) becomes a "now work on X" user turn // on the ongoing transcript rather than starting a fresh session. - const agentSessionId = db.tasks.ensureAgentSession({ + const agentSessionId = db.core.tasks.ensureAgentSession({ agentId: agent.id, companyId, adapterType: connectorId, @@ -281,7 +281,7 @@ function buildConnectorCatalog( ) { return connectors .filter((c) => c.status === "ready") - .filter((c) => db.settings.hasModelPoolSettings(companyId, c.id)) + .filter((c) => db.core.settings.hasModelPoolSettings(companyId, c.id)) .map((c) => ({ id: c.id, label: c.label, @@ -309,7 +309,7 @@ function readHeartbeatSkip( since: string; lastTickAt: string; } | null { - const state = db.agents.getRuntimeState(agentId); + const state = db.core.agents.getRuntimeState(agentId); const raw = state["heartbeatSkip"]; if (!raw || typeof raw !== "object") return null; const record = raw as Record; @@ -339,8 +339,8 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps const { env, apiUrl, apiKey } = assembleApiEnv(db, connector, companyId, agent.id, runId, trigger, deps); env.AGENT_COMPANY_TASK_ID = task?.id ?? ""; - const assignedTasks = db.tasks.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]); - const reviewTasks = db.tasks.listReviewTasksForManager(agent.id, companyId); + const assignedTasks = db.core.tasks.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]); + const reviewTasks = db.core.tasks.listReviewTasksForManager(agent.id, companyId); const recentComments = assembleRecentComments(assignedTasks, reviewTasks, snapshot); const initialHandoff = resolveSessionHandoff(agent.id, deps); const { taskSessionId, sessionHandoff } = resolveSession(db, agent, companyId, connector.id, initialHandoff, deps); @@ -355,11 +355,11 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps task, companyName, locale: snapshot.locale, - executionMode: db.company.getCompanyExecutionMode(companyId) as ExecutionMode, + executionMode: db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode, goals: snapshot.goals.filter((goal) => goal.companyId === companyId), projects: snapshot.projects.filter((project) => project.companyId === companyId), - directReports: db.agents.getDirectReports(agent.id, companyId), - chainOfCommand: db.agents.getChainOfCommand(agent.id), + directReports: db.core.agents.getDirectReports(agent.id, companyId), + chainOfCommand: db.core.agents.getChainOfCommand(agent.id), assignedTasks, allAgents: snapshot.agents.filter((entry) => entry.companyId === companyId), pendingApprovals: filterPendingApprovals(snapshot.approvals, companyId, agent.id), @@ -368,7 +368,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps allTasks: snapshot.tasks.filter((entry) => entry.companyId === companyId), recentComments: normalizeCommentsForPrompt(recentComments), companyDescription, - socialAccounts: db.social.listSocialAccounts(companyId).map((account) => ({ + socialAccounts: db.core.social.listSocialAccounts(companyId).map((account) => ({ id: account.id, platform: account.platform, accountName: account.accountName, @@ -382,7 +382,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps allConnectors: buildConnectorCatalog(db, companyId, snapshot.connectors), sessionHandoff, skills, - companySkillCatalog: db.skills.listCompanySkills(companyId).map((skill) => ({ + companySkillCatalog: db.core.skills.listCompanySkills(companyId).map((skill) => ({ name: skill.name, slug: skill.slug, description: skill.description, @@ -398,8 +398,8 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps const modelForRun = params.resolvedModel !== undefined ? params.resolvedModel : agent.model; const modelSelection = validateConnectorModelSelection( { - getConnector: (id) => db.connectorsCrud.getConnector(id), - getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId), + getConnector: (id) => db.core.connectors.getConnector(id), + getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId), }, companyId, connector.id, @@ -438,7 +438,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps systemPrompt, trigger, taskSessionId: taskSessionId ?? runId, - executionMode: db.company.getCompanyExecutionMode(companyId) as "ai_ceo" | "ai_cofounder", + executionMode: db.core.company.getCompanyExecutionMode(companyId) as "ai_ceo" | "ai_cofounder", initialUserTurn, internalTools, }; diff --git a/src/main/run-housekeeping.ts b/src/main/run-housekeeping.ts index 58b6a8ca..6a78b571 100644 --- a/src/main/run-housekeeping.ts +++ b/src/main/run-housekeeping.ts @@ -54,7 +54,7 @@ function shouldProduceKnowledge( ); } try { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); return Boolean(agent.department && KNOWLEDGE_PRODUCING_DEPARTMENTS.has(agent.department)); } catch (error) { logger.warn( @@ -97,7 +97,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous try { let topic = `Run ${runId.slice(0, 8)}`; try { - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); topic = task.title; } catch (error) { logger.warn( @@ -105,7 +105,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous ); } - db.knowledgeMod.saveKnowledgeEntry({ + db.core.knowledge.saveKnowledgeEntry({ companyId, category: "lesson_learned", topic, @@ -121,7 +121,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous // 3. On failure, record an activity log entry if (status === "failed") { try { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId, actor: agentId, action: "run.failed", diff --git a/src/main/service-graph.ts b/src/main/service-graph.ts index d169b8c0..d591e4d2 100644 --- a/src/main/service-graph.ts +++ b/src/main/service-graph.ts @@ -108,7 +108,7 @@ export function createServiceGraph(deps: ServiceGraphDependencies) { } function init() { - nativeTheme.themeSource = db.snap.listSnapshot().theme; + nativeTheme.themeSource = db.projection.snapshot.listSnapshot().theme; leaf = createLeafServices({ db, logger, notify, publishDomainChanged }); updaterService = createUpdaterService({ db, logger, notify }); diff --git a/src/main/service-graph/create-desktop-shell.ts b/src/main/service-graph/create-desktop-shell.ts index bf60b046..d240cee1 100644 --- a/src/main/service-graph/create-desktop-shell.ts +++ b/src/main/service-graph/create-desktop-shell.ts @@ -60,13 +60,13 @@ export function createDesktopShell({ properties: ["openDirectory", "createDirectory"], })); if (target.canceled || target.filePaths.length === 0) return; - await db.backupMod.exportBackup(`${target.filePaths[0]!}/agentcompany-backup-${Date.now()}`); + await db.core.backup.exportBackup(`${target.filePaths[0]!}/agentcompany-backup-${Date.now()}`); }, onShortcut: (action) => emitEvent({ type: "shortcut", action }), notify, getActiveRunCount: () => { try { - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); return snapshot.runs.filter((r) => r.status === "running" || r.status === "queued").length; } catch (error) { logger.warn("[service-graph] getActiveRunCount failed:", error); diff --git a/src/main/service-graph/create-lifecycle.ts b/src/main/service-graph/create-lifecycle.ts index c44681d5..6fac0576 100644 --- a/src/main/service-graph/create-lifecycle.ts +++ b/src/main/service-graph/create-lifecycle.ts @@ -62,7 +62,7 @@ export function createLifecycle(deps: LifecycleDeps) { function listActiveRunIds(): string[] { try { - return db.snap.listSnapshot().runs + return db.projection.snapshot.listSnapshot().runs .filter((r) => r.status === "running" || r.status === "queued") .map((r) => r.id); } catch (err) { diff --git a/src/main/service-graph/create-orchestration-service.ts b/src/main/service-graph/create-orchestration-service.ts index d64526f7..21083026 100644 --- a/src/main/service-graph/create-orchestration-service.ts +++ b/src/main/service-graph/create-orchestration-service.ts @@ -67,9 +67,9 @@ export function buildOrchestrationService({ checkpointEngine, getSessionHandoff: (agentId) => sessionService.getHandoffForAgent(agentId), evaluatePreRunSessionRotation: (agentId, _connectorId) => { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (!agent.sessionId) return { rotated: false, handoff: null, sessionId: null }; - const session = db.tasks.getSessionById(agent.sessionId); + const session = db.core.tasks.getSessionById(agent.sessionId); if (!session) return { rotated: false, handoff: null, sessionId: agent.sessionId }; const result = sessionService.evaluateRotation(session); if (result.shouldRotate) { diff --git a/src/main/session-management-service.ts b/src/main/session-management-service.ts index ea84780c..302641ae 100644 --- a/src/main/session-management-service.ts +++ b/src/main/session-management-service.ts @@ -25,7 +25,7 @@ export function createSessionManagementService({ db, logger }: SessionManagement // D-08: Accumulate token usage on task_sessions row (called for ALL adapters per D-05) function accumulateTokenUsage(sessionId: string, inputTokens: number, outputTokens: number, runId: string): void { - db.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId); + db.core.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId); } // Any-of thresholds — first trigger wins. Pi-mono delegates context @@ -133,15 +133,15 @@ export function createSessionManagementService({ db, logger }: SessionManagement logger.info(`[session] Rotating session ${session.id} for agent ${session.agentId}: ${reason}`); - db.tasks.archiveSession(session.id, reason, handoff); + db.core.tasks.archiveSession(session.id, reason, handoff); - const newSessionId = db.tasks.createNewSession({ + const newSessionId = db.core.tasks.createNewSession({ companyId: session.companyId, agentId: session.agentId, adapterType: session.adapterType, }); - db.tasks.setAgentSessionId(session.agentId, newSessionId); + db.core.tasks.setAgentSessionId(session.agentId, newSessionId); logger.info(`[session] New session ${newSessionId} created for agent ${session.agentId}`); return newSessionId; diff --git a/src/main/skills/auto-assign-on-hire.ts b/src/main/skills/auto-assign-on-hire.ts index 9801943f..151333f2 100644 --- a/src/main/skills/auto-assign-on-hire.ts +++ b/src/main/skills/auto-assign-on-hire.ts @@ -72,16 +72,16 @@ export function autoAssignSkillsForHire( return { assignedSkillIds: [] }; } - const alreadyAssigned = new Set(db.skills.listAgentSkillAssignments(input.agentId)); + const alreadyAssigned = new Set(db.core.skills.listAgentSkillAssignments(input.agentId)); const wanted = new Set(categories); - const catalogue = db.skills.listCompanySkills(input.companyId); + const catalogue = db.core.skills.listCompanySkills(input.companyId); const assigned: string[] = []; for (const skill of catalogue) { if (alreadyAssigned.has(skill.id)) continue; const category = readBuiltinCategory(skill.metadata); if (!category || !wanted.has(category)) continue; - db.skills.assignSkillToAgent(input.agentId, skill.id); + db.core.skills.assignSkillToAgent(input.agentId, skill.id); assigned.push(skill.id); } diff --git a/src/main/skills/crud.ts b/src/main/skills/crud.ts index 0fdf408d..65767c70 100644 --- a/src/main/skills/crud.ts +++ b/src/main/skills/crud.ts @@ -23,11 +23,11 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl } function listForCompany(companyId: string): SkillRecord[] { - return db.skills.listCompanySkills(companyId); + return db.core.skills.listCompanySkills(companyId); } function getDetail(companyId: string, skillId: string): DesktopResult { - const skill = db.skills.getCompanySkill(companyId, skillId); + const skill = db.core.skills.getCompanySkill(companyId, skillId); if (!skill) return fail("not_found", `Skill ${skillId} not found`); return ok(skill); } @@ -36,12 +36,12 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl companyId: string, skillId: string, ): Promise> { - const skill = db.skills.getCompanySkill(companyId, skillId); + const skill = db.core.skills.getCompanySkill(companyId, skillId); if (!skill) return fail("not_found", `Skill ${skillId} not found`); try { await cleanupSkillFiles(companyId, skill.slug); - db.skills.deleteCompanySkill(companyId, skillId); + db.core.skills.deleteCompanySkill(companyId, skillId); logger.info(`Skill deleted: ${skill.key}`); return ok(undefined); } catch (err) { @@ -56,9 +56,9 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl agentId: string, skillId: string, ): DesktopResult { - const skill = db.skills.getCompanySkill(companyId, skillId); + const skill = db.core.skills.getCompanySkill(companyId, skillId); if (!skill) return fail("not_found", `Skill ${skillId} not found for company`); - db.skills.assignSkillToAgent(agentId, skillId); + db.core.skills.assignSkillToAgent(agentId, skillId); return ok(undefined); } @@ -67,7 +67,7 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl agentId: string, skillId: string, ): DesktopResult { - db.skills.removeSkillFromAgent(agentId, skillId); + db.core.skills.removeSkillFromAgent(agentId, skillId); return ok(undefined); } @@ -75,7 +75,7 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl companyId: string, skillId: string, ): Promise> { - const skill = db.skills.getCompanySkill(companyId, skillId); + const skill = db.core.skills.getCompanySkill(companyId, skillId); if (!skill) return fail("not_found", `Skill ${skillId} not found`); if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") { diff --git a/src/main/skills/import-github.ts b/src/main/skills/import-github.ts index b9e33ab9..9810c2b4 100644 --- a/src/main/skills/import-github.ts +++ b/src/main/skills/import-github.ts @@ -155,7 +155,7 @@ export function createGitHubImporter({ db, logger, skillFilesDir }: GitHubImport await materializeSkillFiles(companyId, rawSlug, ghSkill.markdown, inventory); - const { isUpdate } = db.skills.upsertCompanySkill({ + const { isUpdate } = db.core.skills.upsertCompanySkill({ id: skillId, companyId, key, diff --git a/src/main/skills/import-local.ts b/src/main/skills/import-local.ts index f3759eec..9963b179 100644 --- a/src/main/skills/import-local.ts +++ b/src/main/skills/import-local.ts @@ -69,7 +69,7 @@ export async function importFromLocal( const skillId = randomUUID(); await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory); - const { isUpdate } = db.skills.upsertCompanySkill({ + const { isUpdate } = db.core.skills.upsertCompanySkill({ id: skillId, companyId, key, @@ -121,7 +121,7 @@ export async function importFromLocal( const skillId = randomUUID(); await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory); - const { isUpdate } = db.skills.upsertCompanySkill({ + const { isUpdate } = db.core.skills.upsertCompanySkill({ id: skillId, companyId, key, diff --git a/src/main/skills/import-url.ts b/src/main/skills/import-url.ts index 8fc94eca..be8071e9 100644 --- a/src/main/skills/import-url.ts +++ b/src/main/skills/import-url.ts @@ -59,7 +59,7 @@ export async function importFromUrl( const skillId = randomUUID(); await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory); - const { isUpdate } = db.skills.upsertCompanySkill({ + const { isUpdate } = db.core.skills.upsertCompanySkill({ id: skillId, companyId, key, diff --git a/src/main/skills/runtime.ts b/src/main/skills/runtime.ts index ab936d79..e1397598 100644 --- a/src/main/skills/runtime.ts +++ b/src/main/skills/runtime.ts @@ -35,9 +35,9 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo agentId: string, _connectorId: string, ): ProjectedSkill[] { - const assignedIds = new Set(db.skills.listAgentSkillAssignments(agentId)); + const assignedIds = new Set(db.core.skills.listAgentSkillAssignments(agentId)); if (assignedIds.size === 0) return []; - const companySkills = db.skills.listCompanySkills(companyId); + const companySkills = db.core.skills.listCompanySkills(companyId); const skillPaths = companySkills .filter((s) => assignedIds.has(s.id)) .map((s) => skillFilesDir(companyId, s.slug)); @@ -82,7 +82,7 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo } } - const workspacePaths = db.workspacesMod.listWorkspaceLocalPaths(companyId); + const workspacePaths = db.core.workspaces.listWorkspaceLocalPaths(companyId); for (const wsPath of workspacePaths) { const agentcompanySkillsDir = join(wsPath, ".agentcompany", "skills"); if (!existsSync(agentcompanySkillsDir)) continue; @@ -159,12 +159,12 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo const results = await importFromLocal(companyId, categoryDir); for (const result of results) { try { - const skill = db.skills.getCompanySkill(companyId, result.skillId); + const skill = db.core.skills.getCompanySkill(companyId, result.skillId); if (skill) { const existingMeta = skill.metadata ? JSON.parse(typeof skill.metadata === "string" ? skill.metadata : JSON.stringify(skill.metadata)) : {}; existingMeta.builtinCategory = category; existingMeta.sourceKind = "builtin"; - db.skills.updateCompanySkillMetadata(companyId, result.skillId, JSON.stringify(existingMeta)); + db.core.skills.updateCompanySkillMetadata(companyId, result.skillId, JSON.stringify(existingMeta)); } } catch (error) { logger.warn( diff --git a/src/main/task-quality-gates.ts b/src/main/task-quality-gates.ts index 8e72e6d5..fb594ee1 100644 --- a/src/main/task-quality-gates.ts +++ b/src/main/task-quality-gates.ts @@ -25,12 +25,14 @@ interface CapabilityMatchResult { } interface CapabilityMatchDb { - entityCrud: { - getAgent(id: string): { name: string; capabilities: string }; - }; - skills: { - listAgentSkillAssignments(id: string): string[]; - getCompanySkill(companyId: string, skillId: string): { name: string; description: string | null; slug: string } | null; + core: { + entity: { + getAgent(id: string): { name: string; capabilities: string }; + }; + skills: { + listAgentSkillAssignments(id: string): string[]; + getCompanySkill(companyId: string, skillId: string): { name: string; description: string | null; slug: string } | null; + }; }; } @@ -132,14 +134,14 @@ export function checkCapabilityMatch( return { matched: true, warning: "Task description has no distinctive keywords for capability matching." }; } - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); const capWords = tokenize(agent.capabilities); const capabilityOverlap = taskWords.filter((w) => capWords.includes(w)).length; let skillOverlap = 0; - const skillIds = db.skills.listAgentSkillAssignments(agentId); + const skillIds = db.core.skills.listAgentSkillAssignments(agentId); for (const skillId of skillIds) { - const skill = db.skills.getCompanySkill(companyId, skillId); + const skill = db.core.skills.getCompanySkill(companyId, skillId); if (skill) { const skillWords = tokenize(`${skill.name} ${skill.description ?? ""} ${skill.slug}`); skillOverlap += taskWords.filter((w) => skillWords.includes(w)).length; @@ -201,7 +203,7 @@ export function sweepStaleReviews( const isCofounderMode = deps.getCompanyExecutionMode(companyId) === "ai_cofounder"; let escalatedCount = 0; - const reviewTasks = deps.db.tasks.listReviewTasksForSweep(companyId); + const reviewTasks = deps.db.core.tasks.listReviewTasksForSweep(companyId); for (const task of reviewTasks) { const reviewAge = now - new Date(task.updatedAt).getTime(); @@ -209,14 +211,14 @@ export function sweepStaleReviews( if (!task.assigneeAgentId) continue; // The reviewer is the assignee's direct manager - const reviewer = deps.db.agents.getManagerForAgent(task.assigneeAgentId); + const reviewer = deps.db.core.agents.getManagerForAgent(task.assigneeAgentId); if (!reviewer) continue; // No manager = CEO-assigned task, skip escalation // D-09: Escalate to the reviewer's manager (manager's manager) - const chain = deps.db.agents.getChainOfCommand(reviewer.id); + const chain = deps.db.core.agents.getChainOfCommand(reviewer.id); const escalationTarget = chain.length > 0 ? chain[0] : null; - const pendingApproval = deps.db.approvalsMod.findPendingApprovalForTask(companyId, task.id); + const pendingApproval = deps.db.core.approvals.findPendingApprovalForTask(companyId, task.id); // In ai_cofounder mode, ALL approvals require human decision — never auto-advance. if (isCofounderMode && pendingApproval) { @@ -285,7 +287,7 @@ export function sweepStaleReviews( // D-10: Advance task status from in_review to done try { - deps.db.tasks.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes."); + deps.db.core.tasks.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes."); deps.handleTaskStatusChange(task.id, companyId, "in_review", "done"); } catch (err) { log.warn(`[task-quality-gates] advanceTaskStatus failed for task ${task.id}:`, err instanceof Error ? err.message : String(err)); diff --git a/src/main/thread-integration/index.ts b/src/main/thread-integration/index.ts index b65754c5..34a5d3a8 100644 --- a/src/main/thread-integration/index.ts +++ b/src/main/thread-integration/index.ts @@ -20,12 +20,12 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh */ onTaskCreated(taskId: string, companyId: string): void { try { - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); if (task.companyId !== companyId) return; const thread = ensureTaskThread(db, taskId, companyId, task.title); - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: task.assigneeAgentId ?? "system", actorType: task.assigneeAgentId ? "agent" : "system", @@ -36,11 +36,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh // Auto-enroll assignee and human as participants if (task.assigneeAgentId) { - db.threads.addParticipant({ threadId: thread.id, participantId: task.assigneeAgentId, participantType: "agent", role: "owner" }); - db.threads.subscribe({ threadId: thread.id, subscriberId: task.assigneeAgentId, subscriberType: "agent" }); + db.core.threads.addParticipant({ threadId: thread.id, participantId: task.assigneeAgentId, participantType: "agent", role: "owner" }); + db.core.threads.subscribe({ threadId: thread.id, subscriberId: task.assigneeAgentId, subscriberType: "agent" }); } - db.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" }); - db.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" }); + db.core.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" }); + db.core.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" }); } catch (err) { logger.error(`[thread-integration] onTaskCreated failed: ${err instanceof Error ? err.message : String(err)}`); } @@ -53,12 +53,12 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh */ onTaskStatusChanged(taskId: string, companyId: string, previousStatus: string, newStatus: string): void { try { - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); if (task.companyId !== companyId) return; const thread = ensureTaskThread(db, taskId, companyId, task.title); - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: task.assigneeAgentId ?? "system", actorType: task.assigneeAgentId ? "agent" : "system", @@ -69,10 +69,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh // In co-founder mode, set wait state when task enters review if (newStatus === "in_review") { - const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode; + const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode; if (mode === "ai_cofounder") { const reason = `@Board review needed: Task "${task.title}" is ready for your review.`; - db.threads.setThreadWaitState({ + db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_review", @@ -85,10 +85,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh // Resolve wait states when task moves past review if (previousStatus === "in_review" && (newStatus === "done" || newStatus === "in_progress")) { - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); for (const ws of waitStates) { if (ws.threadId === thread.id && ws.waitingFor === "user_review") { - db.threads.resolveThreadWaitState(ws.id, companyId); + db.core.threads.resolveThreadWaitState(ws.id, companyId); } } } @@ -104,13 +104,13 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh */ onApprovalCreated(approvalId: string, companyId: string, taskId: string | null, approvalType: string, summary: string): void { try { - const approval = db.approvalsMod.getApprovalRecord(approvalId); - const task = taskId ? db.entityCrud.getTask(taskId) : null; + const approval = db.core.approvals.getApprovalRecord(approvalId); + const task = taskId ? db.core.entity.getTask(taskId) : null; const thread = task ? ensureTaskThread(db, task.id, companyId, task.title) : ensureApprovalThread(db, approvalId, companyId, summary || approval.payloadSummary); - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: "system", actorType: "system", @@ -120,17 +120,17 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh }); if (approval.requestedByAgentId) { - db.threads.addParticipant({ threadId: thread.id, participantId: approval.requestedByAgentId, participantType: "agent", role: "owner" }); - db.threads.subscribe({ threadId: thread.id, subscriberId: approval.requestedByAgentId, subscriberType: "agent" }); + db.core.threads.addParticipant({ threadId: thread.id, participantId: approval.requestedByAgentId, participantType: "agent", role: "owner" }); + db.core.threads.subscribe({ threadId: thread.id, subscriberId: approval.requestedByAgentId, subscriberType: "agent" }); } - db.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" }); - db.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" }); + db.core.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" }); + db.core.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" }); // In co-founder mode, set wait state for user approval - const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode; + const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode; if (mode === "ai_cofounder") { const approvalReason = `@Board approval needed: ${approvalType}`; - db.threads.setThreadWaitState({ + db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_approval", @@ -152,13 +152,13 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh onApprovalResolved(approvalId: string, companyId: string, decision: string): void { try { // Resolve any wait states linked to this approval - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); for (const ws of waitStates) { if (ws.relatedApprovalId === approvalId) { - db.threads.resolveThreadWaitState(ws.id, companyId); + db.core.threads.resolveThreadWaitState(ws.id, companyId); // Add decision event to the thread - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: ws.threadId, actorId: "user", actorType: "user", @@ -181,11 +181,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh try { if (!taskId) return; - db.entityCrud.getTask(taskId); // validates task exists and belongs to company + db.core.entity.getTask(taskId); // validates task exists and belongs to company const thread = findTaskThread(db, taskId, companyId); if (!thread) return; - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: authorAgentId ?? "system", actorType: authorAgentId ? "agent" : "system", @@ -208,7 +208,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh const thread = findTaskThread(db, taskId, companyId); if (!thread) return; - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: agentId, actorType: "agent", @@ -235,7 +235,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh ? "Execution completed successfully" : `Execution ${status}${errorMessage ? `: ${errorMessage}` : ""}`; - db.threads.addThreadEvent({ + db.core.threads.addThreadEvent({ threadId: thread.id, actorId: agentId, actorType: "agent", @@ -247,11 +247,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh // In co-founder mode, successful runs create a user_reply wait state // so the heartbeat pauses until the human reviews the output if (status === "succeeded") { - const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode; + const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode; if (mode === "ai_cofounder") { - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); const completionReason = `@Board review needed: Agent completed work on "${task.title}" — awaiting your review.`; - db.threads.setThreadWaitState({ + db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_reply", @@ -280,7 +280,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh try { // Most precise: resolve a single identified wait state if (waitStateId) { - db.threads.resolveThreadWaitState(waitStateId, companyId); + db.core.threads.resolveThreadWaitState(waitStateId, companyId); return; } @@ -288,7 +288,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh const SCOPED_WAIT_TYPES = new Set(["user_reply", "user_review"]); const isScoped = Boolean(threadId || toAgentId); - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); for (const ws of waitStates) { if (isScoped) { if (!SCOPED_WAIT_TYPES.has(ws.waitingFor)) continue; @@ -301,10 +301,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh if (threadId) { if (ws.threadId !== threadId) continue; } else if (toAgentId) { - const thread = db.threads.getThread(ws.threadId, companyId); + const thread = db.core.threads.getThread(ws.threadId, companyId); if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue; try { - const task = db.entityCrud.getTask(thread.relatedEntityId); + const task = db.core.entity.getTask(thread.relatedEntityId); if (task.assigneeAgentId !== toAgentId) continue; } catch (err) { logger.warn(`[thread-integration] getTask failed for thread ${ws.threadId}:`, err instanceof Error ? err.message : String(err)); @@ -312,7 +312,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh } } - db.threads.resolveThreadWaitState(ws.id, companyId); + db.core.threads.resolveThreadWaitState(ws.id, companyId); } } catch (err) { logger.error(`[thread-integration] onHumanMessage failed: ${err instanceof Error ? err.message : String(err)}`); @@ -321,7 +321,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh hasUnresolvedWaitStates(companyId: string): boolean { try { - const waitStates = db.threads.listThreadWaitStates(companyId); + const waitStates = db.core.threads.listThreadWaitStates(companyId); return waitStates.length > 0; } catch (err) { logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}:`, err instanceof Error ? err.message : String(err)); @@ -337,7 +337,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh */ hasUnresolvedWaitStatesForAgent(companyId: string, agentId: string): boolean { try { - return db.threads.hasUnresolvedWaitStateForAgent(companyId, agentId); + return db.core.threads.hasUnresolvedWaitStateForAgent(companyId, agentId); } catch (err) { logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}):`, err instanceof Error ? err.message : String(err)); return false; @@ -350,7 +350,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh */ countUnresolvedWaitStates(companyId: string): number { try { - return db.threads.listThreadWaitStates(companyId).length; + return db.core.threads.listThreadWaitStates(companyId).length; } catch (err) { logger.warn(`[thread-integration] countUnresolvedWaitStates failed for company ${companyId}:`, err instanceof Error ? err.message : String(err)); return 0; diff --git a/src/main/thread-integration/thread-helpers.ts b/src/main/thread-integration/thread-helpers.ts index e4ab3342..6e288da7 100644 --- a/src/main/thread-integration/thread-helpers.ts +++ b/src/main/thread-integration/thread-helpers.ts @@ -12,7 +12,7 @@ export interface ThreadIntegrationDeps { } export function findTaskThread(db: AppDatabase, taskId: string, companyId: string) { - const existing = db.threads.listThreads({ + const existing = db.core.threads.listThreads({ companyId, type: "task" as ThreadType, relatedEntityType: "task", @@ -26,7 +26,7 @@ export function ensureTaskThread(db: AppDatabase, taskId: string, companyId: str const existing = findTaskThread(db, taskId, companyId); if (existing) return existing; - return db.threads.createThread({ + return db.core.threads.createThread({ companyId, title, type: "task" as ThreadType, @@ -36,7 +36,7 @@ export function ensureTaskThread(db: AppDatabase, taskId: string, companyId: str } export function findAgentCommunicationThread(db: AppDatabase, agentId: string, companyId: string) { - const existing = db.threads.listThreads({ + const existing = db.core.threads.listThreads({ companyId, type: "general" as ThreadType, relatedEntityType: "agent", @@ -62,7 +62,7 @@ export function ensureAgentCommunicationThread( const existing = findAgentCommunicationThread(db, agentId, companyId); if (existing) return existing; - return db.threads.createThread({ + return db.core.threads.createThread({ companyId, title: `${agentLabel} · communication`, type: "general" as ThreadType, @@ -72,7 +72,7 @@ export function ensureAgentCommunicationThread( } export function findApprovalThread(db: AppDatabase, approvalId: string, companyId: string) { - const existing = db.threads.listThreads({ + const existing = db.core.threads.listThreads({ companyId, type: "approval" as ThreadType, relatedEntityType: "approval", @@ -86,7 +86,7 @@ export function ensureApprovalThread(db: AppDatabase, approvalId: string, compan const existing = findApprovalThread(db, approvalId, companyId); if (existing) return existing; - return db.threads.createThread({ + return db.core.threads.createThread({ companyId, title, type: "approval" as ThreadType, @@ -104,7 +104,7 @@ function resolveApprovalAnnouncementChannel( ): { channel: "department" | "project" | "company"; channelTargetId: string | null } | null { if (requestedByAgentId) { try { - const requester = db.entityCrud.getAgent(requestedByAgentId); + const requester = db.core.entity.getAgent(requestedByAgentId); if (requester.companyId === companyId && requester.department) { return { channel: "department", channelTargetId: requester.department }; } @@ -115,7 +115,7 @@ function resolveApprovalAnnouncementChannel( if (taskId) { try { - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); if (task.companyId === companyId && task.projectId) { return { channel: "project", channelTargetId: task.projectId }; } @@ -150,7 +150,7 @@ export function announceApprovalToBoard( ); if (!target) return; - const messageId = db.messages.sendAgentMessage({ + const messageId = db.core.messages.sendAgentMessage({ companyId: approval.companyId, fromAgentId: approval.requestedByAgentId, channel: target.channel, @@ -160,7 +160,7 @@ export function announceApprovalToBoard( priority: "urgent", senderKind: "agent", }); - const message = db.messages.getAgentMessage(messageId, approval.companyId); + const message = db.core.messages.getAgentMessage(messageId, approval.companyId); if (message) { deps.emitEvent({ type: "new-message", message }); } @@ -176,7 +176,7 @@ export function notifyThreadSubscribers( notify?: (options: { title: string; body: string }) => void, ): void { if (!notify) return; - const subscriptions = db.threads.listSubscriptions(threadId); + const subscriptions = db.core.threads.listSubscriptions(threadId); for (const sub of subscriptions) { if (sub.subscriberType !== "user") continue; if (sub.notifyOn === "none") continue; diff --git a/src/main/updater-service.ts b/src/main/updater-service.ts index 5e0cfef2..665a6f86 100644 --- a/src/main/updater-service.ts +++ b/src/main/updater-service.ts @@ -107,7 +107,7 @@ export function createUpdaterService({ } async function checkForUpdates(options: { manual?: boolean } = {}) { - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const updateUrl = process.env.AGENTCOMPANY_UPDATE_URL?.trim(); const skipReason = shouldSkipUpdateCheck({ isPackaged: app.isPackaged, diff --git a/src/main/worker-service.ts b/src/main/worker-service.ts index 1027bf2b..1a6a2d4f 100644 --- a/src/main/worker-service.ts +++ b/src/main/worker-service.ts @@ -181,10 +181,10 @@ export function createWorkerService({ // Mark all running/queued runs as interrupted so DB state is clean on restart try { - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); for (const run of snapshot.runs) { if (run.status === "running" || run.status === "queued") { - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "interrupted", summary: "Application shut down.", @@ -195,10 +195,10 @@ export function createWorkerService({ unattributedCost: true, }); if (run.workspaceId) { - db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id); + db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id); } if (run.taskId) { - db.tasks.releaseTaskLock(run.taskId, run.id); + db.core.tasks.releaseTaskLock(run.taskId, run.id); } } } diff --git a/src/main/worker/entry/crash-recovery.ts b/src/main/worker/entry/crash-recovery.ts index 3b2111fe..d451391f 100644 --- a/src/main/worker/entry/crash-recovery.ts +++ b/src/main/worker/entry/crash-recovery.ts @@ -22,7 +22,7 @@ export function handleWorkerExit( logger.warn(`[worker] Worker exited with code ${code}, recovering...`); - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const affectedAgentIds = new Set(); const affectedCompanyIds = new Map(); const requeueCandidates: Array<{ @@ -36,7 +36,7 @@ export function handleWorkerExit( for (const run of snapshot.runs) { if (run.status === "running") { - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "interrupted", summary: "Worker process crashed.", @@ -47,16 +47,16 @@ export function handleWorkerExit( unattributedCost: true, }); if (run.workspaceId) { - db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id); + db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id); } // EXEC-02: Release execution lock on crash recovery if (run.taskId) { - db.tasks.releaseTaskLock(run.taskId, run.id); + db.core.tasks.releaseTaskLock(run.taskId, run.id); } affectedAgentIds.add(run.agentId); affectedCompanyIds.set(run.agentId, run.companyId); - const chain = db.agents.getChainOfCommand(run.agentId); + const chain = db.core.agents.getChainOfCommand(run.agentId); if (chain.length > 0) { wakeAgentIfPossible(chain[0].id, run.companyId, "report_failed"); } @@ -75,11 +75,11 @@ export function handleWorkerExit( for (const agentId of affectedAgentIds) { // EXEC-05: Promote deferred wakes after crash recovery const cid = affectedCompanyIds.get(agentId) ?? ""; - const promoted = db.wakeup.promoteWakeupRequests(agentId, cid); + const promoted = db.automation.wakeup.promoteWakeupRequests(agentId, cid); if (promoted > 0) { logger.info(`[worker] Promoted ${promoted} deferred wake(s) for agent ${agentId} after crash recovery`); } - const pendingWakeTrigger = db.wakeup.getPreferredWakeTrigger(agentId, cid); + const pendingWakeTrigger = db.automation.wakeup.getPreferredWakeTrigger(agentId, cid); if (pendingWakeTrigger) { wakeAgentIfPossible(agentId, cid, pendingWakeTrigger); } @@ -92,18 +92,18 @@ export function handleWorkerExit( const worker = getWorker(); if (requeueCandidates.length > 0 && worker) { logger.info(`[worker] Re-enqueuing ${requeueCandidates.length} queued run(s) after worker restart.`); - const recoverySnapshot = db.snap.listSnapshot(); + const recoverySnapshot = db.projection.snapshot.listSnapshot(); for (const candidate of requeueCandidates) { try { // The crashed run may still own a workspace lock acquired during the // original dispatch; release it before re-dispatch so the replay // does not self-collide with "Workspace is locked by another run". if (candidate.workspaceId) { - db.workspacesMod.releaseWorkspaceLock(candidate.workspaceId, candidate.id); + db.core.workspaces.releaseWorkspaceLock(candidate.workspaceId, candidate.id); } - const task = candidate.taskId ? db.entityCrud.getTask(candidate.taskId) : null; - const agent = db.entityCrud.getAgent(candidate.agentId); - const connector = db.connectorsCrud.getConnector(agent.connectorId); + const task = candidate.taskId ? db.core.entity.getTask(candidate.taskId) : null; + const agent = db.core.entity.getAgent(candidate.agentId); + const connector = db.core.connectors.getConnector(agent.connectorId); const { workspace, lockMode } = resolveWorkspaceWithLockMode(db, task, agent, candidate.companyId); const recoveryCompany = recoverySnapshot.companies.find((company) => company.id === candidate.companyId); @@ -122,7 +122,7 @@ export function handleWorkerExit( }); } catch (error) { logger.warn(`[worker] Failed to re-enqueue run ${candidate.id}: ${error instanceof Error ? error.message : String(error)}`); - db.runs.finishRun({ + db.core.runs.finishRun({ runId: candidate.id, status: "interrupted", summary: "Could not re-enqueue after worker restart.", @@ -133,7 +133,7 @@ export function handleWorkerExit( unattributedCost: true, }); if (candidate.workspaceId) { - db.workspacesMod.releaseWorkspaceLock(candidate.workspaceId, candidate.id); + db.core.workspaces.releaseWorkspaceLock(candidate.workspaceId, candidate.id); } } } diff --git a/src/main/worker/entry/types.ts b/src/main/worker/entry/types.ts index b88535f6..97d054fd 100644 --- a/src/main/worker/entry/types.ts +++ b/src/main/worker/entry/types.ts @@ -111,14 +111,14 @@ export interface WorkerServiceDependencies { dispatchRunToWorker: (params: { runId: string; companyId: string; - agent: ReturnType; - task: ReturnType | null; - connector: ReturnType; - workspace: ReturnType; + agent: ReturnType; + task: ReturnType | null; + connector: ReturnType; + workspace: ReturnType; workspaceMode: "exclusive" | "shared" | "none"; companyName: string; companyDescription: string; - snapshot: ReturnType; + snapshot: ReturnType; trigger: string; }) => void; processRunCompletion: (run: RunRecord, event: z.infer & { type: "run-finished" }) => void; diff --git a/src/main/worker/handlers/context-compaction-handler.ts b/src/main/worker/handlers/context-compaction-handler.ts index 2074431a..c0dfd92e 100644 --- a/src/main/worker/handlers/context-compaction-handler.ts +++ b/src/main/worker/handlers/context-compaction-handler.ts @@ -6,7 +6,7 @@ export function handleContextCompaction( deps: HandlerDeps, _context: HandlerContext, ): void { - const runState = deps.db.runs.getRunAuthState(event.runId); + const runState = deps.db.core.runs.getRunAuthState(event.runId); if (!runState) { deps.logger.warn(`[context-compaction] run ${event.runId} disappeared before telemetry could be recorded.`); return; diff --git a/src/main/worker/handlers/run-finished-helpers.ts b/src/main/worker/handlers/run-finished-helpers.ts index 80013495..736f8cbc 100644 --- a/src/main/worker/handlers/run-finished-helpers.ts +++ b/src/main/worker/handlers/run-finished-helpers.ts @@ -7,13 +7,13 @@ import { isUsageStatusUnattributed } from "../../run-usage-status"; import { decideRunAttempt, runAttemptKey } from "../../run-attempt-policy"; type RunFinishedEvent = Extract; -type RunRecord = ReturnType; -type TaskRecord = ReturnType; +type RunRecord = ReturnType; +type TaskRecord = ReturnType; export interface RunFinishedContext { run: RunRecord; task: TaskRecord | null; - runOutcome: ReturnType; + runOutcome: ReturnType; } export function finalizeRun( @@ -26,7 +26,7 @@ export function finalizeRun( let run: RunRecord; try { - run = db.runs.getRun(event.runId); + run = db.core.runs.getRun(event.runId); } catch (error) { logger.warn( `[finalizeRun] getRun(${event.runId}) failed; run may have been deleted: ${error instanceof Error ? error.message : String(error)}`, @@ -60,7 +60,7 @@ export function finalizeRun( let task: TaskRecord | null = null; if (run.taskId) { try { - task = db.entityCrud.getTask(run.taskId); + task = db.core.entity.getTask(run.taskId); } catch (error) { logger.warn( `[finalizeRun] getTask(${run.taskId}) failed; task may have been deleted: ${error instanceof Error ? error.message : String(error)}`, @@ -70,11 +70,11 @@ export function finalizeRun( } leaseRenewalMap.delete(event.runId); - const runOutcome = db.runs.finishRun({ ...event, unattributedCost }); + const runOutcome = db.core.runs.finishRun({ ...event, unattributedCost }); if (run.workspaceId) { - db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id); + db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id); } - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: run.companyId, actor: run.agentId, action: `run.${event.status}`, @@ -101,9 +101,9 @@ export function accumulateAndRotateSession( // heartbeat run — the exact failure mode that lets a taskless transcript // grow past the model's context window. Resolve the session through the // agent, never through the task. - let agentRecord: ReturnType; + let agentRecord: ReturnType; try { - agentRecord = db.entityCrud.getAgent(run.agentId); + agentRecord = db.core.entity.getAgent(run.agentId); } catch (error) { logger.warn( `[session] accumulateAndRotateSession: getAgent(${run.agentId}) failed; skipping: ${error instanceof Error ? error.message : String(error)}`, @@ -111,7 +111,7 @@ export function accumulateAndRotateSession( return; } if (!agentRecord.sessionId) return; - const session = db.tasks.getSessionById(agentRecord.sessionId); + const session = db.core.tasks.getSessionById(agentRecord.sessionId); if (!session) return; sessionService.accumulateTokenUsage( @@ -124,7 +124,7 @@ export function accumulateAndRotateSession( // that include this run, otherwise a run that just crossed the // threshold waits a full additional cycle to rotate — the exact // failure mode that let transcripts balloon past the context window. - const updatedSession = db.tasks.getSessionById(agentRecord.sessionId); + const updatedSession = db.core.tasks.getSessionById(agentRecord.sessionId); if (!updatedSession) return; const rotationResult = sessionService.evaluateRotation(updatedSession); if (rotationResult.shouldRotate) { @@ -158,9 +158,9 @@ export function emitAndNotifyCompletion( }); } - let updatedAgent: ReturnType | null = null; + let updatedAgent: ReturnType | null = null; try { - updatedAgent = db.entityCrud.getAgent(run.agentId); + updatedAgent = db.core.entity.getAgent(run.agentId); } catch (error) { logger.warn( `[emitAndNotifyCompletion] getAgent(${run.agentId}) failed; skipping budget-paused notification: ${error instanceof Error ? error.message : String(error)}`, @@ -169,7 +169,7 @@ export function emitAndNotifyCompletion( } if (updatedAgent && runOutcome.budgetHardStopped) { - db.activityMod.addActivity({ + db.core.activity.addActivity({ companyId: updatedAgent.companyId, actor: "system", action: "agent.auto_paused", @@ -233,8 +233,8 @@ export function runPostCompletionAutomation( // EXEC-05: Promote deferred wakes after run completion let hadDeferredWakes = false; if (!isQuitting() && !destroyed) { - const promoted = db.wakeup.promoteWakeupRequests(run.agentId, run.companyId); - const pendingWakeTrigger = db.wakeup.getPreferredWakeTrigger(run.agentId, run.companyId); + const promoted = db.automation.wakeup.promoteWakeupRequests(run.agentId, run.companyId); + const pendingWakeTrigger = db.automation.wakeup.getPreferredWakeTrigger(run.agentId, run.companyId); if (promoted > 0) { logger.info(`[worker] Promoted ${promoted} deferred wake(s) for agent ${run.agentId}`); } @@ -297,7 +297,7 @@ export function triggerSelfWake( if (hadDeferredWakes || isQuitting() || destroyed) return; try { - const actionableTasks = db.tasks.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]); + const actionableTasks = db.core.tasks.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]); const hasWorkToContinue = actionableTasks.length > 0 || hasUnfinishedTeamBuilding(db, run.agentId, run.companyId); const consecutiveSelfWakes = selfWakeAttemptMap.get(attemptKey) ?? 0; const decision = decideRunAttempt({ @@ -316,7 +316,7 @@ export function triggerSelfWake( ); // Escalate to manager since agent is stuck try { - const chain = db.agents.getChainOfCommand(run.agentId); + const chain = db.core.agents.getChainOfCommand(run.agentId); if (chain.length > 0) { wakeAgentIfPossible(chain[0].id, run.companyId, "report_stuck"); } @@ -354,15 +354,15 @@ function hasUnfinishedTeamBuilding( companyId: string, ): boolean { try { - const agent = db.entityCrud.getAgent(agentId); + const agent = db.core.entity.getAgent(agentId); if (agent.reportsTo !== null) return false; - const activeReports = db.agents + const activeReports = db.core.agents .getDirectReports(agentId, companyId) .filter((r) => r.status !== "pending_approval" && r.status !== "terminated"); if (activeReports.length > 0) return false; - const goalCount = db.entityCrud.countActiveGoalsForCompany(companyId); + const goalCount = db.core.entity.countActiveGoalsForCompany(companyId); if (goalCount === 0) return false; - const pendingHires = db.approvalsMod + const pendingHires = db.core.approvals .listCompanyApprovalsByType(companyId, "hire_agent") .filter((a) => a.state === "pending"); return pendingHires.length === 0; diff --git a/src/main/worker/handlers/run-log-handler.ts b/src/main/worker/handlers/run-log-handler.ts index dbae9431..5bcc3447 100644 --- a/src/main/worker/handlers/run-log-handler.ts +++ b/src/main/worker/handlers/run-log-handler.ts @@ -16,8 +16,8 @@ export async function handleRunLog( const filtered = filterRunLogChunk(event.stream, event.chunk); if (filtered === null) return; - await db.runs.appendRunLog(event.runId, event.stream, filtered); - const runState = db.runs.getRunAuthState(event.runId); + await db.core.runs.appendRunLog(event.runId, event.stream, filtered); + const runState = db.core.runs.getRunAuthState(event.runId); if (runState) { persistRunEvent(db, logger, { companyId: runState.companyId, @@ -36,9 +36,9 @@ export async function handleRunLog( const lastRenewal = leaseRenewalMap.get(event.runId) ?? 0; if (now - lastRenewal >= LEASE_RENEWAL_INTERVAL_MS) { try { - const run = db.runs.getRun(event.runId); + const run = db.core.runs.getRun(event.runId); if (run.taskId) { - db.tasks.renewTaskLease(run.taskId, run.id); + db.core.tasks.renewTaskLease(run.taskId, run.id); } leaseRenewalMap.set(event.runId, now); } catch (err) { diff --git a/src/main/worker/handlers/run-status-handler.ts b/src/main/worker/handlers/run-status-handler.ts index 4ebf95d0..528585c2 100644 --- a/src/main/worker/handlers/run-status-handler.ts +++ b/src/main/worker/handlers/run-status-handler.ts @@ -9,8 +9,8 @@ export function handleRunStatus( ): void { const { db, logger, emitEvent, onRunStarted } = deps; - const runState = db.runs.getRunAuthState(event.runId); - db.runs.markRunStatus(event.runId, event.status, event.message); + const runState = db.core.runs.getRunAuthState(event.runId); + db.core.runs.markRunStatus(event.runId, event.status, event.message); if (runState) { persistRunEvent(db, logger, { companyId: runState.companyId, @@ -30,9 +30,9 @@ export function handleRunStatus( // dispatchRunToWorker before enqueue. Only lease renewal and // run-started housekeeping run here. try { - const run = db.runs.getRun(event.runId); + const run = db.core.runs.getRun(event.runId); if (run.taskId) { - db.tasks.renewTaskLease(run.taskId, run.id); + db.core.tasks.renewTaskLease(run.taskId, run.id); } onRunStarted?.(run.id, run.taskId, run.agentId, run.companyId); } catch (err) { diff --git a/src/main/worker/observability/failure-escalation.ts b/src/main/worker/observability/failure-escalation.ts index da2bfbfc..56311f66 100644 --- a/src/main/worker/observability/failure-escalation.ts +++ b/src/main/worker/observability/failure-escalation.ts @@ -13,15 +13,17 @@ import { decideRunAttempt } from "../../run-attempt-policy"; export interface EscalationDeps { db: { - tasks: { - getTaskRetryCount: (taskId: string) => number; - incrementTaskRetryCount: (taskId: string) => void; - resetTaskRetryCount: (taskId: string) => void; - advanceTaskStatus: (taskId: string, status: string, note: string) => unknown; - }; - agents: { - getChainOfCommand: (agentId: string) => Array<{ id: string; name: string }>; - getCeoForCompany: (companyId: string) => { id: string; name: string } | null; + core: { + tasks: { + getTaskRetryCount: (taskId: string) => number; + incrementTaskRetryCount: (taskId: string) => void; + resetTaskRetryCount: (taskId: string) => void; + advanceTaskStatus: (taskId: string, status: string, note: string) => unknown; + }; + agents: { + getChainOfCommand: (agentId: string) => Array<{ id: string; name: string }>; + getCeoForCompany: (companyId: string) => { id: string; name: string } | null; + }; }; }; wakeAgentIfPossible: (agentId: string, companyId: string, trigger: string) => unknown; @@ -48,7 +50,7 @@ export function handleFailureEscalation( scheduleRetry: (fn: () => void, delayMs: number) => void, ): EscalationResult { const { db, wakeAgentIfPossible, notify, logger } = deps; - const retryCount = db.tasks.getTaskRetryCount(run.taskId); + const retryCount = db.core.tasks.getTaskRetryCount(run.taskId); const retryDecision = decideRunAttempt({ kind: "task_failure", retryCount, @@ -56,8 +58,8 @@ export function handleFailureEscalation( }); if (retryDecision.action === "retry") { - db.tasks.incrementTaskRetryCount(run.taskId); - db.tasks.advanceTaskStatus( + db.core.tasks.incrementTaskRetryCount(run.taskId); + db.core.tasks.advanceTaskStatus( run.taskId, "todo", `Run ${eventStatus} (attempt ${retryDecision.attemptNumber}/${maxRetryAttempts}). Auto-retrying after ${retryCooldownMs / 1000}s cooldown.`, @@ -72,14 +74,14 @@ export function handleFailureEscalation( } // Retry limit reached — escalate up the chain - db.tasks.resetTaskRetryCount(run.taskId); + db.core.tasks.resetTaskRetryCount(run.taskId); logger.warn( `[escalation] Task ${run.taskId} failed ${maxRetryAttempts} times. Escalating to manager.`, ); let escalated = false; try { - const chain = db.agents.getChainOfCommand(run.agentId); + const chain = db.core.agents.getChainOfCommand(run.agentId); if (chain.length > 0) { wakeAgentIfPossible(chain[0].id, run.companyId, "report_failed"); escalated = true; @@ -95,7 +97,7 @@ export function handleFailureEscalation( if (!escalated) { try { - const ceo = db.agents.getCeoForCompany(run.companyId); + const ceo = db.core.agents.getCeoForCompany(run.companyId); if (ceo) { wakeAgentIfPossible(ceo.id, run.companyId, "report_failed"); escalated = true; @@ -131,7 +133,7 @@ export function handleSuccessReset( logger?: EscalationDeps["logger"], ): void { try { - db.tasks.resetTaskRetryCount(taskId); + db.core.tasks.resetTaskRetryCount(taskId); } catch (error) { logger?.warn( `[escalation] resetTaskRetryCount failed for task ${taskId}: ${error instanceof Error ? error.message : String(error)}`, diff --git a/src/main/worker/observability/run-event-recorder.ts b/src/main/worker/observability/run-event-recorder.ts index d01bb53b..1f87b3d9 100644 --- a/src/main/worker/observability/run-event-recorder.ts +++ b/src/main/worker/observability/run-event-recorder.ts @@ -22,7 +22,7 @@ export function persistRunEvent( input: PersistInput, ) { try { - db.runs.appendRunEvent(input); + db.core.runs.appendRunEvent(input); } catch (error) { logger.warn( `[run-events] Failed to append ${input.eventType} for ${input.runId}: ${ diff --git a/src/main/worker/runtime/internal-tools/file-approval-tool.ts b/src/main/worker/runtime/internal-tools/file-approval-tool.ts index 0cc0a5c7..640db6e5 100644 --- a/src/main/worker/runtime/internal-tools/file-approval-tool.ts +++ b/src/main/worker/runtime/internal-tools/file-approval-tool.ts @@ -4,7 +4,7 @@ * Collapses the `bash + node -e + fetch POST /api/companies/:id/approvals` * chain into a single tool-call the CEO (or any agent with request * authority) can invoke directly. The handler in main reuses the same - * `db.approvalsMod.requestApproval` primitive and auto-approval policy + * `db.core.approvals.requestApproval` primitive and auto-approval policy * the HTTP route uses, so both paths produce identical rows and the * `approve_ceo_strategy` ordering gate fires identically. * diff --git a/tests/integration/agent-model-switching.spec.ts b/tests/integration/agent-model-switching.spec.ts index cb905326..c42bb386 100644 --- a/tests/integration/agent-model-switching.spec.ts +++ b/tests/integration/agent-model-switching.spec.ts @@ -58,7 +58,7 @@ function createHarness(db: AppDatabase) { runDispatch.resolveWorkspace, runDispatch.getRunEligibilityFailure, runDispatch.dispatchRunToWorker, - (filter) => db.runs.hasActiveRun(filter), + (filter) => db.core.runs.hasActiveRun(filter), ); return { enqueueWorkerRun, taskRunner }; @@ -70,7 +70,7 @@ describe("agent model switching", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -78,12 +78,12 @@ describe("agent model switching", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Model Switch Co", description: "Tests post-hire model switching", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -91,7 +91,7 @@ describe("agent model switching", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -107,7 +107,7 @@ describe("agent model switching", () => { const { enqueueWorkerRun, taskRunner } = createHarness(db); - const firstTaskId = db.tasks.saveTask({ + const firstTaskId = db.core.tasks.saveTask({ companyId, title: "Initial model task", description: "Use the first model for this run.", @@ -126,7 +126,7 @@ describe("agent model switching", () => { throw new Error("Expected first run to queue successfully."); } - db.runs.finishRun({ + db.core.runs.finishRun({ runId: firstRun.data, status: "succeeded", summary: "Done", @@ -137,7 +137,7 @@ describe("agent model switching", () => { unattributedCost: true, }); - db.agents.saveAgent({ + db.core.agents.saveAgent({ id: agentId, companyId, name: "Engineer", @@ -152,7 +152,7 @@ describe("agent model switching", () => { budgetMonthlyUsd: 200, }); - const secondTaskId = db.tasks.saveTask({ + const secondTaskId = db.core.tasks.saveTask({ companyId, title: "Updated model task", description: "Use the new model for this run.", diff --git a/tests/integration/api-recursive-org-execution.spec.ts b/tests/integration/api-recursive-org-execution.spec.ts index a2c6cc18..45ca738e 100644 --- a/tests/integration/api-recursive-org-execution.spec.ts +++ b/tests/integration/api-recursive-org-execution.spec.ts @@ -25,7 +25,7 @@ async function createHarness() { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -33,12 +33,12 @@ async function createHarness() { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Recursive Org Co", description: "Tests manager-driven recursive hires", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -46,7 +46,7 @@ async function createHarness() { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -59,7 +59,7 @@ async function createHarness() { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", @@ -73,7 +73,7 @@ async function createHarness() { budgetMonthlyUsd: 220, department: "engineering", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Build the org", description: "CTO should hire the first IC under engineering.", @@ -82,7 +82,7 @@ async function createHarness() { priority: "high", status: "in_progress", }); - const run = db.runs.createRun({ companyId, taskId, agentId: ctoId, workspaceId, connectorId: "openai-codex" }); + const run = db.core.runs.createRun({ companyId, taskId, agentId: ctoId, workspaceId, connectorId: "openai-codex" }); const token = createAgentToken(ctoId, companyId, run.id); const server = createApiServer(db, createTestApiServerCallbacks()); const port = await server.start(); @@ -127,26 +127,26 @@ describe("recursive org execution", () => { const body = await response.json() as { agentId: string; approvalId: string; autoApproved: boolean }; expect(body.autoApproved).toBe(false); - const pendingHire = harness.db.entityCrud.getAgent(body.agentId); + const pendingHire = harness.db.core.entity.getAgent(body.agentId); expect(pendingHire.status).toBe("pending_approval"); expect(pendingHire.reportsTo).toBe(harness.ctoId); - const approval = harness.db.approvalsMod.getApprovalRecord(body.approvalId); + const approval = harness.db.core.approvals.getApprovalRecord(body.approvalId); expect(approval.type).toBe("hire_agent"); expect(approval.requestedByAgentId).toBe(harness.ctoId); expect(approval.relatedAgentId).toBe(body.agentId); - harness.db.approvalsMod.decideApproval({ + harness.db.core.approvals.decideApproval({ approvalId: body.approvalId, state: "approved", decisionNote: "Approved by board", }); - const approvedHire = harness.db.entityCrud.getAgent(body.agentId); + const approvedHire = harness.db.core.entity.getAgent(body.agentId); expect(approvedHire.status).toBe("idle"); - expect(harness.db.agents.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId); - expect(harness.db.agents.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId); - expect(harness.db.agents.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]); + expect(harness.db.core.agents.getDirectReports(harness.ctoId, harness.companyId).map((agent) => agent.id)).toContain(body.agentId); + expect(harness.db.core.agents.getDirectReports(harness.ceoId, harness.companyId).map((agent) => agent.id)).not.toContain(body.agentId); + expect(harness.db.core.agents.getChainOfCommand(body.agentId).map((agent) => agent.id)).toEqual([harness.ctoId, harness.ceoId]); const agentResponse = await fetch(`${harness.baseUrl}/api/agents/${body.agentId}`, { headers: harness.headers, @@ -170,7 +170,7 @@ describe("recursive org execution", () => { it("routes cross-department hires to the relevant department lead when reportsTo is omitted", async () => { const harness = await createHarness(); try { - const hrId = harness.db.agents.saveAgent({ + const hrId = harness.db.core.agents.saveAgent({ companyId: harness.companyId, name: "HR Director", role: "HR Director", @@ -184,7 +184,7 @@ describe("recursive org execution", () => { budgetMonthlyUsd: 180, department: "hr", }); - const hrTaskId = harness.db.tasks.saveTask({ + const hrTaskId = harness.db.core.tasks.saveTask({ companyId: harness.companyId, title: "Fill engineering gap", description: "Request a backend engineer for the engineering team.", @@ -193,7 +193,7 @@ describe("recursive org execution", () => { priority: "high", status: "in_progress", }); - const hrRun = harness.db.runs.createRun({ + const hrRun = harness.db.core.runs.createRun({ companyId: harness.companyId, taskId: hrTaskId, agentId: hrId, @@ -224,7 +224,7 @@ describe("recursive org execution", () => { expect(response.status).toBe(201); const body = await response.json() as { agentId: string }; - const pendingHire = harness.db.entityCrud.getAgent(body.agentId); + const pendingHire = harness.db.core.entity.getAgent(body.agentId); expect(pendingHire.department).toBe("engineering"); expect(pendingHire.reportsTo).toBe(harness.ctoId); } finally { @@ -264,7 +264,7 @@ describe("recursive org execution", () => { it("manager cannot route a hire outside their reporting branch", async () => { const harness = await createHarness(); try { - const cmoId = harness.db.agents.saveAgent({ + const cmoId = harness.db.core.agents.saveAgent({ companyId: harness.companyId, name: "CMO", role: "CMO", diff --git a/tests/integration/api-run-events.spec.ts b/tests/integration/api-run-events.spec.ts index 83d01e47..c9f6b925 100644 --- a/tests/integration/api-run-events.spec.ts +++ b/tests/integration/api-run-events.spec.ts @@ -25,8 +25,8 @@ async function createHarness() { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ name: "Orbit Foundry", description: "API test", status: "active" }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const companyId = db.core.company.saveCompany({ name: "Orbit Foundry", description: "API test", status: "active" }); + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -34,7 +34,7 @@ async function createHarness() { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Helios", role: "CEO", @@ -47,7 +47,7 @@ async function createHarness() { capabilities: "Operate the company", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Ship the API", description: "Capture structured run events.", @@ -56,7 +56,7 @@ async function createHarness() { priority: "high", status: "todo", }); - const run = db.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" }); + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" }); const token = createAgentToken(agentId, companyId, run.id); const server = createApiServer(db, createTestApiServerCallbacks()); const port = await server.start(); @@ -96,7 +96,7 @@ describe("run event API", () => { it("returns persisted run events for the active run token", async () => { const harness = await createHarness(); try { - harness.db.runs.appendRunEvent({ + harness.db.core.runs.appendRunEvent({ companyId: harness.companyId, runId: harness.runId, agentId: harness.agentId, @@ -142,7 +142,7 @@ describe("run event API", () => { expect(reader).toBeDefined(); setTimeout(() => { - harness.db.runs.appendRunEvent({ + harness.db.core.runs.appendRunEvent({ companyId: harness.companyId, runId: harness.runId, agentId: harness.agentId, diff --git a/tests/integration/autonomous-chain/approval-decisions.spec.ts b/tests/integration/autonomous-chain/approval-decisions.spec.ts index 56cd2c7c..c260ae0e 100644 --- a/tests/integration/autonomous-chain/approval-decisions.spec.ts +++ b/tests/integration/autonomous-chain/approval-decisions.spec.ts @@ -14,7 +14,7 @@ describe("Step 4: Hire approval + decideApproval", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" }); - const approvalId = shared.db.approvalsMod.requestApproval({ + const approvalId = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "hire_agent", @@ -23,13 +23,13 @@ describe("Step 4: Hire approval + decideApproval", () => { payloadJson: JSON.stringify({ agentId: hireId }), }); - shared.db.approvalsMod.decideApproval({ + shared.db.core.approvals.decideApproval({ approvalId, state: "approved", decisionNote: "Approved by board", }); - const updatedAgent = shared.db.entityCrud.getAgent(hireId); + const updatedAgent = shared.db.core.entity.getAgent(hireId); expect(updatedAgent.status).toBe("idle"); }); @@ -37,7 +37,7 @@ describe("Step 4: Hire approval + decideApproval", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" }); - const approvalId = shared.db.approvalsMod.requestApproval({ + const approvalId = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "hire_agent", @@ -46,20 +46,20 @@ describe("Step 4: Hire approval + decideApproval", () => { payloadJson: JSON.stringify({ agentId: hireId }), }); - shared.db.approvalsMod.decideApproval({ + shared.db.core.approvals.decideApproval({ approvalId, state: "rejected", decisionNote: "Not needed", }); - const updatedAgent = shared.db.entityCrud.getAgent(hireId); + const updatedAgent = shared.db.core.entity.getAgent(hireId); expect(updatedAgent.status).toBe("terminated"); }); it("getApprovalRecord includes payloadJson", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" }); - const approvalId = shared.db.approvalsMod.requestApproval({ + const approvalId = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "hire_agent", @@ -68,7 +68,7 @@ describe("Step 4: Hire approval + decideApproval", () => { payloadJson: JSON.stringify({ agentId: hireId, config: { connector: "openai-codex" } }), }); - const approval = shared.db.approvalsMod.getApprovalRecord(approvalId); + const approval = shared.db.core.approvals.getApprovalRecord(approvalId); expect(approval.payloadJson).toBeTruthy(); const payload = JSON.parse(approval.payloadJson!); expect(payload.agentId).toBe(hireId); diff --git a/tests/integration/autonomous-chain/automation-messages.spec.ts b/tests/integration/autonomous-chain/automation-messages.spec.ts index 1e4ad7a3..d961d72c 100644 --- a/tests/integration/autonomous-chain/automation-messages.spec.ts +++ b/tests/integration/autonomous-chain/automation-messages.spec.ts @@ -11,7 +11,7 @@ const { createAgent } = shared; describe("Automation Rules CRUD", () => { it("creates, reads, toggles, and deletes an automation rule", () => { - const ruleId = shared.db.automationMod.saveAutomationRule({ + const ruleId = shared.db.automation.rules.saveAutomationRule({ companyId: shared.companyId, name: "Auto-escalate blocked tasks", description: "When a task is blocked, notify the manager", @@ -28,7 +28,7 @@ describe("Automation Rules CRUD", () => { expect(ruleId).toBeTruthy(); // Rule appears in snapshot - const snapshot = shared.db.snap.listSnapshot(); + const snapshot = shared.db.projection.snapshot.listSnapshot(); const rule = snapshot.automationRules.find(r => r.id === ruleId); expect(rule).toBeTruthy(); expect(rule!.name).toBe("Auto-escalate blocked tasks"); @@ -39,36 +39,36 @@ describe("Automation Rules CRUD", () => { expect(rule!.executionCount).toBe(0); // Toggle to paused - shared.db.automationMod.toggleAutomationRule(ruleId, shared.companyId, "paused"); - const updated = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId); + shared.db.automation.rules.toggleAutomationRule(ruleId, shared.companyId, "paused"); + const updated = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId); expect(updated!.status).toBe("paused"); // Record execution - shared.db.automationMod.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123"); - const afterExec = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId); + shared.db.automation.rules.recordAutomationExecution(ruleId, shared.companyId, "Auto-escalate blocked tasks", "task_blocked", "escalate_to_manager", "Escalated task-123"); + const afterExec = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId); expect(afterExec!.executionCount).toBe(1); expect(afterExec!.lastExecutedAt).toBeTruthy(); // Automation log - const log = shared.db.automationMod.getAutomationLog(shared.companyId); + const log = shared.db.automation.rules.getAutomationLog(shared.companyId); expect(log).toHaveLength(1); expect(log[0].ruleName).toBe("Auto-escalate blocked tasks"); // Delete - shared.db.automationMod.deleteAutomationRule(ruleId, shared.companyId); - const afterDelete = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId); + shared.db.automation.rules.deleteAutomationRule(ruleId, shared.companyId); + const afterDelete = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId); expect(afterDelete).toBeUndefined(); }); it("updates an existing rule", () => { - const ruleId = shared.db.automationMod.saveAutomationRule({ + const ruleId = shared.db.automation.rules.saveAutomationRule({ companyId: shared.companyId, name: "Original name", trigger: "task_created", action: "assign_task", }); - shared.db.automationMod.saveAutomationRule({ + shared.db.automation.rules.saveAutomationRule({ id: ruleId, companyId: shared.companyId, name: "Updated name", @@ -77,7 +77,7 @@ describe("Automation Rules CRUD", () => { priority: 90, }); - const rule = shared.db.snap.listSnapshot().automationRules.find(r => r.id === ruleId); + const rule = shared.db.projection.snapshot.listSnapshot().automationRules.find(r => r.id === ruleId); expect(rule!.name).toBe("Updated name"); expect(rule!.action).toBe("notify_agent"); expect(rule!.priority).toBe(90); @@ -89,13 +89,13 @@ describe("Agent Messages", () => { let agentBId: string; beforeEach(() => { - agentAId = shared.db.agents.saveAgent({ + agentAId = shared.db.core.agents.saveAgent({ companyId: shared.companyId, name: "Alice", role: "Engineer", title: "Senior Engineer", department: "engineering", connectorId: "claude-pro", status: "active", capabilities: "Backend development, API design, code review", budgetMonthlyUsd: 100, }); - agentBId = shared.db.agents.saveAgent({ + agentBId = shared.db.core.agents.saveAgent({ companyId: shared.companyId, name: "Bob", role: "Designer", title: "UX Designer", department: "design", connectorId: "claude-pro", status: "active", capabilities: "UI design, wireframing, user research", @@ -104,7 +104,7 @@ describe("Agent Messages", () => { }); it("sends and retrieves direct messages", () => { - const msgId = shared.db.messages.sendAgentMessage({ + const msgId = shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, @@ -116,7 +116,7 @@ describe("Agent Messages", () => { expect(msgId).toBeTruthy(); - const messages = shared.db.messages.listAgentMessages(shared.companyId); + const messages = shared.db.core.messages.listAgentMessages(shared.companyId); expect(messages).toHaveLength(1); expect(messages[0].subject).toBe("Need design review"); expect(messages[0].fromAgentId).toBe(agentAId); @@ -125,7 +125,7 @@ describe("Agent Messages", () => { }); it("marks messages as read", () => { - const msgId = shared.db.messages.sendAgentMessage({ + const msgId = shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, @@ -133,14 +133,14 @@ describe("Agent Messages", () => { body: "Hello", }); - shared.db.messages.markMessageReadForReader(msgId, shared.companyId); - const messages = shared.db.messages.listAgentMessages(shared.companyId); + shared.db.core.messages.markMessageReadForReader(msgId, shared.companyId); + const messages = shared.db.core.messages.listAgentMessages(shared.companyId); const updatedMessage = messages.find((message) => message.id === msgId); expect(updatedMessage?.readAt).toBeTruthy(); }); it("tracks read state per agent without marking the sender unread", () => { - const msgId = shared.db.messages.sendAgentMessage({ + const msgId = shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, @@ -148,22 +148,22 @@ describe("Agent Messages", () => { body: "Hello", }); - const senderView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentAId }); + const senderView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentAId }); expect(senderView).toHaveLength(1); expect(senderView[0].id).toBe(msgId); expect(senderView[0].readAt).toBeTruthy(); - const recipientViewBefore = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true }); + const recipientViewBefore = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true }); expect(recipientViewBefore).toHaveLength(1); expect(recipientViewBefore[0].id).toBe(msgId); - shared.db.messages.markMessageReadForReader(msgId, shared.companyId, agentBId); + shared.db.core.messages.markMessageReadForReader(msgId, shared.companyId, agentBId); - const recipientViewAfter = shared.db.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true }); + const recipientViewAfter = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: agentBId, unreadOnly: true }); expect(recipientViewAfter).toHaveLength(0); const thirdAgentId = createAgent({ name: "Agent C" }); - const thirdView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: thirdAgentId }); + const thirdView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: thirdAgentId }); expect(thirdView).toHaveLength(0); }); @@ -171,14 +171,14 @@ describe("Agent Messages", () => { const engineeringLeadId = createAgent({ name: "Engineering Lead", role: "Lead", department: "engineering" }); const productLeadId = createAgent({ name: "Product Lead", role: "Lead", department: "product" }); - const projectId = shared.db.entityCrud.saveProject({ + const projectId = shared.db.core.entity.saveProject({ companyId: shared.companyId, name: "Cross-team Project", description: "Scope", leadAgentId: engineeringLeadId, status: "active", }); - shared.db.tasks.saveTask({ + shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Project task", description: "Do work", @@ -188,7 +188,7 @@ describe("Agent Messages", () => { priority: "medium", }); - const deptMessageId = shared.db.messages.sendAgentMessage({ + const deptMessageId = shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: engineeringLeadId, channel: "department", @@ -196,7 +196,7 @@ describe("Agent Messages", () => { subject: "Engineering update", body: "Only engineering should see this", }); - const projectMessageId = shared.db.messages.sendAgentMessage({ + const projectMessageId = shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: engineeringLeadId, channel: "project", @@ -205,31 +205,31 @@ describe("Agent Messages", () => { body: "Only project members should see this", }); - const engineeringView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: engineeringLeadId }); + const engineeringView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: engineeringLeadId }); expect(engineeringView.some((message) => message.id === deptMessageId)).toBe(true); expect(engineeringView.some((message) => message.id === projectMessageId)).toBe(true); - const productView = shared.db.messages.listAgentMessages(shared.companyId, { agentId: productLeadId }); + const productView = shared.db.core.messages.listAgentMessages(shared.companyId, { agentId: productLeadId }); expect(productView.some((message) => message.id === deptMessageId)).toBe(false); expect(productView.some((message) => message.id === projectMessageId)).toBe(false); }); it("filters messages by channel", () => { - shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, subject: "Dept update", body: "All good", channel: "department", channelTargetId: "engineering" }); - shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, subject: "DM", body: "Hey", channel: "direct" }); + shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, subject: "Dept update", body: "All good", channel: "department", channelTargetId: "engineering" }); + shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, toAgentId: agentBId, subject: "DM", body: "Hey", channel: "direct" }); - const deptMsgs = shared.db.messages.listAgentMessages(shared.companyId, { channel: "department" }); + const deptMsgs = shared.db.core.messages.listAgentMessages(shared.companyId, { channel: "department" }); expect(deptMsgs).toHaveLength(1); expect(deptMsgs[0].subject).toBe("Dept update"); - const directMsgs = shared.db.messages.listAgentMessages(shared.companyId, { channel: "direct" }); + const directMsgs = shared.db.core.messages.listAgentMessages(shared.companyId, { channel: "direct" }); expect(directMsgs).toHaveLength(1); expect(directMsgs[0].subject).toBe("DM"); }); it("appears in snapshot", () => { - shared.db.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, channel: "company", subject: "Snapshot test", body: "Hi" }); - const snapshot = shared.db.snap.listSnapshot(); + shared.db.core.messages.sendAgentMessage({ companyId: shared.companyId, fromAgentId: agentAId, channel: "company", subject: "Snapshot test", body: "Hi" }); + const snapshot = shared.db.projection.snapshot.listSnapshot(); expect(snapshot.agentMessages.length).toBeGreaterThanOrEqual(1); expect(snapshot.agentMessages[0].subject).toBe("Snapshot test"); }); @@ -240,13 +240,13 @@ describe("Auto Task Assignment", () => { let frontendId: string; beforeEach(() => { - backendId = shared.db.agents.saveAgent({ + backendId = shared.db.core.agents.saveAgent({ companyId: shared.companyId, name: "Backend Dev", role: "Backend Engineer", title: "Senior Backend", department: "engineering", connectorId: "openai-codex", status: "active", capabilities: "API development, database design, Node.js, PostgreSQL, backend testing", budgetMonthlyUsd: 200, }); - frontendId = shared.db.agents.saveAgent({ + frontendId = shared.db.core.agents.saveAgent({ companyId: shared.companyId, name: "Frontend Dev", role: "Frontend Engineer", title: "Senior Frontend", department: "engineering", connectorId: "openai-codex", status: "active", capabilities: "React, TypeScript, CSS, Tailwind, responsive design, frontend testing, UI components", @@ -255,68 +255,68 @@ describe("Auto Task Assignment", () => { }); it("assigns to the best-matching agent by capability keywords", () => { - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Build React component for user dashboard", description: "Create a responsive React component with Tailwind CSS styling", priority: "medium", status: "todo", }); - const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId); + const assignedId = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId); expect(assignedId).toBe(frontendId); // "React", "responsive", "Tailwind" match frontend }); it("assigns backend task to backend agent", () => { - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Design database schema for orders", description: "PostgreSQL database design with API endpoints for order management", priority: "high", status: "todo", }); - const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId); + const assignedId = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId); expect(assignedId).toBe(backendId); // "database", "PostgreSQL", "API" match backend }); it("returns existing assignee if task already assigned", () => { - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Already assigned task", description: "This task has an assignee", assigneeAgentId: frontendId, priority: "medium", status: "todo", }); - const result = shared.db.tasks.autoAssignTask(taskId, shared.companyId); + const result = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId); expect(result).toBe(frontendId); }); it("returns null if all agents have exhausted their budget", () => { // Set positive budgets that are fully spent - shared.db.agents.saveAgent({ id: backendId, companyId: shared.companyId, name: "Backend Dev", role: "Backend", title: "Backend", connectorId: "openai-codex", status: "active", capabilities: "backend", budgetMonthlyUsd: 100 }); - shared.db.agents.saveAgent({ id: frontendId, companyId: shared.companyId, name: "Frontend Dev", role: "Frontend", title: "Frontend", connectorId: "openai-codex", status: "active", capabilities: "frontend", budgetMonthlyUsd: 100 }); + shared.db.core.agents.saveAgent({ id: backendId, companyId: shared.companyId, name: "Backend Dev", role: "Backend", title: "Backend", connectorId: "openai-codex", status: "active", capabilities: "backend", budgetMonthlyUsd: 100 }); + shared.db.core.agents.saveAgent({ id: frontendId, companyId: shared.companyId, name: "Frontend Dev", role: "Frontend", title: "Frontend", connectorId: "openai-codex", status: "active", capabilities: "frontend", budgetMonthlyUsd: 100 }); // Exhaust budgets by recording spending via direct SQL (since finishRun would need a real run) (shared.db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db.prepare("update agents set spent_monthly_usd = 100 where id = ?").run(backendId); (shared.db as unknown as { db: { prepare: (sql: string) => { run: (...args: unknown[]) => void } } }).db.prepare("update agents set spent_monthly_usd = 100 where id = ?").run(frontendId); - const taskId = shared.db.tasks.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" }); - const result = shared.db.tasks.autoAssignTask(taskId, shared.companyId); + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "No budget task", description: "Test", priority: "low", status: "todo" }); + const result = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId); expect(result).toBeNull(); }); it("penalizes agents with many active tasks", () => { // Give backend agent 5 active tasks for (let i = 0; i < 5; i++) { - shared.db.tasks.saveTask({ companyId: shared.companyId, title: `Backend task ${i}`, description: "", assigneeAgentId: backendId, priority: "medium", status: "in_progress" }); + shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: `Backend task ${i}`, description: "", assigneeAgentId: backendId, priority: "medium", status: "in_progress" }); } // Generic task with no strong keyword match — should go to less busy frontend agent - const taskId = shared.db.tasks.saveTask({ companyId: shared.companyId, title: "Implement feature", description: "Build the feature", priority: "medium", status: "todo" }); - const assignedId = shared.db.tasks.autoAssignTask(taskId, shared.companyId); + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Implement feature", description: "Build the feature", priority: "medium", status: "todo" }); + const assignedId = shared.db.core.tasks.autoAssignTask(taskId, shared.companyId); expect(assignedId).toBe(frontendId); // Frontend has 0 active tasks vs backend's 5 }); }); describe("Workflow Pipelines", () => { it("creates, activates, and deletes a workflow", () => { - const wfId = shared.db.automationMod.saveWorkflow({ + const wfId = shared.db.automation.rules.saveWorkflow({ companyId: shared.companyId, name: "Code Review Pipeline", description: "Auto-review flow for PRs", @@ -330,14 +330,14 @@ describe("Workflow Pipelines", () => { }); expect(wfId).toBeTruthy(); - const snapshot = shared.db.snap.listSnapshot(); + const snapshot = shared.db.projection.snapshot.listSnapshot(); const wf = snapshot.workflows.find(w => w.id === wfId); expect(wf!.name).toBe("Code Review Pipeline"); expect(wf!.status).toBe("draft"); expect(wf!.runCount).toBe(0); // Delete - shared.db.automationMod.deleteWorkflow(wfId, shared.companyId); - expect(shared.db.snap.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined(); + shared.db.automation.rules.deleteWorkflow(wfId, shared.companyId); + expect(shared.db.projection.snapshot.listSnapshot().workflows.find(w => w.id === wfId)).toBeUndefined(); }); }); diff --git a/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts b/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts index c721c0ac..e7eca309 100644 --- a/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts +++ b/tests/integration/autonomous-chain/ceo-phased-prompts.spec.ts @@ -36,7 +36,7 @@ describe("Step 2: CEO phased prompts", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" }); - const hire = shared.db.entityCrud.getAgent(hireId); + const hire = shared.db.core.entity.getAgent(hireId); const ctx = buildCtx({ agentId: ceoId, @@ -86,7 +86,7 @@ describe("Step 2: CEO phased prompts", () => { it("Phase 2: shows normal executive instructions when active reports and no pending org approval", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" }); - const cto = shared.db.entityCrud.getAgent(ctoId); + const cto = shared.db.core.entity.getAgent(ctoId); const ctx = buildCtx({ agentId: ceoId, @@ -107,7 +107,7 @@ describe("Step 2: CEO phased prompts", () => { it("includes pending approvals section in prompt", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" }); - const cto = shared.db.entityCrud.getAgent(ctoId); + const cto = shared.db.core.entity.getAgent(ctoId); const ctx = buildCtx({ agentId: ceoId, @@ -137,10 +137,10 @@ describe("Step 2: CEO phased prompts", () => { it("Manager prompt includes credential escalation guidance", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); - const ceo = shared.db.entityCrud.getAgent(ceoId); + const ceo = shared.db.core.entity.getAgent(ceoId); const managerId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: managerId }); - const ic = shared.db.entityCrud.getAgent(icId); + const ic = shared.db.core.entity.getAgent(icId); const ctx = buildCtx({ agentId: managerId, @@ -154,7 +154,7 @@ describe("Step 2: CEO phased prompts", () => { it("IC prompt includes credential escalation guidance", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); - const ceo = shared.db.entityCrud.getAgent(ceoId); + const ceo = shared.db.core.entity.getAgent(ceoId); const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId }); const ctx = buildCtx({ diff --git a/tests/integration/autonomous-chain/crash-recovery.spec.ts b/tests/integration/autonomous-chain/crash-recovery.spec.ts index 008c6926..e85cdd18 100644 --- a/tests/integration/autonomous-chain/crash-recovery.spec.ts +++ b/tests/integration/autonomous-chain/crash-recovery.spec.ts @@ -13,7 +13,7 @@ const { createAgent } = shared; describe("Step 7: Worker crash recovery (database layer)", () => { it("interruptStaleRuns clears running and queued runs on init", async () => { const agentId = createAgent({ name: "Agent" }); - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "In-progress task", description: "Running when crash happened", @@ -23,7 +23,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => { status: "in_progress", }); - const run = shared.db.runs.createRun({ + const run = shared.db.core.runs.createRun({ companyId: shared.companyId, taskId, agentId, @@ -31,10 +31,10 @@ describe("Step 7: Worker crash recovery (database layer)", () => { connectorId: "openai-codex", }); - shared.db.runs.markRunStatus(run.id, "running", "Started"); + shared.db.core.runs.markRunStatus(run.id, "running", "Started"); // Verify it's running - let snapshot = shared.db.snap.listSnapshot(); + let snapshot = shared.db.projection.snapshot.listSnapshot(); expect(snapshot.runs.find((r) => r.id === run.id)?.status).toBe("running"); // Simulate app restart — close and re-init @@ -44,14 +44,14 @@ describe("Step 7: Worker crash recovery (database layer)", () => { shared.db = new AppDatabase(profileDir); shared.db.init(); // This calls interruptStaleRuns() - snapshot = shared.db.snap.listSnapshot(); + snapshot = shared.db.projection.snapshot.listSnapshot(); const recoveredRun = snapshot.runs.find((r) => r.id === run.id); expect(recoveredRun?.status).toBe("interrupted"); }); it("finishRun can mark interrupted runs from crash recovery", () => { const agentId = createAgent({ name: "Agent" }); - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Crash recovery task", description: "Test", @@ -61,7 +61,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => { status: "in_progress", }); - const run = shared.db.runs.createRun({ + const run = shared.db.core.runs.createRun({ companyId: shared.companyId, taskId, agentId, @@ -69,10 +69,10 @@ describe("Step 7: Worker crash recovery (database layer)", () => { connectorId: "openai-codex", }); - shared.db.runs.markRunStatus(run.id, "running", "Started"); + shared.db.core.runs.markRunStatus(run.id, "running", "Started"); // Simulate crash recovery - mark as interrupted - shared.db.runs.finishRun({ + shared.db.core.runs.finishRun({ runId: run.id, status: "interrupted", summary: "Worker process crashed.", @@ -83,7 +83,7 @@ describe("Step 7: Worker crash recovery (database layer)", () => { unattributedCost: true, }); - const snapshot = shared.db.snap.listSnapshot(); + const snapshot = shared.db.projection.snapshot.listSnapshot(); const recovered = snapshot.runs.find((r) => r.id === run.id); expect(recovered?.status).toBe("interrupted"); expect(recovered?.errorMessage).toBe("Worker process exited unexpectedly."); diff --git a/tests/integration/autonomous-chain/e2e-flows.spec.ts b/tests/integration/autonomous-chain/e2e-flows.spec.ts index 163e2c7f..0d65316e 100644 --- a/tests/integration/autonomous-chain/e2e-flows.spec.ts +++ b/tests/integration/autonomous-chain/e2e-flows.spec.ts @@ -31,7 +31,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => { const cmoId = createAgent({ name: "Aria", role: "CMO", reportsTo: ceoId, status: "pending_approval" as "idle" }); // 3. CEO submits org strategy approval - const orgApprovalId = shared.db.approvalsMod.requestApproval({ + const orgApprovalId = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "approve_ceo_strategy", @@ -40,8 +40,8 @@ describe("E2E: Full autonomous CEO cold start flow", () => { }); // CEO now in Phase 1 - const ctoRecord = shared.db.entityCrud.getAgent(ctoId); - const cmoRecord = shared.db.entityCrud.getAgent(cmoId); + const ctoRecord = shared.db.core.entity.getAgent(ctoId); + const cmoRecord = shared.db.core.entity.getAgent(cmoId); const phase1Ctx = buildCtx({ agentId: ceoId, directReports: [ctoRecord, cmoRecord], @@ -64,7 +64,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => { expect(phase1Prompt).toContain("Awaiting Org Strategy Approval"); // 4. Board approves hires - const hireApprovalCTO = shared.db.approvalsMod.requestApproval({ + const hireApprovalCTO = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "hire_agent", @@ -72,9 +72,9 @@ describe("E2E: Full autonomous CEO cold start flow", () => { impactSummary: "Test", payloadJson: JSON.stringify({ agentId: ctoId }), }); - shared.db.approvalsMod.decideApproval({ approvalId: hireApprovalCTO, state: "approved", decisionNote: "Approved" }); + shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCTO, state: "approved", decisionNote: "Approved" }); - const hireApprovalCMO = shared.db.approvalsMod.requestApproval({ + const hireApprovalCMO = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "hire_agent", @@ -82,18 +82,18 @@ describe("E2E: Full autonomous CEO cold start flow", () => { impactSummary: "Test", payloadJson: JSON.stringify({ agentId: cmoId }), }); - shared.db.approvalsMod.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" }); + shared.db.core.approvals.decideApproval({ approvalId: hireApprovalCMO, state: "approved", decisionNote: "Approved" }); // Agents now idle - expect(shared.db.entityCrud.getAgent(ctoId).status).toBe("idle"); - expect(shared.db.entityCrud.getAgent(cmoId).status).toBe("idle"); + expect(shared.db.core.entity.getAgent(ctoId).status).toBe("idle"); + expect(shared.db.core.entity.getAgent(cmoId).status).toBe("idle"); // 5. Board approves org strategy - shared.db.approvalsMod.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" }); + shared.db.core.approvals.decideApproval({ approvalId: orgApprovalId, state: "approved", decisionNote: "Go ahead" }); // 6. CEO now in Phase 2 - const updatedCto = shared.db.entityCrud.getAgent(ctoId); - const updatedCmo = shared.db.entityCrud.getAgent(cmoId); + const updatedCto = shared.db.core.entity.getAgent(ctoId); + const updatedCmo = shared.db.core.entity.getAgent(cmoId); const phase2Ctx = buildCtx({ agentId: ceoId, directReports: [updatedCto, updatedCmo], @@ -106,12 +106,12 @@ describe("E2E: Full autonomous CEO cold start flow", () => { expect(phase2Prompt).toContain("Delegation"); // 7. Verify hierarchical chain - const ctoChain = shared.db.agents.getChainOfCommand(ctoId); + const ctoChain = shared.db.core.agents.getChainOfCommand(ctoId); expect(ctoChain).toHaveLength(1); expect(ctoChain[0].id).toBe(ceoId); // 8. Create task tree - const projectId = shared.db.entityCrud.saveProject({ + const projectId = shared.db.core.entity.saveProject({ companyId: shared.companyId, name: "Launch MVP", description: "Build and launch MVP", @@ -120,7 +120,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => { status: "in_progress", }); - const parentTaskId = shared.db.tasks.saveTask({ + const parentTaskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Build backend", description: "Implement API", @@ -133,7 +133,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => { // CTO creates subtask for IC (would be done via API) const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId, status: "idle" }); - const subtaskId = shared.db.tasks.saveTask({ + const subtaskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Implement auth", description: "JWT auth", @@ -146,12 +146,12 @@ describe("E2E: Full autonomous CEO cold start flow", () => { }); // Verify parent-child relationship - const subtask = shared.db.entityCrud.getTask(subtaskId); + const subtask = shared.db.core.entity.getTask(subtaskId); expect(subtask.parentId).toBe(parentTaskId); // 9. Wakeup request test: enqueue wake while IC is busy - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment"); - const queuedWakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" }); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, icId, "comment", "comment"); + const queuedWakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: icId, status: "queued" }); expect(queuedWakes).toHaveLength(1); expect(queuedWakes[0].source).toBe("comment"); @@ -159,7 +159,7 @@ describe("E2E: Full autonomous CEO cold start flow", () => { const icPromptCtx = buildCtx({ agentId: icId, task: subtask, - chainOfCommand: [shared.db.entityCrud.getAgent(ctoId), shared.db.entityCrud.getAgent(ceoId)], + chainOfCommand: [shared.db.core.entity.getAgent(ctoId), shared.db.core.entity.getAgent(ceoId)], wakeReason: "assignment", }); const icPrompt = buildTaskPrompt(icPromptCtx); @@ -175,12 +175,12 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => { // Simulate: agent is running, wakes come in. All triggers are coalesced // into a single wakeup request with JSON array of trigger details. - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed"); - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment"); - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "report_blocked", "report_blocked"); // Verify coalesced into single row with all triggers - const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); + const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(1); expect(wakes[0].coalescedCount).toBe(2); @@ -191,7 +191,7 @@ describe("E2E: Wakeup request lifecycle with run lifecycle", () => { expect(triggers[2].trigger).toBe("report_blocked"); // Wakes remain queued until consumed by the run lifecycle - expect(shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1); + expect(shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" })).toHaveLength(1); }); }); @@ -202,11 +202,11 @@ describe("E2E: Blocked escalation through org hierarchy", () => { const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId }); // When IC marks task blocked, we need to find its manager - const chain = shared.db.agents.getChainOfCommand(icId); + const chain = shared.db.core.agents.getChainOfCommand(icId); expect(chain[0].id).toBe(ctoId); // immediate manager gets woken // When CTO needs to escalate further - const ctoChain = shared.db.agents.getChainOfCommand(ctoId); + const ctoChain = shared.db.core.agents.getChainOfCommand(ctoId); expect(ctoChain[0].id).toBe(ceoId); }); }); @@ -216,7 +216,7 @@ describe("Review Fix: CEO phase ordering edge case", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); // Hires submitted but not yet approved const hireId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "pending_approval" as "idle" }); - const hire = shared.db.entityCrud.getAgent(hireId); + const hire = shared.db.core.entity.getAgent(hireId); // Org strategy already approved (not in pendingApprovals), but hires still pending const ctx = buildCtx({ @@ -236,7 +236,7 @@ describe("Review Fix: CEO phase ordering edge case", () => { it("moves to Phase 2 once hires are approved (no pending org approval)", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId, status: "idle" }); - const cto = shared.db.entityCrud.getAgent(ctoId); + const cto = shared.db.core.entity.getAgent(ctoId); const ctx = buildCtx({ agentId: ceoId, @@ -287,7 +287,7 @@ describe("Review Fix: Rejected approval wakes agent", () => { it("decideApproval with rejected state still triggers wake (tested via DB)", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); - const approvalId = shared.db.approvalsMod.requestApproval({ + const approvalId = shared.db.core.approvals.requestApproval({ companyId: shared.companyId, requestedByAgentId: ceoId, type: "approve_ceo_strategy", @@ -295,14 +295,14 @@ describe("Review Fix: Rejected approval wakes agent", () => { impactSummary: "test", }); - shared.db.approvalsMod.decideApproval({ + shared.db.core.approvals.decideApproval({ approvalId, state: "rejected", decisionNote: "Not ready", }); // Verify approval state - const approval = shared.db.approvalsMod.getApprovalRecord(approvalId); + const approval = shared.db.core.approvals.getApprovalRecord(approvalId); expect(approval.state).toBe("rejected"); // The IPC handler in index.ts now wakes on rejected too — // we verify the DB layer works; the wakeAgentIfPossible call is in index.ts @@ -315,7 +315,7 @@ describe("Review Fix: Root task completion notification", () => { const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); // CTO has a root-level task (no parentId) - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Standalone task from CEO", description: "No parent", @@ -325,11 +325,11 @@ describe("Review Fix: Root task completion notification", () => { status: "done", }); - const task = shared.db.entityCrud.getTask(taskId); + const task = shared.db.core.entity.getTask(taskId); expect(task.parentId).toBeNull(); // CTO's chain of command includes CEO — so api-server can wake CEO on completion - const chain = shared.db.agents.getChainOfCommand(ctoId); + const chain = shared.db.core.agents.getChainOfCommand(ctoId); expect(chain).toHaveLength(1); expect(chain[0].id).toBe(ceoId); }); diff --git a/tests/integration/autonomous-chain/role-detection.spec.ts b/tests/integration/autonomous-chain/role-detection.spec.ts index 96284223..a672c220 100644 --- a/tests/integration/autonomous-chain/role-detection.spec.ts +++ b/tests/integration/autonomous-chain/role-detection.spec.ts @@ -31,9 +31,9 @@ describe("Step 1: CEO role detection", () => { it("identifies manager when has reports and has chain of command", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const managerId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); - const ceo = shared.db.entityCrud.getAgent(ceoId); + const ceo = shared.db.core.entity.getAgent(ceoId); const icId = createAgent({ name: "IC", role: "Engineer", reportsTo: managerId }); - const ic = shared.db.entityCrud.getAgent(icId); + const ic = shared.db.core.entity.getAgent(icId); const ctx = buildCtx({ agentId: managerId, @@ -49,7 +49,7 @@ describe("Step 1: CEO role detection", () => { it("identifies IC when has chain of command and no direct reports", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); - const ceo = shared.db.entityCrud.getAgent(ceoId); + const ceo = shared.db.core.entity.getAgent(ceoId); const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ceoId }); const ctx = buildCtx({ diff --git a/tests/integration/autonomous-chain/run-failure.spec.ts b/tests/integration/autonomous-chain/run-failure.spec.ts index 5b4e83d2..da6884f9 100644 --- a/tests/integration/autonomous-chain/run-failure.spec.ts +++ b/tests/integration/autonomous-chain/run-failure.spec.ts @@ -12,7 +12,7 @@ const { createAgent } = shared; describe("Step 6: Run failure escalation (database layer)", () => { it("finishRun marks run as failed and returns outcome", () => { const agentId = createAgent({ name: "IC", role: "Engineer" }); - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Failing task", description: "Will fail", @@ -22,7 +22,7 @@ describe("Step 6: Run failure escalation (database layer)", () => { status: "in_progress", }); - const run = shared.db.runs.createRun({ + const run = shared.db.core.runs.createRun({ companyId: shared.companyId, taskId, agentId, @@ -30,9 +30,9 @@ describe("Step 6: Run failure escalation (database layer)", () => { connectorId: "openai-codex", }); - shared.db.runs.markRunStatus(run.id, "running", "Started"); + shared.db.core.runs.markRunStatus(run.id, "running", "Started"); - const outcome = shared.db.runs.finishRun({ + const outcome = shared.db.core.runs.finishRun({ runId: run.id, status: "failed", summary: null, @@ -45,7 +45,7 @@ describe("Step 6: Run failure escalation (database layer)", () => { expect(outcome.budgetHardStopped).toBe(false); - const snapshot = shared.db.snap.listSnapshot(); + const snapshot = shared.db.projection.snapshot.listSnapshot(); const finishedRun = snapshot.runs.find((r) => r.id === run.id); expect(finishedRun?.status).toBe("failed"); expect(finishedRun?.errorMessage).toBe("Process crashed"); @@ -56,7 +56,7 @@ describe("Step 6: Run failure escalation (database layer)", () => { const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId }); - const chain = shared.db.agents.getChainOfCommand(icId); + const chain = shared.db.core.agents.getChainOfCommand(icId); // First in chain is the immediate manager (CTO) expect(chain.length).toBeGreaterThan(0); expect(chain[0].name).toBe("CTO"); diff --git a/tests/integration/autonomous-chain/shared-setup.ts b/tests/integration/autonomous-chain/shared-setup.ts index 52bd26aa..7fd3e733 100644 --- a/tests/integration/autonomous-chain/shared-setup.ts +++ b/tests/integration/autonomous-chain/shared-setup.ts @@ -30,7 +30,7 @@ export interface SharedHandle { set db(value: AppDatabase); get companyId(): string; get workspaceId(): string; - createAgent(overrides?: Partial[0]>): string; + createAgent(overrides?: Partial[0]>): string; buildCtx(overrides?: Record): Parameters[0]; tempDirs: string[]; } @@ -54,13 +54,13 @@ export function installSharedSetup(): SharedHandle { state.db = new AppDatabase(profileDir); state.db.init(); - state.companyId = state.db.company.saveCompany({ + state.companyId = state.db.core.company.saveCompany({ name: "Autonomous Test Corp", description: "Test company for autonomous chain", status: "active", }); - state.workspaceId = state.db.workspacesMod.saveWorkspace({ + state.workspaceId = state.db.core.workspaces.saveWorkspace({ companyId: state.companyId, name: "Primary", localPath: profileDir, @@ -80,8 +80,8 @@ export function installSharedSetup(): SharedHandle { return state.db; } - function createAgent(overrides: Partial[0]> = {}): string { - return currentDb().agents.saveAgent({ + function createAgent(overrides: Partial[0]> = {}): string { + return currentDb().core.agents.saveAgent({ companyId: state.companyId, name: "TestAgent", role: "CEO", @@ -98,7 +98,7 @@ export function installSharedSetup(): SharedHandle { } function buildCtx(overrides: Record = {}): Parameters[0] { - const agent = currentDb().entityCrud.getAgent(overrides.agentId as string ?? createAgent()); + const agent = currentDb().core.entity.getAgent(overrides.agentId as string ?? createAgent()); return { agent, apiUrl: "http://127.0.0.1:3000", diff --git a/tests/integration/autonomous-chain/task-propagation.spec.ts b/tests/integration/autonomous-chain/task-propagation.spec.ts index 2bdf625f..3e56802f 100644 --- a/tests/integration/autonomous-chain/task-propagation.spec.ts +++ b/tests/integration/autonomous-chain/task-propagation.spec.ts @@ -15,7 +15,7 @@ describe("Step 5: Task status + comment wake propagation", () => { const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); const icId = createAgent({ name: "Dev", role: "Engineer", reportsTo: ctoId }); - const chain = shared.db.agents.getChainOfCommand(icId); + const chain = shared.db.core.agents.getChainOfCommand(icId); expect(chain).toHaveLength(2); expect(chain[0].id).toBe(ctoId); // immediate manager expect(chain[1].id).toBe(ceoId); // top level @@ -25,7 +25,7 @@ describe("Step 5: Task status + comment wake propagation", () => { // Create a potential cycle (shouldn't happen in practice, but guard against it) const agent1 = createAgent({ name: "Agent1" }); // agent1 reports to no one (CEO), so chain should be empty - const chain = shared.db.agents.getChainOfCommand(agent1); + const chain = shared.db.core.agents.getChainOfCommand(agent1); expect(chain).toHaveLength(0); }); @@ -33,7 +33,7 @@ describe("Step 5: Task status + comment wake propagation", () => { const ceoId = createAgent({ name: "CEO", role: "CEO" }); const ctoId = createAgent({ name: "CTO", role: "CTO", reportsTo: ceoId }); - const parentTaskId = shared.db.tasks.saveTask({ + const parentTaskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Build feature X", description: "Top-level task", @@ -43,7 +43,7 @@ describe("Step 5: Task status + comment wake propagation", () => { status: "in_progress", }); - const childTaskId = shared.db.tasks.saveTask({ + const childTaskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Implement module A", description: "Subtask", @@ -54,10 +54,10 @@ describe("Step 5: Task status + comment wake propagation", () => { status: "todo", }); - const childTask = shared.db.entityCrud.getTask(childTaskId); + const childTask = shared.db.core.entity.getTask(childTaskId); expect(childTask.parentId).toBe(parentTaskId); - const parentTask = shared.db.entityCrud.getTask(parentTaskId); + const parentTask = shared.db.core.entity.getTask(parentTaskId); expect(parentTask.assigneeAgentId).toBe(ctoId); }); }); diff --git a/tests/integration/autonomous-chain/wake-reason.spec.ts b/tests/integration/autonomous-chain/wake-reason.spec.ts index 9f27a7aa..21d69180 100644 --- a/tests/integration/autonomous-chain/wake-reason.spec.ts +++ b/tests/integration/autonomous-chain/wake-reason.spec.ts @@ -14,7 +14,7 @@ describe("Step 8: Wake reason injection", () => { it("includes wake reason section for non-timer triggers", () => { const agentId = createAgent({ name: "CTO", role: "CTO" }); const ceoId = createAgent({ name: "CEO", role: "CEO" }); - const ceo = shared.db.entityCrud.getAgent(ceoId); + const ceo = shared.db.core.entity.getAgent(ceoId); const ctx = buildCtx({ agentId, @@ -92,7 +92,7 @@ describe("Step 8: Wake reason injection", () => { it("includes wake reason before task description", () => { const agentId = createAgent({ name: "Agent" }); - const taskId = shared.db.tasks.saveTask({ + const taskId = shared.db.core.tasks.saveTask({ companyId: shared.companyId, title: "Do the thing", description: "Test task", @@ -101,7 +101,7 @@ describe("Step 8: Wake reason injection", () => { priority: "medium", status: "todo", }); - const task = shared.db.entityCrud.getTask(taskId); + const task = shared.db.core.entity.getTask(taskId); const ctx = buildCtx({ agentId, diff --git a/tests/integration/autonomous-chain/wakeup-queue.spec.ts b/tests/integration/autonomous-chain/wakeup-queue.spec.ts index 62a28da8..92e11e01 100644 --- a/tests/integration/autonomous-chain/wakeup-queue.spec.ts +++ b/tests/integration/autonomous-chain/wakeup-queue.spec.ts @@ -15,11 +15,11 @@ describe("Step 3: Wakeup request lifecycle (database)", () => { // Enqueue two wakes for the same busy agent. Coalesced into one row // with trigger_detail JSON array containing both triggers. - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed"); - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "subtask_completed", "subtask_completed"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agentId, "comment", "comment"); // List returns the coalesced wake - const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); + const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(1); expect(wakes[0].companyId).toBe(shared.companyId); expect(wakes[0].coalescedCount).toBe(1); @@ -32,7 +32,7 @@ describe("Step 3: Wakeup request lifecycle (database)", () => { it("returns empty array when no wakeup requests exist", () => { const agentId = createAgent({ name: "TestAgent" }); - const wakes = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); + const wakes = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId, status: "queued" }); expect(wakes).toHaveLength(0); }); @@ -40,15 +40,15 @@ describe("Step 3: Wakeup request lifecycle (database)", () => { const agent1 = createAgent({ name: "Agent1" }); const agent2 = createAgent({ name: "Agent2" }); - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment"); - shared.db.wakeup.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agent1, "comment", "comment"); + shared.db.automation.wakeup.enqueueWakeupRequest(shared.companyId, agent2, "subtask_completed", "subtask_completed"); - const wakes1 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" }); + const wakes1 = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: agent1, status: "queued" }); expect(wakes1).toHaveLength(1); expect(wakes1[0].source).toBe("comment"); // Agent2's wake should still be there - const wakes2 = shared.db.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" }); + const wakes2 = shared.db.automation.wakeup.listWakeupRequests(shared.companyId, { agentId: agent2, status: "queued" }); expect(wakes2).toHaveLength(1); expect(wakes2[0].source).toBe("subtask_completed"); }); diff --git a/tests/integration/connector-model-enforcement.spec.ts b/tests/integration/connector-model-enforcement.spec.ts index c8176264..3473a289 100644 --- a/tests/integration/connector-model-enforcement.spec.ts +++ b/tests/integration/connector-model-enforcement.spec.ts @@ -57,7 +57,7 @@ function createOrchestrationHarness(db: AppDatabase) { runDispatch.resolveWorkspace, runDispatch.getRunEligibilityFailure, runDispatch.dispatchRunToWorker, - (filter) => db.runs.hasActiveRun(filter), + (filter) => db.core.runs.hasActiveRun(filter), ); return { deps, taskRunner }; @@ -69,7 +69,7 @@ describe("connector model enforcement", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -77,12 +77,12 @@ describe("connector model enforcement", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Runtime Enforcement Co", description: "Blocks runs when agent models are disabled", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -90,7 +90,7 @@ describe("connector model enforcement", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -103,7 +103,7 @@ describe("connector model enforcement", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Run with invalid model", description: "This task should be blocked because the agent model is disabled.", @@ -113,7 +113,7 @@ describe("connector model enforcement", () => { status: "todo", }); - db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); + db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); const { taskRunner } = createOrchestrationHarness(db); const result = taskRunner.queueTaskRun(taskId, "manual", "board"); @@ -123,7 +123,7 @@ describe("connector model enforcement", () => { throw new Error("Expected queueTaskRun to reject the disabled model."); } expect(result.error.code).toBe("CONNECTOR_MODEL_NOT_ALLOWED"); - expect(db.snap.listSnapshot().runs).toHaveLength(0); + expect(db.projection.snapshot.listSnapshot().runs).toHaveLength(0); db.close(); }); @@ -133,7 +133,7 @@ describe("connector model enforcement", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -141,12 +141,12 @@ describe("connector model enforcement", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Recovered Run Co", description: "Rejects invalid queued runs during recovery", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -154,7 +154,7 @@ describe("connector model enforcement", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -167,7 +167,7 @@ describe("connector model enforcement", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Recover queued run", description: "This queued run should fail on recovery because the model is disabled.", @@ -177,22 +177,22 @@ describe("connector model enforcement", () => { status: "todo", }); - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - const checkoutSuccess = db.tasks.checkoutTask(taskId, run.id); + const checkoutSuccess = db.core.tasks.checkoutTask(taskId, run.id); expect(checkoutSuccess).toBe(true); - db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); + db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); const { taskRunner } = createOrchestrationHarness(db); taskRunner.resumeQueuedRuns(); - const recoveredRun = db.snap.listSnapshot().runs.find((entry) => entry.id === run.id); + const recoveredRun = db.projection.snapshot.listSnapshot().runs.find((entry) => entry.id === run.id); expect(recoveredRun?.status).toBe("failed"); expect(recoveredRun?.errorMessage).toContain("Connector model is disabled or invalid"); diff --git a/tests/integration/cross-subsystem.spec.ts b/tests/integration/cross-subsystem.spec.ts index 1917fe64..887dc7d4 100644 --- a/tests/integration/cross-subsystem.spec.ts +++ b/tests/integration/cross-subsystem.spec.ts @@ -57,20 +57,20 @@ const mockLogger = { function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult { const connectorId: ConnectorId = "gemini-cli"; - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: connectorId, model: "gemini-2.5-pro", envBindingText: "", notes: "Non-exempt connector for session rotation tests", }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Pipeline Test Corp", description: "Cross-subsystem pipeline integration tests", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary Workspace", localPath: "/tmp/pipeline-test", @@ -79,7 +79,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult { isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "PipelineAgent", role: "IC", @@ -93,7 +93,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult { budgetMonthlyUsd: 100, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Implement Authentication", description: "Build full authentication system", @@ -101,7 +101,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult { status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Auth Project", @@ -112,7 +112,7 @@ function scaffold(db: AppDatabase, budgetAmountCents = 500): ScaffoldResult { }); // Budget policy: company-level with hardStop - const policyId = db.budget.saveBudgetPolicy({ + const policyId = db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -175,7 +175,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { expect(validation.errors).toHaveLength(0); // Create the task in the DB - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: s.companyId, title: "Build login page", description: @@ -187,14 +187,14 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // 2. CAS checkout: create run and checkout task - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: s.agentId, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - const checkoutResult = db.tasks.checkoutTask(taskId, runId); + const checkoutResult = db.core.tasks.checkoutTask(taskId, runId); expect(checkoutResult).toBe(true); // 3. Budget gate: verify run is allowed @@ -207,9 +207,9 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { expect(budgetResult.blockedBy).toHaveLength(0); // 4. Execute run - db.runs.markRunStatus(runId, "running", "Starting"); - db.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.05, unattributed: false }); - db.runs.finishRun({ + db.core.runs.markRunStatus(runId, "running", "Starting"); + db.core.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.05, unattributed: false }); + db.core.runs.finishRun({ runId, status: "succeeded", summary: "Login page built with form validation", @@ -221,7 +221,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // 5. Session management: create session and track usage - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", @@ -229,13 +229,13 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { sessionService.accumulateTokenUsage(sessionId, 5000, 2000, runId); // 6. Verify final state - const run = db.runs.getRun(runId); + const run = db.core.runs.getRun(runId); expect(run.status).toBe("succeeded"); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.costUsd).toBeGreaterThan(0); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.totalInputTokens).toBe(5000); expect(session!.totalOutputTokens).toBe(2000); @@ -254,7 +254,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { it("CAS prevents double checkout within pipeline", () => { const s = scaffold(db); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: s.companyId, title: "Concurrent Task", description: @@ -266,17 +266,17 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // First agent checks out - const { id: runId1 } = db.runs.createRun({ + const { id: runId1 } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: s.agentId, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true); // Second agent creates a run but cannot check out - const agent2Id = db.agents.saveAgent({ + const agent2Id = db.core.agents.saveAgent({ companyId: s.companyId, name: "Agent2", role: "IC", @@ -289,14 +289,14 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { capabilities: "Backend development", budgetMonthlyUsd: 100, }); - const { id: runId2 } = db.runs.createRun({ + const { id: runId2 } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: agent2Id, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false); + expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false); }); it("budget gate blocks run when policy exhausted", () => { @@ -311,7 +311,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // Create and complete a run that exceeds the budget - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: s.companyId, title: "Expensive Task", description: @@ -322,7 +322,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { status: "todo", }); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: s.agentId, @@ -330,10 +330,10 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { connectorId: s.connectorId, }); - db.tasks.checkoutTask(taskId, runId); - db.runs.markRunStatus(runId, "running", "Starting"); - db.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: s.connectorId, amountUsd: 1.5, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.markRunStatus(runId, "running", "Starting"); + db.core.runs.recordCostEntry({ runId, companyId: s.companyId, agentId: s.agentId, taskId, connectorId: s.connectorId, amountUsd: 1.5, unattributed: false }); + db.core.runs.finishRun({ runId, status: "succeeded", summary: "Done", @@ -363,7 +363,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { logger: mockLogger, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: s.companyId, title: "Multi-Run Task", description: @@ -375,14 +375,14 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // Create session - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", }); // Run 1: create run and accumulate tokens - const { id: runId1 } = db.runs.createRun({ + const { id: runId1 } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: s.agentId, @@ -392,7 +392,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { sessionService.accumulateTokenUsage(sessionId, 5000, 2000, runId1); // Run 2: create another run and accumulate more tokens - const { id: runId2 } = db.runs.createRun({ + const { id: runId2 } = db.core.runs.createRun({ companyId: s.companyId, taskId, agentId: s.agentId, @@ -402,7 +402,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { sessionService.accumulateTokenUsage(sessionId, 3000, 1000, runId2); // Verify accumulated session state - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.runCount).toBe(2); expect(session!.totalInputTokens).toBe(8000); @@ -421,7 +421,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { }); // --- First run: costs $3, budget allows --- - const taskId1 = db.tasks.saveTask({ + const taskId1 = db.core.tasks.saveTask({ companyId: s.companyId, title: "First Pipeline Task", description: @@ -432,17 +432,17 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { status: "todo", }); - const { id: runId1 } = db.runs.createRun({ + const { id: runId1 } = db.core.runs.createRun({ companyId: s.companyId, taskId: taskId1, agentId: s.agentId, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - db.tasks.checkoutTask(taskId1, runId1); - db.runs.markRunStatus(runId1, "running", "Starting"); - db.runs.recordCostEntry({ runId: runId1, companyId: s.companyId, agentId: s.agentId, taskId: taskId1, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId1, runId1); + db.core.runs.markRunStatus(runId1, "running", "Starting"); + db.core.runs.recordCostEntry({ runId: runId1, companyId: s.companyId, agentId: s.agentId, taskId: taskId1, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false }); + db.core.runs.finishRun({ runId: runId1, status: "succeeded", summary: "First task done", @@ -462,7 +462,7 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { expect(afterFirst.allowed).toBe(true); // --- Second run: costs $3, now total = $6 > $5 budget --- - const taskId2 = db.tasks.saveTask({ + const taskId2 = db.core.tasks.saveTask({ companyId: s.companyId, title: "Second Pipeline Task", description: @@ -473,17 +473,17 @@ describe("Cross-Subsystem Pipeline (D-04)", () => { status: "todo", }); - const { id: runId2 } = db.runs.createRun({ + const { id: runId2 } = db.core.runs.createRun({ companyId: s.companyId, taskId: taskId2, agentId: s.agentId, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - db.tasks.checkoutTask(taskId2, runId2); - db.runs.markRunStatus(runId2, "running", "Starting"); - db.runs.recordCostEntry({ runId: runId2, companyId: s.companyId, agentId: s.agentId, taskId: taskId2, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId2, runId2); + db.core.runs.markRunStatus(runId2, "running", "Starting"); + db.core.runs.recordCostEntry({ runId: runId2, companyId: s.companyId, agentId: s.agentId, taskId: taskId2, connectorId: s.connectorId, amountUsd: 3.0, unattributed: false }); + db.core.runs.finishRun({ runId: runId2, status: "succeeded", summary: "Second task done", diff --git a/tests/integration/database.spec.ts b/tests/integration/database.spec.ts index 7b707daa..ccff0c91 100644 --- a/tests/integration/database.spec.ts +++ b/tests/integration/database.spec.ts @@ -37,13 +37,13 @@ describe("AppDatabase", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Orbit Foundry", description: "Test company", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -52,7 +52,7 @@ describe("AppDatabase", () => { isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Helios", role: "CEO", @@ -66,7 +66,7 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 200, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Ship the desktop app", description: "Reach packaged desktop parity.", @@ -74,7 +74,7 @@ describe("AppDatabase", () => { status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Desktop rebuild", @@ -83,7 +83,7 @@ describe("AppDatabase", () => { status: "in_progress", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId, @@ -95,25 +95,25 @@ describe("AppDatabase", () => { status: "todo", }); - db.entityCrud.saveSecret({ + db.core.entity.saveSecret({ companyId, name: "vault-openai", description: "Test key", value: "sk-test-123", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: null, envBindingText: "OPENAI_API_KEY=vault-openai", notes: "Connector note", }); - expect(db.entityCrud.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({ + expect(db.core.entity.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({ OPENAI_API_KEY: "sk-test-123", }); - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, @@ -121,11 +121,11 @@ describe("AppDatabase", () => { connectorId: "openai-codex", }); - await db.runs.appendRunLog(run.id, "stdout", "hello from run\n"); - expect(db.runs.readRunLog(run.id)).toContain("hello from run"); + await db.core.runs.appendRunLog(run.id, "stdout", "hello from run\n"); + expect(db.core.runs.readRunLog(run.id)).toContain("hello from run"); - db.runs.markRunStatus(run.id, "running", "Connector launched"); - db.runs.finishRun({ + db.core.runs.markRunStatus(run.id, "running", "Connector launched"); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Completed successfully", @@ -136,22 +136,22 @@ describe("AppDatabase", () => { unattributedCost: false, }); - const snapshotBeforeBackup = db.snap.listSnapshot(); + const snapshotBeforeBackup = db.projection.snapshot.listSnapshot(); expect(snapshotBeforeBackup.companies).toHaveLength(1); expect(snapshotBeforeBackup.runs[0]?.status).toBe("succeeded"); expect(snapshotBeforeBackup.tasks[0]?.activeRunId).toBeNull(); - await db.backupMod.exportBackup(backupDir); + await db.core.backup.exportBackup(backupDir); - db.company.saveCompany({ + db.core.company.saveCompany({ name: "Temporary company", description: "Should disappear after restore", status: "active", }); - expect(db.snap.listSnapshot().companies).toHaveLength(2); + expect(db.projection.snapshot.listSnapshot().companies).toHaveLength(2); - await db.backupMod.restoreFromBackup(backupDir); - const restoredSnapshot = db.snap.listSnapshot(); + await db.core.backup.restoreFromBackup(backupDir); + const restoredSnapshot = db.projection.snapshot.listSnapshot(); expect(restoredSnapshot.companies).toHaveLength(1); expect(restoredSnapshot.companies[0]?.name).toBe("Orbit Foundry"); expect(restoredSnapshot.runs[0]?.summary).toBe("Completed successfully"); @@ -164,19 +164,19 @@ describe("AppDatabase", () => { const db = new AppDatabase(profileDir); db.init(); - const alphaCompanyId = db.company.saveCompany({ + const alphaCompanyId = db.core.company.saveCompany({ name: "Alpha", description: "Alpha company", status: "active", }); - const betaCompanyId = db.company.saveCompany({ + const betaCompanyId = db.core.company.saveCompany({ name: "Beta", description: "Beta company", status: "active", }); - const alphaWorkspaceId = db.workspacesMod.saveWorkspace({ + const alphaWorkspaceId = db.core.workspaces.saveWorkspace({ companyId: alphaCompanyId, name: "Alpha Workspace", localPath: profileDir, @@ -185,7 +185,7 @@ describe("AppDatabase", () => { isPrimary: true, }); - const betaWorkspaceId = db.workspacesMod.saveWorkspace({ + const betaWorkspaceId = db.core.workspaces.saveWorkspace({ companyId: betaCompanyId, name: "Beta Workspace", localPath: profileDir, @@ -194,7 +194,7 @@ describe("AppDatabase", () => { isPrimary: true, }); - const alphaAgentId = db.agents.saveAgent({ + const alphaAgentId = db.core.agents.saveAgent({ companyId: alphaCompanyId, name: "Alpha CEO", role: "CEO", @@ -208,7 +208,7 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 100, }); - db.agents.saveAgent({ + db.core.agents.saveAgent({ companyId: betaCompanyId, name: "Beta CEO", role: "CEO", @@ -222,7 +222,7 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 100, }); - db.tasks.saveTask({ + db.core.tasks.saveTask({ companyId: alphaCompanyId, title: "Alpha task", description: "Alpha work", @@ -232,7 +232,7 @@ describe("AppDatabase", () => { status: "todo", }); - db.tasks.saveTask({ + db.core.tasks.saveTask({ companyId: betaCompanyId, title: "Beta task", description: "Beta work", @@ -242,9 +242,9 @@ describe("AppDatabase", () => { status: "todo", }); - db.settings.setCurrentCompany(alphaCompanyId); + db.core.settings.setCurrentCompany(alphaCompanyId); - const rendererSnapshot = db.snap.listRendererSnapshot(); + const rendererSnapshot = db.projection.snapshot.listRendererSnapshot(); expect(rendererSnapshot.currentCompanyId).toBe(alphaCompanyId); expect(rendererSnapshot.companies).toHaveLength(2); expect(rendererSnapshot.connectors.length).toBeGreaterThan(0); diff --git a/tests/integration/document-run-provenance.spec.ts b/tests/integration/document-run-provenance.spec.ts index be78cc5b..32441973 100644 --- a/tests/integration/document-run-provenance.spec.ts +++ b/tests/integration/document-run-provenance.spec.ts @@ -27,7 +27,7 @@ async function createHarness() { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -35,12 +35,12 @@ async function createHarness() { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Document Provenance Co", description: "Tests document revision provenance through run completion", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -48,7 +48,7 @@ async function createHarness() { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Research Lead", role: "Researcher", @@ -61,7 +61,7 @@ async function createHarness() { capabilities: "Research and synthesis", budgetMonthlyUsd: 180, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Refresh the market report", description: "Update the report with new findings and submit it for review.", @@ -70,9 +70,9 @@ async function createHarness() { priority: "high", status: "in_progress", }); - db.tasks.setTaskType(taskId, "research"); + db.core.tasks.setTaskType(taskId, "research"); - const existingDocumentId = db.documentsMod.saveDocument({ + const existingDocumentId = db.core.documents.saveDocument({ companyId, type: "knowledge_article", title: "Market report", @@ -83,7 +83,7 @@ async function createHarness() { status: "draft", }); - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, @@ -150,7 +150,7 @@ describe("document run provenance", () => { )[0]; expect(updatedDocument?.sourceRunId).toBe(harness.run.id); - const persistedRun = harness.db.snap.listSnapshot().runs.find((entry) => entry.id === harness.run.id); + const persistedRun = harness.db.projection.snapshot.listSnapshot().runs.find((entry) => entry.id === harness.run.id); if (!persistedRun) { throw new Error("Expected persisted run record for provenance test."); } @@ -173,7 +173,7 @@ describe("document run provenance", () => { // ai_ceo + top-level CEO without `reviewDeliverables` advances // straight to `done` — there is no reviewer in that configuration // so the prior `in_review` state was a dead letter. - const task = harness.db.entityCrud.getTask(harness.taskId); + const task = harness.db.core.entity.getTask(harness.taskId); expect(task.status).toBe("done"); } finally { harness.server.stop(); @@ -285,7 +285,7 @@ describe("document run provenance", () => { it("creates deliverable review approvals that stay linked to the originating task", async () => { const harness = await createHarness(); try { - harness.db.company.saveCompany({ + harness.db.core.company.saveCompany({ id: harness.companyId, name: "Document Provenance Co", description: "Tests document review task linkage", @@ -308,7 +308,7 @@ describe("document run provenance", () => { ); expect(createResponse.status).toBe(201); - const approvals = harness.db.snap.listSnapshot().approvals.filter((approval) => approval.type === "deliverable_review"); + const approvals = harness.db.projection.snapshot.listSnapshot().approvals.filter((approval) => approval.type === "deliverable_review"); expect(approvals).toHaveLength(1); expect(approvals[0]?.relatedTaskId).toBe(harness.taskId); } finally { diff --git a/tests/integration/onboarding-model-pool.spec.ts b/tests/integration/onboarding-model-pool.spec.ts index 7cbc0af9..7aed9d3b 100644 --- a/tests/integration/onboarding-model-pool.spec.ts +++ b/tests/integration/onboarding-model-pool.spec.ts @@ -52,11 +52,11 @@ function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; lead throw new Error("Missing lead agent or task in onboarding summary."); } - const agent = db.entityCrud.getAgent(summary.leadAgentId); - const task = db.entityCrud.getTask(summary.taskId); - const connector = db.connectorsCrud.getConnector(agent.connectorId); - const workspace = summary.workspaceId ? db.workspacesMod.getWorkspace(summary.workspaceId) : null; - const company = db.snap.listSnapshot().companies.find((entry) => entry.id === summary.companyId); + const agent = db.core.entity.getAgent(summary.leadAgentId); + const task = db.core.entity.getTask(summary.taskId); + const connector = db.core.connectors.getConnector(agent.connectorId); + const workspace = summary.workspaceId ? db.core.workspaces.getWorkspace(summary.workspaceId) : null; + const company = db.projection.snapshot.listSnapshot().companies.find((entry) => entry.id === summary.companyId); if (!company) { throw new Error(`Company ${summary.companyId} not found in snapshot.`); @@ -72,7 +72,7 @@ function buildKickoffPayload(db: AppDatabase, summary: { companyId: string; lead workspaceMode: "exclusive", companyName: company.name, companyDescription: company.description, - snapshot: db.snap.listSnapshot(), + snapshot: db.projection.snapshot.listSnapshot(), trigger: "bootstrap", }, { db, @@ -98,7 +98,7 @@ describe("onboarding model pool integration", () => { throw new Error("openai-codex requires at least two models for this integration test."); } - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -125,11 +125,11 @@ describe("onboarding model pool integration", () => { throw new Error("Expected lead agent id from onboarding bootstrap."); } - const agent = db.entityCrud.getAgent(summary.leadAgentId); + const agent = db.core.entity.getAgent(summary.leadAgentId); expect(agent.connectorId).toBe("openai-codex"); expect(agent.model).toBe(enabledModel); - expect(db.settings.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]); - expect(db.settings.getCurrentCompanyId()).toBe(summary.companyId); + expect(db.core.settings.getDisabledModels(summary.companyId, "openai-codex")).toEqual([{ id: disabledModel, label: disabledModel }]); + expect(db.core.settings.getCurrentCompanyId()).toBe(summary.companyId); const payload = buildKickoffPayload(db, summary); expect(payload.model).toBe(enabledModel); @@ -161,7 +161,7 @@ describe("onboarding model pool integration", () => { throw new Error("anthropic provider must expose at least two models from pi-ai."); } - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "anthropic", status: "ready", authState: "authenticated", @@ -205,7 +205,7 @@ describe("onboarding model pool integration", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -230,7 +230,7 @@ describe("onboarding model pool integration", () => { throw new Error("Expected lead agent id from onboarding bootstrap."); } - const agent = db.entityCrud.getAgent(summary.leadAgentId); + const agent = db.core.entity.getAgent(summary.leadAgentId); expect(agent.model).toBeNull(); const payload = buildKickoffPayload(db, summary); @@ -254,7 +254,7 @@ describe("onboarding model pool integration", () => { throw new Error("openai-codex requires a default and fallback model for reroute testing."); } - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -296,7 +296,7 @@ describe("onboarding model pool integration", () => { throw new Error("openai-codex requires a model to validate onboarding rejection."); } - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", diff --git a/tests/integration/pi-mono-migrations.spec.ts b/tests/integration/pi-mono-migrations.spec.ts index 7b0f27fb..4498dd70 100644 --- a/tests/integration/pi-mono-migrations.spec.ts +++ b/tests/integration/pi-mono-migrations.spec.ts @@ -35,13 +35,13 @@ describe("pi-mono migrations 27 + 28", () => { const db = new AppDatabase(dir); db.init(); - db.sessionEntries.append({ + db.projection.sessionEntries.append({ taskSessionId: "test-session", kind: "message", role: "user", payloadJson: JSON.stringify({ role: "user", content: "hello" }), }); - const rows = db.sessionEntries.listActive("test-session"); + const rows = db.projection.sessionEntries.listActive("test-session"); expect(rows).toHaveLength(1); expect(rows[0]?.kind).toBe("message"); db.close(); @@ -53,7 +53,7 @@ describe("pi-mono migrations 27 + 28", () => { db.init(); const cipher = db.vault.encrypt(JSON.stringify({ source: "literal", value: "sk-test-1234567890" })); - db.credentialsTable.upsert({ + db.core.credentials.upsert({ connectorId: "test-anthropic", kind: "api_key", backend: cipher.backend, @@ -65,7 +65,7 @@ describe("pi-mono migrations 27 + 28", () => { lastError: null, }); - const rows = db.credentialsTable.list(); + const rows = db.core.credentials.list(); expect(rows.some((row) => row.connectorId === "test-anthropic")).toBe(true); db.close(); }); @@ -74,14 +74,14 @@ describe("pi-mono migrations 27 + 28", () => { const dir = await freshProfile("agentcompany-migration-idempotent-"); const dbA = new AppDatabase(dir); dbA.init(); - dbA.sessionEntries.append({ + dbA.projection.sessionEntries.append({ taskSessionId: "session-x", kind: "message", role: "user", payloadJson: JSON.stringify({ role: "user", content: "first" }), }); const cipher = dbA.vault.encrypt(JSON.stringify({ source: "literal", value: "sk-keep-me" })); - dbA.credentialsTable.upsert({ + dbA.core.credentials.upsert({ connectorId: "test-keep", kind: "api_key", backend: cipher.backend, @@ -96,8 +96,8 @@ describe("pi-mono migrations 27 + 28", () => { const dbB = new AppDatabase(dir); dbB.init(); - expect(dbB.sessionEntries.listActive("session-x")).toHaveLength(1); - expect(dbB.credentialsTable.list().filter((row) => row.connectorId === "test-keep")).toHaveLength(1); + expect(dbB.projection.sessionEntries.listActive("session-x")).toHaveLength(1); + expect(dbB.core.credentials.list().filter((row) => row.connectorId === "test-keep")).toHaveLength(1); dbB.close(); }); @@ -106,7 +106,7 @@ describe("pi-mono migrations 27 + 28", () => { const db = new AppDatabase(dir); db.init(); - const rows = db.snap.listSnapshot().connectors.map((entry) => entry.id); + const rows = db.projection.snapshot.listSnapshot().connectors.map((entry) => entry.id); expect(rows.length).toBe(26); expect(rows).toContain("claude-pro"); expect(rows).toContain("amazon-bedrock"); diff --git a/tests/integration/process-recovery.spec.ts b/tests/integration/process-recovery.spec.ts index 845f38aa..cb8b21e2 100644 --- a/tests/integration/process-recovery.spec.ts +++ b/tests/integration/process-recovery.spec.ts @@ -54,7 +54,7 @@ function scaffold(db: AppDatabase): ScaffoldResult { const connectorId: ConnectorId = "gemini-cli"; // Connector - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: connectorId, model: "gemini-2.5-pro", envBindingText: "", @@ -62,14 +62,14 @@ function scaffold(db: AppDatabase): ScaffoldResult { }); // Company - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Recovery Test Corp", description: "Process recovery integration tests", status: "active", }); // Workspace - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary Workspace", localPath: "/tmp/recovery-test", @@ -79,7 +79,7 @@ function scaffold(db: AppDatabase): ScaffoldResult { }); // Agent - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "RecoveryAgent", role: "IC", @@ -94,7 +94,7 @@ function scaffold(db: AppDatabase): ScaffoldResult { }); // Goal - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Build login page", description: "Implement login page with authentication", @@ -103,7 +103,7 @@ function scaffold(db: AppDatabase): ScaffoldResult { }); // Project - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Auth Module", @@ -145,7 +145,7 @@ function createAndStartRun( ): { runId: string; taskId: string } { const taskId = opts.taskId ?? - db.tasks.saveTask({ + db.core.tasks.saveTask({ companyId: opts.companyId, title: opts.taskTitle ?? "Test Task", description: @@ -157,7 +157,7 @@ function createAndStartRun( status: "todo", }); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId: opts.companyId, taskId, agentId: opts.agentId, @@ -165,11 +165,11 @@ function createAndStartRun( connectorId: opts.connectorId, }); - db.tasks.checkoutTask(taskId, runId); - db.runs.markRunStatus(runId, "running", "Starting execution"); + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.markRunStatus(runId, "running", "Starting execution"); if (opts.claimWorkspace !== false) { - db.workspacesMod.claimWorkspaceLock(opts.workspaceId, runId); + db.core.workspaces.claimWorkspaceLock(opts.workspaceId, runId); } // Set agent status to "running" to simulate active execution @@ -243,7 +243,7 @@ describe("Process Recovery -- Worker Crash Detection", () => { const s = scaffold(db); // Create a second agent for a second run - const agent2Id = db.agents.saveAgent({ + const agent2Id = db.core.agents.saveAgent({ companyId: s.companyId, name: "Agent2", role: "IC", @@ -278,8 +278,8 @@ describe("Process Recovery -- Worker Crash Detection", () => { const interruptedCount = simulateWorkerCrash(db); expect(interruptedCount).toBe(2); - expect(db.runs.getRun(run1.runId).status).toBe("interrupted"); - expect(db.runs.getRun(run2.runId).status).toBe("interrupted"); + expect(db.core.runs.getRun(run1.runId).status).toBe("interrupted"); + expect(db.core.runs.getRun(run2.runId).status).toBe("interrupted"); }); it("releases task locks after crash", () => { @@ -306,7 +306,7 @@ describe("Process Recovery -- Worker Crash Detection", () => { // However, after crash, the task is set to "blocked" and a subsequent // stale lock recovery pass handles the checkout_run_id. // For this test, verify the task status is blocked (the recovery mechanism). - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("blocked"); }); @@ -320,11 +320,11 @@ describe("Process Recovery -- Worker Crash Detection", () => { }); // Verify agent is running before crash - expect(db.entityCrud.getAgent(s.agentId).status).toBe("running"); + expect(db.core.entity.getAgent(s.agentId).status).toBe("running"); simulateWorkerCrash(db); - expect(db.entityCrud.getAgent(s.agentId).status).toBe("idle"); + expect(db.core.entity.getAgent(s.agentId).status).toBe("idle"); }); it("releases workspace locks after crash", () => { @@ -356,7 +356,7 @@ describe("Process Recovery -- Worker Crash Detection", () => { expect(wsAfter.lock_holder_run_id).toBeNull(); // Verify we can re-claim the workspace (lock is truly released) - const agent2Id = db.agents.saveAgent({ + const agent2Id = db.core.agents.saveAgent({ companyId: s.companyId, name: "Agent2", role: "IC", @@ -369,7 +369,7 @@ describe("Process Recovery -- Worker Crash Detection", () => { capabilities: "Testing", budgetMonthlyUsd: 100, }); - const taskId2 = db.tasks.saveTask({ + const taskId2 = db.core.tasks.saveTask({ companyId: s.companyId, title: "Recovery Task", description: "A recovery task after crash to verify workspace lock is released properly.", @@ -378,14 +378,14 @@ describe("Process Recovery -- Worker Crash Detection", () => { priority: "medium", status: "todo", }); - const { id: newRunId } = db.runs.createRun({ + const { id: newRunId } = db.core.runs.createRun({ companyId: s.companyId, taskId: taskId2, agentId: agent2Id, workspaceId: s.workspaceId, connectorId: s.connectorId, }); - expect(db.workspacesMod.claimWorkspaceLock(s.workspaceId, newRunId)).toBe(true); + expect(db.core.workspaces.claimWorkspaceLock(s.workspaceId, newRunId)).toBe(true); }); it("tasks set to blocked after crash", () => { @@ -398,12 +398,12 @@ describe("Process Recovery -- Worker Crash Detection", () => { }); // Verify task is in_progress before crash - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); simulateWorkerCrash(db); // Task should be blocked (prevents heartbeat re-queuing) - expect(db.entityCrud.getTask(taskId).status).toBe("blocked"); + expect(db.core.entity.getTask(taskId).status).toBe("blocked"); }); }); @@ -422,12 +422,12 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { }); // Verify assignment before crash - expect(db.entityCrud.getTask(taskId).assigneeAgentId).toBe(s.agentId); + expect(db.core.entity.getTask(taskId).assigneeAgentId).toBe(s.agentId); simulateWorkerCrash(db); // Agent assignment must be preserved for retry context - expect(db.entityCrud.getTask(taskId).assigneeAgentId).toBe(s.agentId); + expect(db.core.entity.getTask(taskId).assigneeAgentId).toBe(s.agentId); }); it("session handoff markdown generated on crash", () => { @@ -440,7 +440,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { }); // Create a task session (simulating what happens during normal execution) - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId: s.companyId, agentId: s.agentId, adapterType: "gemini-cli", @@ -448,7 +448,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { // Drive the session past the maxRuns=20 threshold so rotation fires. for (let i = 0; i < 20; i++) { - db.tasks.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`); + db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`); } const sessionService = createSessionManagementService({ @@ -456,7 +456,7 @@ describe("Process Recovery -- Context Preservation (D-02)", () => { logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, }); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); const rotationResult = sessionService.evaluateRotation(session!); @@ -499,7 +499,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => { const agents = [s.agentId]; for (let i = 2; i <= 3; i++) { agents.push( - db.agents.saveAgent({ + db.core.agents.saveAgent({ companyId: s.companyId, name: `Agent${i}`, role: "IC", @@ -530,7 +530,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => { expect(interruptedCount).toBe(3); for (const run of runs) { - expect(db.runs.getRun(run.runId).status).toBe("interrupted"); + expect(db.core.runs.getRun(run.runId).status).toBe("interrupted"); } }); @@ -540,7 +540,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => { const agents = [s.agentId]; for (let i = 2; i <= 3; i++) { agents.push( - db.agents.saveAgent({ + db.core.agents.saveAgent({ companyId: s.companyId, name: `Agent${i}`, role: "IC", @@ -571,7 +571,7 @@ describe("Process Recovery -- Multiple Concurrent Runs", () => { // All tasks should be blocked (crash recovery sets them to blocked) for (const run of runs) { - const task = db.entityCrud.getTask(run.taskId); + const task = db.core.entity.getTask(run.taskId); expect(task.status).toBe("blocked"); } diff --git a/tests/integration/skill-aware-assignment.spec.ts b/tests/integration/skill-aware-assignment.spec.ts index 31b07318..7d508dc9 100644 --- a/tests/integration/skill-aware-assignment.spec.ts +++ b/tests/integration/skill-aware-assignment.spec.ts @@ -24,7 +24,7 @@ describe("skill-aware capability registry", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -32,12 +32,12 @@ describe("skill-aware capability registry", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Skill Match Co", description: "Tests skill-aware assignment", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -46,7 +46,7 @@ describe("skill-aware capability registry", () => { isPrimary: true, }); - db.agents.saveAgent({ + db.core.agents.saveAgent({ companyId, name: "Generic Engineer", role: "Engineer", @@ -59,7 +59,7 @@ describe("skill-aware capability registry", () => { capabilities: "General software delivery", budgetMonthlyUsd: 200, }); - const styledEngineerId = db.agents.saveAgent({ + const styledEngineerId = db.core.agents.saveAgent({ companyId, name: "Frontend Specialist", role: "Engineer", @@ -75,7 +75,7 @@ describe("skill-aware capability registry", () => { const skillId = randomUUID(); const now = new Date().toISOString(); - db.skills.upsertCompanySkill({ + db.core.skills.upsertCompanySkill({ id: skillId, companyId, key: "frontend/react-ui", @@ -92,9 +92,9 @@ describe("skill-aware capability registry", () => { createdAt: now, updatedAt: now, }); - db.skills.assignSkillToAgent(styledEngineerId, skillId); + db.core.skills.assignSkillToAgent(styledEngineerId, skillId); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Build React dashboard widget", description: "Create an accessible React component with hooks and Tailwind styling.", @@ -103,7 +103,7 @@ describe("skill-aware capability registry", () => { status: "todo", }); - const assignedId = db.tasks.autoAssignTask(taskId, companyId); + const assignedId = db.core.tasks.autoAssignTask(taskId, companyId); expect(assignedId).toBe(styledEngineerId); db.close(); @@ -114,7 +114,7 @@ describe("skill-aware capability registry", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -122,12 +122,12 @@ describe("skill-aware capability registry", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Registry Co", description: "Tests capability registry prompt injection", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -135,7 +135,7 @@ describe("skill-aware capability registry", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -151,7 +151,7 @@ describe("skill-aware capability registry", () => { const skillId = randomUUID(); const now = new Date().toISOString(); - db.skills.upsertCompanySkill({ + db.core.skills.upsertCompanySkill({ id: skillId, companyId, key: "frontend/react-ui", @@ -169,9 +169,9 @@ describe("skill-aware capability registry", () => { updatedAt: now, }); - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); const prompt = buildTaskPrompt({ - agent: db.entityCrud.getAgent(agentId), + agent: db.core.entity.getAgent(agentId), apiUrl: "http://127.0.0.1:3210", apiKey: "token", runId: "run-1", @@ -193,7 +193,7 @@ describe("skill-aware capability registry", () => { allConnectors: snapshot.connectors .filter((connector) => connector.id === "openai-codex") .map((connector) => ({ id: connector.id, label: connector.label, status: connector.status, enabledModels: ["gpt-5.4"] })), - companySkillCatalog: db.skills.listCompanySkills(companyId).map((skill) => ({ + companySkillCatalog: db.core.skills.listCompanySkills(companyId).map((skill) => ({ name: skill.name, slug: skill.slug, description: skill.description, diff --git a/tests/integration/thread-wait-heartbeat.spec.ts b/tests/integration/thread-wait-heartbeat.spec.ts index 9bb98c06..2b4b65ab 100644 --- a/tests/integration/thread-wait-heartbeat.spec.ts +++ b/tests/integration/thread-wait-heartbeat.spec.ts @@ -28,7 +28,7 @@ function createNoopLogger() { } function createScaffold(db: AppDatabase) { - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -36,13 +36,13 @@ function createScaffold(db: AppDatabase) { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Thread Wait Co", description: "Tests cofounder wait states and replies", status: "active", executionMode: "ai_cofounder", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: "/tmp/thread-wait", @@ -50,7 +50,7 @@ function createScaffold(db: AppDatabase) { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -63,7 +63,7 @@ function createScaffold(db: AppDatabase) { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const engineerId = db.agents.saveAgent({ + const engineerId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -76,7 +76,7 @@ function createScaffold(db: AppDatabase) { capabilities: "Ship features", budgetMonthlyUsd: 180, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Ship the feature", description: "Implement the approved deliverable and wait for co-founder review.", @@ -116,7 +116,7 @@ describe("thread wait + heartbeat integration", () => { threadIntegration.onTaskCreated(taskId, companyId); threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null); - const thread = db.threads.listThreads({ + const thread = db.core.threads.listThreads({ companyId, type: "task", relatedEntityType: "task", @@ -128,12 +128,12 @@ describe("thread wait + heartbeat integration", () => { throw new Error("Expected a task thread to exist for the completed run."); } - let waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id); + let waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id); expect(waitStates).toHaveLength(1); expect(waitStates[0]?.waitingFor).toBe("user_reply"); expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true); - const otherAgentId = db.agents.saveAgent({ + const otherAgentId = db.core.agents.saveAgent({ companyId, name: "Marketer", role: "CMO", @@ -141,7 +141,7 @@ describe("thread wait + heartbeat integration", () => { status: "idle", reportsTo: null, connectorId: "openai-codex", - workspaceId: db.entityCrud.getTask(taskId).workspaceId ?? null, + workspaceId: db.core.entity.getTask(taskId).workspaceId ?? null, model: null, capabilities: "Drive marketing work", budgetMonthlyUsd: 180, @@ -165,7 +165,7 @@ describe("thread wait + heartbeat integration", () => { body: "Board reviewed this deliverable. Continue.", }); - waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id); + waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.threadId === thread.id); expect(waitStates).toHaveLength(0); expect(threadIntegration.hasUnresolvedWaitStatesForAgent(companyId, engineerId)).toBe(false); expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false); @@ -192,7 +192,7 @@ describe("thread wait + heartbeat integration", () => { }); threadIntegration.onTaskCreated(taskId, companyId); - const thread = db.threads.listThreads({ + const thread = db.core.threads.listThreads({ companyId, type: "task", relatedEntityType: "task", @@ -204,7 +204,7 @@ describe("thread wait + heartbeat integration", () => { throw new Error("Expected a task thread to exist for approval resolution testing."); } - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: engineerId, @@ -215,7 +215,7 @@ describe("thread wait + heartbeat integration", () => { }); threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "draft_review", "Draft review needed"); - let waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); + let waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); expect(waitStates).toHaveLength(1); expect(waitStates[0]?.waitingFor).toBe("user_approval"); expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true); @@ -234,17 +234,17 @@ describe("thread wait + heartbeat integration", () => { body: "Board left a comment but did not decide approval yet.", }); - waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); + waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); expect(waitStates).toHaveLength(1); expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(true); - db.approvalsMod.decideApproval({ + db.core.approvals.decideApproval({ approvalId, state: "approved", decisionNote: "Approved by board", }); threadIntegration.onApprovalResolved(approvalId, companyId, "approved"); - waitStates = db.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); + waitStates = db.core.threads.listThreadWaitStates(companyId).filter((entry) => entry.relatedApprovalId === approvalId); expect(waitStates).toHaveLength(0); expect(checkpointEngine.shouldPauseHeartbeat(companyId, engineerId)).toBe(false); diff --git a/tests/integration/work-products-lifecycle.spec.ts b/tests/integration/work-products-lifecycle.spec.ts index b8c32847..6da128e0 100644 --- a/tests/integration/work-products-lifecycle.spec.ts +++ b/tests/integration/work-products-lifecycle.spec.ts @@ -45,7 +45,7 @@ async function createHarness(): Promise { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -53,12 +53,12 @@ async function createHarness(): Promise { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Work Products Co", description: "Tests archived visibility, bulk retirement, deliverable signal", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: workspacePath, @@ -66,7 +66,7 @@ async function createHarness(): Promise { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Artifact Engineer", role: "Engineer", @@ -79,7 +79,7 @@ async function createHarness(): Promise { capabilities: "Writes files", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Produce artifact", description: "", @@ -88,7 +88,7 @@ async function createHarness(): Promise { priority: "medium", status: "in_progress", }); - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, @@ -107,7 +107,7 @@ async function createHarness(): Promise { } function registerArtifact(harness: Harness, relativePath: string): string { - return harness.db.documentsMod.createWorkProduct({ + return harness.db.core.documents.createWorkProduct({ companyId: harness.companyId, taskId: harness.taskId, workspaceId: harness.workspaceId, @@ -126,12 +126,12 @@ describe("work_products archived visibility", () => { try { const activeId = registerArtifact(harness, "docs/active.md"); const archivedId = registerArtifact(harness, "docs/retired.md"); - harness.db.documentsMod.updateWorkProduct(archivedId, harness.companyId, { + harness.db.core.documents.updateWorkProduct(archivedId, harness.companyId, { status: "archived", lastTouchedByRunId: harness.runId, }); - const snapshot = harness.db.snap.listSnapshot(); + const snapshot = harness.db.projection.snapshot.listSnapshot(); const visibleIds = snapshot.workProducts.map((wp) => wp.id); expect(visibleIds).toContain(activeId); expect(visibleIds).not.toContain(archivedId); @@ -145,12 +145,12 @@ describe("work_products archived visibility", () => { try { const activeId = registerArtifact(harness, "src/feature.ts"); const archivedId = registerArtifact(harness, "src/removed.ts"); - harness.db.documentsMod.updateWorkProduct(archivedId, harness.companyId, { + harness.db.core.documents.updateWorkProduct(archivedId, harness.companyId, { status: "archived", lastTouchedByRunId: harness.runId, }); - const rows = harness.db.documentsMod.listWorkProductsForTask(harness.taskId, harness.companyId); + const rows = harness.db.core.documents.listWorkProductsForTask(harness.taskId, harness.companyId); const ids = rows.map((r) => r.id); expect(ids).toContain(activeId); expect(ids).not.toContain(archivedId); @@ -164,16 +164,16 @@ describe("work_products archived visibility", () => { try { const id = registerArtifact(harness, "notes/ghost.md"); expect( - harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId), + harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId), ).not.toBeNull(); - harness.db.documentsMod.updateWorkProduct(id, harness.companyId, { + harness.db.core.documents.updateWorkProduct(id, harness.companyId, { status: "archived", lastTouchedByRunId: harness.runId, }); expect( - harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId), + harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId), ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); @@ -193,7 +193,7 @@ describe("work_products bulk retirement", () => { ids.push(registerArtifact(harness, rel)); } - const archived = harness.db.documentsMod.archiveWorkProductsByExternalPaths({ + const archived = harness.db.core.documents.archiveWorkProductsByExternalPaths({ companyId: harness.companyId, workspaceId: harness.workspaceId, provider: "filesystem", @@ -206,7 +206,7 @@ describe("work_products bulk retirement", () => { for (const id of ids) { expect(archivedIdSet.has(id)).toBe(true); } - const visible = harness.db.snap.listSnapshot().workProducts; + const visible = harness.db.projection.snapshot.listSnapshot().workProducts; expect(visible.length).toBe(0); } finally { harness.db.close(); @@ -217,7 +217,7 @@ describe("work_products bulk retirement", () => { const harness = await createHarness(); try { const id = registerArtifact(harness, "tmp/scratch.log"); - harness.db.documentsMod.archiveWorkProductsByExternalPaths({ + harness.db.core.documents.archiveWorkProductsByExternalPaths({ companyId: harness.companyId, workspaceId: harness.workspaceId, provider: "filesystem", @@ -225,7 +225,7 @@ describe("work_products bulk retirement", () => { lastTouchedByRunId: harness.runId, }); - const second = harness.db.documentsMod.archiveWorkProductsByExternalPaths({ + const second = harness.db.core.documents.archiveWorkProductsByExternalPaths({ companyId: harness.companyId, workspaceId: harness.workspaceId, provider: "filesystem", @@ -235,7 +235,7 @@ describe("work_products bulk retirement", () => { expect(second).toHaveLength(0); expect( - harness.db.documentsMod.resolveWorkProductTarget(id, harness.companyId), + harness.db.core.documents.resolveWorkProductTarget(id, harness.companyId), ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); @@ -252,7 +252,7 @@ describe("work_products bulk retirement", () => { const workProductId = registerArtifact(harness, relPath); expect( - harness.db.documentsMod.resolveWorkProductTarget(workProductId, harness.companyId), + harness.db.core.documents.resolveWorkProductTarget(workProductId, harness.companyId), ).not.toBeNull(); const baseline = { @@ -277,7 +277,7 @@ describe("work_products bulk retirement", () => { expect(retired.map((o) => (o as { relativePath: string }).relativePath)).toContain(relPath); expect( - harness.db.documentsMod.resolveWorkProductTarget(workProductId, harness.companyId), + harness.db.core.documents.resolveWorkProductTarget(workProductId, harness.companyId), ).toMatchObject({ kind: "error", reason: "archived" }); } finally { harness.db.close(); @@ -290,19 +290,19 @@ describe("work_products deliverable signal", () => { const harness = await createHarness(); try { const id = registerArtifact(harness, "deliverables/final.md"); - const beforeArchive = harness.db.knowledgeMod.collectRunDeliverableSignals( + const beforeArchive = harness.db.core.knowledge.collectRunDeliverableSignals( harness.runId, harness.companyId, harness.taskId, ); expect(beforeArchive.hasWorkProduct).toBe(true); - harness.db.documentsMod.updateWorkProduct(id, harness.companyId, { + harness.db.core.documents.updateWorkProduct(id, harness.companyId, { status: "archived", lastTouchedByRunId: harness.runId, }); - const afterArchive = harness.db.knowledgeMod.collectRunDeliverableSignals( + const afterArchive = harness.db.core.knowledge.collectRunDeliverableSignals( harness.runId, harness.companyId, harness.taskId, diff --git a/tests/unit/agent-budget-policy-sync.test.ts b/tests/unit/agent-budget-policy-sync.test.ts index a966f942..01ed64a2 100644 --- a/tests/unit/agent-budget-policy-sync.test.ts +++ b/tests/unit/agent-budget-policy-sync.test.ts @@ -22,19 +22,19 @@ describe("agent budget policy synchronization", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Budget Sync Co", description: "Budget sync regression coverage", status: "active", }); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", version: "test", lastError: null, }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -42,7 +42,7 @@ describe("agent budget policy synchronization", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -57,7 +57,7 @@ describe("agent budget policy synchronization", () => { department: "engineering", }); - const firstPolicyId = db.budget.saveBudgetPolicy({ + const firstPolicyId = db.core.budget.saveBudgetPolicy({ companyId, scopeType: "agent", scopeId: agentId, @@ -70,7 +70,7 @@ describe("agent budget policy synchronization", () => { isActive: true, }); - db.agents.saveAgent({ + db.core.agents.saveAgent({ id: agentId, companyId, name: "Engineer", @@ -86,7 +86,7 @@ describe("agent budget policy synchronization", () => { department: "engineering", }); - const secondPolicyId = db.budget.saveBudgetPolicy({ + const secondPolicyId = db.core.budget.saveBudgetPolicy({ companyId, scopeType: "agent", scopeId: agentId, @@ -99,13 +99,13 @@ describe("agent budget policy synchronization", () => { isActive: true, }); - const agentPolicies = db.budget.listBudgetPolicies(companyId).filter( + const agentPolicies = db.core.budget.listBudgetPolicies(companyId).filter( (policy) => policy.scopeType === "agent" && policy.scopeId === agentId, ); expect(secondPolicyId).toBe(firstPolicyId); expect(agentPolicies).toHaveLength(1); expect(agentPolicies[0]?.amount).toBe(25_000); - expect(db.budget.getActivePoliciesForRun(companyId, agentId, null).map((policy) => policy.amount)).toEqual([25_000]); + expect(db.core.budget.getActivePoliciesForRun(companyId, agentId, null).map((policy) => policy.amount)).toEqual([25_000]); db.close(); }); diff --git a/tests/unit/agent-hierarchy-cycle.test.ts b/tests/unit/agent-hierarchy-cycle.test.ts index 249c91df..0589cbe5 100644 --- a/tests/unit/agent-hierarchy-cycle.test.ts +++ b/tests/unit/agent-hierarchy-cycle.test.ts @@ -26,7 +26,7 @@ describe("agent hierarchy cycle guard", () => { tempDir = await mkdtemp(join(tmpdir(), "agentcompany-hierarchy-")); db = new AppDatabase(tempDir); db.init(); - companyId = db.company.saveCompany({ + companyId = db.core.company.saveCompany({ name: "Hierarchy Guard Co", description: "Tests reporting relationships.", status: "active", @@ -57,11 +57,11 @@ describe("agent hierarchy cycle guard", () => { saveAgent("engineer", "ceo"); - expect(db.entityCrud.getAgent("engineer").reportsTo).toBe("ceo"); + expect(db.core.entity.getAgent("engineer").reportsTo).toBe("ceo"); }); function saveAgent(id: string, reportsTo: string | null): string { - return db.agents.saveAgent({ + return db.core.agents.saveAgent({ id, companyId, name: id, diff --git a/tests/unit/agent-hire-routing.test.ts b/tests/unit/agent-hire-routing.test.ts index dd3a20ed..ad447f5d 100644 --- a/tests/unit/agent-hire-routing.test.ts +++ b/tests/unit/agent-hire-routing.test.ts @@ -23,19 +23,19 @@ describe("hire routing guards", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Routing Co", description: "Hire routing regression coverage", status: "active", }); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", version: "test", lastError: null, }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -43,7 +43,7 @@ describe("hire routing guards", () => { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -57,7 +57,7 @@ describe("hire routing guards", () => { budgetMonthlyUsd: 250, department: "executive", }); - const hrId = db.agents.saveAgent({ + const hrId = db.core.agents.saveAgent({ companyId, name: "HR Lead", role: "HR Lead", @@ -71,7 +71,7 @@ describe("hire routing guards", () => { budgetMonthlyUsd: 180, department: "hr", }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", diff --git a/tests/unit/agent-permissions-ceo-strategy.test.ts b/tests/unit/agent-permissions-ceo-strategy.test.ts index dc9a9bbc..427912fe 100644 --- a/tests/unit/agent-permissions-ceo-strategy.test.ts +++ b/tests/unit/agent-permissions-ceo-strategy.test.ts @@ -18,12 +18,12 @@ async function setupCompany(prefix: string) { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "CEO Strategy Co", description: "Tests canFileCeoStrategy orthogonality", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -43,7 +43,7 @@ describe("canFileCeoStrategy", () => { it("returns true for a genuine CEO who reports to the board", async () => { const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-real-"); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "Ada", role: "CEO", @@ -65,7 +65,7 @@ describe("canFileCeoStrategy", () => { it("returns false for a CEO-titled agent with a virtual superior", async () => { const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-virtual-"); - const boardId = db.agents.saveAgent({ + const boardId = db.core.agents.saveAgent({ companyId, name: "Board Proxy", role: "Board", @@ -78,7 +78,7 @@ describe("canFileCeoStrategy", () => { capabilities: "Hold CEO accountable", budgetMonthlyUsd: 100, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "Ada", role: "CEO", @@ -100,7 +100,7 @@ describe("canFileCeoStrategy", () => { it("returns false for a VP with no manager (role is not CEO)", async () => { const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-vp-"); - const vpId = db.agents.saveAgent({ + const vpId = db.core.agents.saveAgent({ companyId, name: "Kai", role: "VP Product", @@ -124,7 +124,7 @@ describe("canFileCeoStrategy", () => { it("returns false for a plain IC", async () => { const { db, companyId, workspaceId } = await setupCompany("agentcompany-ceo-strategy-ic-"); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "Ada", role: "CEO", @@ -137,7 +137,7 @@ describe("canFileCeoStrategy", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const icId = db.agents.saveAgent({ + const icId = db.core.agents.saveAgent({ companyId, name: "Eve", role: "Engineer", diff --git a/tests/unit/agent-permissions.test.ts b/tests/unit/agent-permissions.test.ts index 83e1064f..9645a069 100644 --- a/tests/unit/agent-permissions.test.ts +++ b/tests/unit/agent-permissions.test.ts @@ -30,12 +30,12 @@ describe("canManageOrganization", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Org Permissions Co", description: "Tests recursive org authority", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -44,7 +44,7 @@ describe("canManageOrganization", () => { isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -57,7 +57,7 @@ describe("canManageOrganization", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", @@ -71,7 +71,7 @@ describe("canManageOrganization", () => { budgetMonthlyUsd: 220, department: "engineering", }); - const engineerId = db.agents.saveAgent({ + const engineerId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -100,12 +100,12 @@ describe("canManageOrganization", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Org Permission Override Co", description: "Tests permission-based org authority", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -113,7 +113,7 @@ describe("canManageOrganization", () => { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -126,7 +126,7 @@ describe("canManageOrganization", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Operations Lead", role: "Specialist", @@ -142,7 +142,7 @@ describe("canManageOrganization", () => { }); expect(canManageOrganization(db, companyId, agentId)).toBe(false); - db.agents.setAgentPermission(agentId, companyId, "organization_manage", true); + db.core.agents.setAgentPermission(agentId, companyId, "organization_manage", true); expect(canManageOrganization(db, companyId, agentId)).toBe(true); expect(canRequestHire(db, companyId, agentId)).toBe(true); @@ -154,12 +154,12 @@ describe("canManageOrganization", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Scoped Authority Co", description: "Tests that branch growth does not unlock org-wide admin powers", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -167,7 +167,7 @@ describe("canManageOrganization", () => { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -180,7 +180,7 @@ describe("canManageOrganization", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", @@ -194,7 +194,7 @@ describe("canManageOrganization", () => { budgetMonthlyUsd: 220, department: "engineering", }); - const cmoId = db.agents.saveAgent({ + const cmoId = db.core.agents.saveAgent({ companyId, name: "CMO", role: "CMO", @@ -208,7 +208,7 @@ describe("canManageOrganization", () => { budgetMonthlyUsd: 220, department: "marketing", }); - const ctoReportId = db.agents.saveAgent({ + const ctoReportId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -223,7 +223,7 @@ describe("canManageOrganization", () => { department: "engineering", }); - const cmoTaskId = db.tasks.saveTask({ + const cmoTaskId = db.core.tasks.saveTask({ companyId, title: "Launch campaign", description: "Owned by the marketing branch", @@ -242,7 +242,7 @@ describe("canManageOrganization", () => { expect(canManageProject(db, companyId, ctoId, cmoId)).toBe(false); expect(canManageGoal(db, companyId, ceoId, cmoId)).toBe(true); expect(canManageProject(db, companyId, ceoId, cmoId)).toBe(true); - expect(db.entityCrud.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId); + expect(db.core.entity.getTask(cmoTaskId).assigneeAgentId).toBe(cmoId); db.close(); }); diff --git a/tests/unit/approval-hire-routing.test.ts b/tests/unit/approval-hire-routing.test.ts index 859871a0..638e98cd 100644 --- a/tests/unit/approval-hire-routing.test.ts +++ b/tests/unit/approval-hire-routing.test.ts @@ -23,7 +23,7 @@ describe("hire approval routing", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -31,12 +31,12 @@ describe("hire approval routing", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Hire Routing Co", description: "Tests recursive hire wake routing", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -44,7 +44,7 @@ describe("hire approval routing", () => { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -57,7 +57,7 @@ describe("hire approval routing", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", @@ -91,7 +91,7 @@ describe("hire approval routing", () => { payloadJson: "{}", }); - db.approvalsMod.decideApproval({ + db.core.approvals.decideApproval({ approvalId, state: "approved", decisionNote: "Approved by board", @@ -116,7 +116,7 @@ describe("hire approval routing", () => { trackTimer: vi.fn(), }, approvalId, companyId, "approved"); - expect(db.entityCrud.getAgent(hireId).status).toBe("idle"); + expect(db.core.entity.getAgent(hireId).status).toBe("idle"); expect(dispatchAutomation).toHaveBeenCalledWith("approval_resolved", expect.objectContaining({ companyId, agentId: ctoId })); expect(dispatchAutomation).toHaveBeenCalledWith("hire_approved", expect.objectContaining({ companyId, agentId: hireId })); expect(wakeAgentIfPossible).toHaveBeenCalledWith(hireId, companyId, "assignment"); diff --git a/tests/unit/approval-review-feedback-writeback.test.ts b/tests/unit/approval-review-feedback-writeback.test.ts index 13f3a9f8..c2be6735 100644 --- a/tests/unit/approval-review-feedback-writeback.test.ts +++ b/tests/unit/approval-review-feedback-writeback.test.ts @@ -23,7 +23,7 @@ describe("deliverable review feedback writeback", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -31,12 +31,12 @@ describe("deliverable review feedback writeback", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Review Feedback Co", description: "Tests review feedback knowledge writeback", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -44,7 +44,7 @@ describe("deliverable review feedback writeback", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Research Lead", role: "Researcher", @@ -58,7 +58,7 @@ describe("deliverable review feedback writeback", () => { budgetMonthlyUsd: 180, department: "research", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Draft market report", description: "Produce a market report with concrete evidence and business insight.", @@ -67,7 +67,7 @@ describe("deliverable review feedback writeback", () => { priority: "high", status: "in_review", }); - const documentId = db.documentsMod.saveDocument({ + const documentId = db.core.documents.saveDocument({ companyId, type: "knowledge_article", title: "Market report", @@ -75,7 +75,7 @@ describe("deliverable review feedback writeback", () => { authorAgentId: agentId, status: "in_review", }); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: agentId, @@ -85,7 +85,7 @@ describe("deliverable review feedback writeback", () => { payloadJson: JSON.stringify({ documentIds: [documentId] }), }); - db.approvalsMod.decideApproval({ + db.core.approvals.decideApproval({ approvalId, state: "revision_requested", decisionNote: "Add stronger competitor evidence and a clearer recommendation.", @@ -107,7 +107,7 @@ describe("deliverable review feedback writeback", () => { trackTimer: vi.fn(), }, approvalId, companyId, "revision_requested"); - const knowledgeEntries = db.snap.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId); + const knowledgeEntries = db.projection.snapshot.listSnapshot().knowledgeBase.filter((entry) => entry.companyId === companyId); expect(knowledgeEntries).toHaveLength(1); expect(knowledgeEntries[0]?.category).toBe("lesson_learned"); expect(knowledgeEntries[0]?.importance).toBe("high"); @@ -116,8 +116,8 @@ describe("deliverable review feedback writeback", () => { expect(knowledgeEntries[0]?.content).toContain("revision_requested"); expect(knowledgeEntries[0]?.content).toContain("stronger competitor evidence"); expect(wakeAgentIfPossible).toHaveBeenCalledWith(agentId, companyId, "revision_needed"); - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); - const document = db.snap.listSnapshot().documents.find((entry) => entry.id === documentId); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); + const document = db.projection.snapshot.listSnapshot().documents.find((entry) => entry.id === documentId); expect(document?.status).toBe("draft"); db.close(); diff --git a/tests/unit/atomic-checkout.test.ts b/tests/unit/atomic-checkout.test.ts index 7c5e3499..09616e68 100644 --- a/tests/unit/atomic-checkout.test.ts +++ b/tests/unit/atomic-checkout.test.ts @@ -29,7 +29,7 @@ type RawDb = { // --------------------------------------------------------------------------- function createTestCompany(db: AppDatabase): string { - return db.company.saveCompany({ + return db.core.company.saveCompany({ name: "Test Company", description: "Atomic checkout tests", status: "active", @@ -37,7 +37,7 @@ function createTestCompany(db: AppDatabase): string { } function createTestConnector(db: AppDatabase): void { - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", @@ -46,7 +46,7 @@ function createTestConnector(db: AppDatabase): void { } function createTestWorkspace(db: AppDatabase, companyId: string): string { - return db.workspacesMod.saveWorkspace({ + return db.core.workspaces.saveWorkspace({ companyId, name: "Test Workspace", localPath: "/tmp/test-workspace", @@ -57,7 +57,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string { } function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string { - return db.agents.saveAgent({ + return db.core.agents.saveAgent({ companyId, name, role: "IC", @@ -73,7 +73,7 @@ function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string } function createTestTask(db: AppDatabase, companyId: string, agentId: string | null, workspaceId: string): string { - return db.tasks.saveTask({ + return db.core.tasks.saveTask({ companyId, title: "Test Task", description: "A test task for checkout", @@ -110,7 +110,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -118,10 +118,10 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - const result = db.tasks.checkoutTask(taskId, runId); + const result = db.core.tasks.checkoutTask(taskId, runId); expect(result).toBe(true); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("in_progress"); }); @@ -132,7 +132,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - db.runs.createRun({ + db.core.runs.createRun({ companyId, taskId, agentId, @@ -140,7 +140,7 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.activeRunId).toBeNull(); expect(task.status).toBe("todo"); }); @@ -152,9 +152,9 @@ describe("CAS checkout", () => { const taskId = createTestTask(db, companyId, null, workspaceId); const missingRunId = randomUUID(); - expect(() => db.tasks.checkoutTask(taskId, missingRunId)).toThrow(/Run not found/); + expect(() => db.core.tasks.checkoutTask(taskId, missingRunId)).toThrow(/Run not found/); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.activeRunId).toBeNull(); expect(task.status).toBe("todo"); const lock = (db as unknown as RawDb).db @@ -171,7 +171,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const run = db.runs.createTaskRun({ + const run = db.core.runs.createTaskRun({ companyId, taskId, agentId, @@ -180,7 +180,7 @@ describe("CAS checkout", () => { }); expect(run).not.toBeNull(); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("in_progress"); expect(task.activeRunId).toBe(run?.id); const lock = (db as unknown as RawDb).db @@ -196,7 +196,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const firstRun = db.runs.createTaskRun({ + const firstRun = db.core.runs.createTaskRun({ companyId, taskId, agentId, @@ -206,7 +206,7 @@ describe("CAS checkout", () => { expect(firstRun).not.toBeNull(); const losingRunId = randomUUID(); - const secondRun = db.runs.createTaskRun({ + const secondRun = db.core.runs.createTaskRun({ id: losingRunId, companyId, taskId, @@ -229,14 +229,14 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId1 } = db.runs.createRun({ + const { id: runId1 } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - const { id: runId2 } = db.runs.createRun({ + const { id: runId2 } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -244,8 +244,8 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true); - expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false); + expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false); }); it("CAS checkout rejects already-in-progress tasks", () => { @@ -258,7 +258,7 @@ describe("CAS checkout", () => { // Manually set task to in_progress (db as unknown as RawDb).db.prepare("UPDATE tasks SET status = 'in_progress' WHERE id = ?").run(taskId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -266,7 +266,7 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - expect(db.tasks.checkoutTask(taskId, runId)).toBe(false); + expect(db.core.tasks.checkoutTask(taskId, runId)).toBe(false); }); it("lock release in finishRun clears checkout fields", () => { @@ -276,7 +276,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -284,9 +284,9 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); - db.runs.markRunStatus(runId, "running", ""); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.markRunStatus(runId, "running", ""); + db.core.runs.finishRun({ runId, status: "succeeded", summary: "Done", @@ -312,7 +312,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -320,11 +320,11 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); - db.runs.markRunStatus(runId, "running", ""); + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.markRunStatus(runId, "running", ""); // Deleting the agent triggers cancelRunForDeletion for active runs - db.agents.deleteAgent(agentId, companyId); + db.core.agents.deleteAgent(agentId, companyId); const row = (db as unknown as RawDb).db .prepare("SELECT checkout_run_id, execution_locked_at FROM tasks WHERE id = ?") @@ -341,7 +341,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -349,7 +349,7 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); + db.core.tasks.checkoutTask(taskId, runId); // Backdate execution_locked_at to 20 minutes ago const backdated = new Date(Date.now() - 20 * 60 * 1000).toISOString(); @@ -363,7 +363,7 @@ describe("CAS checkout", () => { expect(recovered[0].taskId).toBe(taskId); expect(recovered[0].companyId).toBe(companyId); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("todo"); const row = (db as unknown as RawDb).db @@ -381,7 +381,7 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, @@ -389,12 +389,12 @@ describe("CAS checkout", () => { connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); + db.core.tasks.checkoutTask(taskId, runId); const recovered = db.recoverStaleLocks(); expect(recovered.length).toBe(0); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("in_progress"); }); @@ -414,19 +414,19 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); + db.core.tasks.checkoutTask(taskId, runId); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); // Never call markRunStatus('running', ...) — this simulates the // "workspace already locked" bail-out inside dispatchRunToWorker. - db.runs.finishRun({ + db.core.runs.finishRun({ runId, status: "failed", summary: null, @@ -437,18 +437,18 @@ describe("CAS checkout", () => { unattributedCost: true, }); - expect(db.entityCrud.getTask(taskId).status).toBe("todo"); + expect(db.core.entity.getTask(taskId).status).toBe("todo"); // And the next CAS checkout must succeed — proving the heartbeat // retry path is unblocked. - const { id: retryRunId } = db.runs.createRun({ + const { id: retryRunId } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - expect(db.tasks.checkoutTask(taskId, retryRunId)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, retryRunId)).toBe(true); }); it("cancelled run that never started also rolls task back to 'todo'", () => { @@ -462,15 +462,15 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.finishRun({ runId, status: "cancelled", summary: null, @@ -481,7 +481,7 @@ describe("CAS checkout", () => { unattributedCost: true, }); - expect(db.entityCrud.getTask(taskId).status).toBe("todo"); + expect(db.core.entity.getTask(taskId).status).toBe("todo"); }); it("failed run that did start stays 'in_progress' (human retry path)", () => { @@ -495,16 +495,16 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: runId } = db.runs.createRun({ + const { id: runId } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, runId); - db.runs.markRunStatus(runId, "running", ""); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, runId); + db.core.runs.markRunStatus(runId, "running", ""); + db.core.runs.finishRun({ runId, status: "failed", summary: null, @@ -515,7 +515,7 @@ describe("CAS checkout", () => { unattributedCost: false, }); - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); }); it("advanceTaskStatus('in_review') rejects a CEO self-review that has no reviewer", () => { @@ -531,9 +531,9 @@ describe("CAS checkout", () => { const ceoId = createTestAgent(db, companyId, workspaceId, "CEO"); const taskId = createTestTask(db, companyId, ceoId, workspaceId); - expect(() => db.tasks.advanceTaskStatus(taskId, "in_review")).toThrow(/no reviewer/); + expect(() => db.core.tasks.advanceTaskStatus(taskId, "in_review")).toThrow(/no reviewer/); // Task status must remain unchanged after the rejected transition. - expect(db.entityCrud.getTask(taskId).status).toBe("todo"); + expect(db.core.entity.getTask(taskId).status).toBe("todo"); }); it("advanceTaskStatus('in_review') accepts a CEO assignee flagged requires_user_review=1", () => { @@ -543,10 +543,10 @@ describe("CAS checkout", () => { const workspaceId = createTestWorkspace(db, companyId); const ceoId = createTestAgent(db, companyId, workspaceId, "CEO"); const taskId = createTestTask(db, companyId, ceoId, workspaceId); - db.tasks.setTaskRequiresUserReview(taskId, true); + db.core.tasks.setTaskRequiresUserReview(taskId, true); - db.tasks.advanceTaskStatus(taskId, "in_review"); - expect(db.entityCrud.getTask(taskId).status).toBe("in_review"); + db.core.tasks.advanceTaskStatus(taskId, "in_review"); + expect(db.core.entity.getTask(taskId).status).toBe("in_review"); }); it("interruptStaleRuns surfaces shutdown-era running runs without startup task rewrites", () => { @@ -563,21 +563,21 @@ describe("CAS checkout", () => { const agentId = createTestAgent(db, companyId, workspaceId); const taskId = createTestTask(db, companyId, null, workspaceId); - const { id: liveRunId } = db.runs.createRun({ + const { id: liveRunId } = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex", }); - db.tasks.checkoutTask(taskId, liveRunId); - db.runs.markRunStatus(liveRunId, "running", ""); + db.core.tasks.checkoutTask(taskId, liveRunId); + db.core.runs.markRunStatus(liveRunId, "running", ""); db.close(); db = new AppDatabase(tempDir); db.init(); expect(db.lastInterruptedCount).toBe(1); - expect(db.entityCrud.getTask(taskId).status).toBe("blocked"); + expect(db.core.entity.getTask(taskId).status).toBe("blocked"); }); }); diff --git a/tests/unit/budget-enforcement.test.ts b/tests/unit/budget-enforcement.test.ts index 8bfa3071..75dea328 100644 --- a/tests/unit/budget-enforcement.test.ts +++ b/tests/unit/budget-enforcement.test.ts @@ -147,7 +147,7 @@ function createMockDb() { return computeSpentCents(filtered); }, }; - return { policies, costEntries, budget }; + return { policies, costEntries, core: { budget } }; } // ====================================================================== @@ -162,7 +162,7 @@ describe("Budget Policy CRUD", () => { }); it("saveBudgetPolicy creates new policy and returns its ID", () => { - const id = db.budget.saveBudgetPolicy({ + const id = db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", @@ -175,13 +175,13 @@ describe("Budget Policy CRUD", () => { }); it("saveBudgetPolicy updates existing policy when ID provided", () => { - const id = db.budget.saveBudgetPolicy({ + const id = db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 10000, }); - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ id, companyId: "c1", scopeType: "company", @@ -193,14 +193,14 @@ describe("Budget Policy CRUD", () => { }); it("deleteBudgetPolicy removes policy by ID", () => { - const id = db.budget.saveBudgetPolicy({ + const id = db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 10000, }); expect(db.policies).toHaveLength(1); - db.budget.deleteBudgetPolicy("c1", id); + db.core.budget.deleteBudgetPolicy("c1", id); expect(db.policies).toHaveLength(0); }); }); @@ -213,27 +213,27 @@ describe("getActivePoliciesForRun", () => { }); it("returns all active policies matching company+agent+project scopes", () => { - db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 }); - db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 }); - db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 }); + db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 }); + db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 }); + db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 }); // Different company — should not match - db.budget.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 }); + db.core.budget.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 }); - const result = db.budget.getActivePoliciesForRun("c1", "a1", "p1"); + const result = db.core.budget.getActivePoliciesForRun("c1", "a1", "p1"); expect(result).toHaveLength(3); expect(result.map((p) => p.scopeType).sort()).toEqual(["agent", "company", "project"]); }); it("returns empty array when no policies exist (D-02: no policy = unlimited)", () => { - const result = db.budget.getActivePoliciesForRun("c1", "a1", null); + const result = db.core.budget.getActivePoliciesForRun("c1", "a1", null); expect(result).toEqual([]); }); it("excludes project scope when projectId is null", () => { - db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 }); - db.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 }); + db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 }); + db.core.budget.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 }); - const result = db.budget.getActivePoliciesForRun("c1", "a1", null); + const result = db.core.budget.getActivePoliciesForRun("c1", "a1", null); expect(result).toHaveLength(1); expect(result[0]!.scopeType).toBe("company"); }); @@ -259,7 +259,7 @@ describe("getSpentInWindow", () => { amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true, isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(), }; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); expect(spent).toBe(380); // (1.50 + 2.30) * 100 = 380 cents }); @@ -276,7 +276,7 @@ describe("getSpentInWindow", () => { amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true, isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(), }; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); expect(spent).toBe(500); // Only a1's $5.00 = 500 cents }); @@ -293,7 +293,7 @@ describe("getSpentInWindow", () => { amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true, isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(), }; - const spent = db.budget.getSpentInWindow(policy); + const spent = db.core.budget.getSpentInWindow(policy); expect(spent).toBe(750); // Only p1's $7.50 = 750 cents }); }); diff --git a/tests/unit/budget-incidents.test.ts b/tests/unit/budget-incidents.test.ts index 59585c11..78664c86 100644 --- a/tests/unit/budget-incidents.test.ts +++ b/tests/unit/budget-incidents.test.ts @@ -131,7 +131,7 @@ function createMockIncidentDb() { return true; }, }; - return { incidents, budget }; + return { incidents, core: { budget } }; } function makePolicy(overrides: Partial = {}): BudgetPolicyRecord { @@ -166,7 +166,7 @@ describe("upsertBudgetIncident", () => { it("creates new incident when none exists for policy+window+threshold_type", () => { const policy = makePolicy(); - const id = db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + const id = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); expect(id).toBeTruthy(); expect(db.incidents).toHaveLength(1); expect(db.incidents[0]!.thresholdType).toBe("warn_threshold"); @@ -176,8 +176,8 @@ describe("upsertBudgetIncident", () => { it("updates amount_observed when open incident already exists (D-08 dedup)", () => { const policy = makePolicy(); - const id1 = db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - const id2 = db.budget.upsertBudgetIncident(policy, "warn_threshold", 9200); + const id1 = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + const id2 = db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 9200); expect(id1).toBe(id2); // Same incident, not a new one expect(db.incidents).toHaveLength(1); // Still just one expect(db.incidents[0]!.amountObserved).toBe(9200); // Updated @@ -185,8 +185,8 @@ describe("upsertBudgetIncident", () => { it("creates separate incidents for different threshold types", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); expect(db.incidents).toHaveLength(2); expect(db.incidents[0]!.thresholdType).toBe("warn_threshold"); expect(db.incidents[1]!.thresholdType).toBe("hard_stop"); @@ -202,10 +202,10 @@ describe("resolveIncidentsForPolicy", () => { it("sets status to provided resolution reason for all open incidents", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); - const count = db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); + const count = db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); expect(count).toBe(2); expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase"); expect(db.incidents[1]!.status).toBe("auto_resolved_limit_increase"); @@ -214,10 +214,10 @@ describe("resolveIncidentsForPolicy", () => { it("does not affect already-resolved incidents", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.resolveIncidentsForPolicy("pol-1", "resolved"); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.resolveIncidentsForPolicy("pol-1", "resolved"); // Now try to resolve again - const count = db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); + const count = db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); expect(count).toBe(0); expect(db.incidents[0]!.status).toBe("resolved"); // Unchanged }); @@ -252,7 +252,7 @@ describe("autoCloseExpiredIncidents", () => { updatedAt: "2025-01-15T00:00:00.000Z", }); - const count = db.budget.autoCloseExpiredIncidents("c1"); + const count = db.core.budget.autoCloseExpiredIncidents("c1"); expect(count).toBe(1); expect(db.incidents[0]!.status).toBe("auto_closed"); expect(db.incidents[0]!.resolvedAt).toBeTruthy(); @@ -260,10 +260,10 @@ describe("autoCloseExpiredIncidents", () => { it("does not close incidents whose window_end is in the future", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); // The auto-created incident has window_end in the future (end of current month) - const count = db.budget.autoCloseExpiredIncidents("c1"); + const count = db.core.budget.autoCloseExpiredIncidents("c1"); expect(count).toBe(0); expect(db.incidents[0]!.status).toBe("open"); }); @@ -278,22 +278,22 @@ describe("listBudgetIncidents", () => { it("returns all incidents for a company when status is 'all'", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); - db.budget.resolveIncidentsForPolicy("pol-1", "resolved"); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.resolveIncidentsForPolicy("pol-1", "resolved"); - const all = db.budget.listBudgetIncidents("c1", "all"); + const all = db.core.budget.listBudgetIncidents("c1", "all"); expect(all).toHaveLength(2); }); it("filters by status when specified", () => { const policy = makePolicy(); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); // Resolve only warn - db.budget.resolveWarnIncidentsForPolicy("pol-1", "resolved"); + db.core.budget.resolveWarnIncidentsForPolicy("pol-1", "resolved"); - const open = db.budget.listBudgetIncidents("c1", "open"); + const open = db.core.budget.listBudgetIncidents("c1", "open"); expect(open).toHaveLength(1); expect(open[0]!.thresholdType).toBe("hard_stop"); }); @@ -308,7 +308,7 @@ describe("checkAutoResolve (D-06)", () => { it("auto-resolves incidents when policy limit increases above observed amount", () => { const policy = makePolicy({ amount: 10000 }); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); expect(db.incidents[0]!.status).toBe("open"); // Simulate limit increase: now checking if spent < new limit @@ -316,7 +316,7 @@ describe("checkAutoResolve (D-06)", () => { const spent = 10500; const newAmount = 20000; if (spent < newAmount) { - db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); + db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); } expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase"); @@ -324,14 +324,14 @@ describe("checkAutoResolve (D-06)", () => { it("auto-resolves only warn incidents when below warn threshold but above hard stop", () => { const policy = makePolicy({ amount: 10000, warnPercent: 80 }); - db.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); - db.budget.upsertBudgetIncident(policy, "hard_stop", 10500); + db.core.budget.upsertBudgetIncident(policy, "warn_threshold", 8500); + db.core.budget.upsertBudgetIncident(policy, "hard_stop", 10500); // Spent is now 7500 (below warn threshold of 8000 = 10000*80/100) const spent = 7500; const _warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100); if (spent < policy.amount) { - db.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); + db.core.budget.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase"); } // Both resolved since spent < policy.amount expect(db.incidents.every((i) => i.status === "auto_resolved_limit_increase")).toBe(true); diff --git a/tests/unit/cofounder-approval-gate.test.ts b/tests/unit/cofounder-approval-gate.test.ts index 0cc43bd5..22447d5d 100644 --- a/tests/unit/cofounder-approval-gate.test.ts +++ b/tests/unit/cofounder-approval-gate.test.ts @@ -19,19 +19,19 @@ import { } from "../../src/main/agent-runtime/hook-handlers"; function seedCofounderRun(db: AppDatabase) { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Gate Co", description: "Cofounder runtime gate test", status: "active", executionMode: "ai_cofounder", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", notes: "fixture", }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Researcher", role: "Research Lead", @@ -45,7 +45,7 @@ function seedCofounderRun(db: AppDatabase) { capabilities: "Research", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Draft market plan", description: "Prepare the market plan after approval.", @@ -87,7 +87,7 @@ describe("cofounder approval runtime gate", () => { it("blocks destructive tools while a cofounder plan review is pending", () => { const { companyId, agentId, taskId, runContext } = seedCofounderRun(db); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: agentId, @@ -97,7 +97,7 @@ describe("cofounder approval runtime gate", () => { payloadJson: "{}", }); - const blocking = db.approvalsMod.getBlockingApproval(companyId, agentId, taskId); + const blocking = db.core.approvals.getBlockingApproval(companyId, agentId, taskId); expect(blocking?.id).toBe(approvalId); const result = handleBeforeToolCall( diff --git a/tests/unit/company-mode-switch.test.ts b/tests/unit/company-mode-switch.test.ts index 41208593..8f8a5c97 100644 --- a/tests/unit/company-mode-switch.test.ts +++ b/tests/unit/company-mode-switch.test.ts @@ -32,7 +32,7 @@ describe("company mode switching", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -40,7 +40,7 @@ describe("company mode switching", () => { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Mode Switch Co", description: "Execution mode cleanup", status: "active", @@ -48,7 +48,7 @@ describe("company mode switching", () => { autoApproveHires: false, reviewDeliverables: true, }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -56,7 +56,7 @@ describe("company mode switching", () => { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -69,7 +69,7 @@ describe("company mode switching", () => { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const engineerId = db.agents.saveAgent({ + const engineerId = db.core.agents.saveAgent({ companyId, name: "Engineer", role: "Engineer", @@ -83,7 +83,7 @@ describe("company mode switching", () => { budgetMonthlyUsd: 180, department: "engineering", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Ship feature", description: "Waits for cofounder review.", @@ -100,8 +100,8 @@ describe("company mode switching", () => { threadIntegration.onTaskCreated(taskId, companyId); threadIntegration.onRunFinished("run-1", taskId, engineerId, companyId, "succeeded", null); - db.tasks.setTaskRequiresUserReview(taskId, true); - const approvalId = db.approvalsMod.requestApproval({ + db.core.tasks.setTaskRequiresUserReview(taskId, true); + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: taskId, requestedByAgentId: engineerId, @@ -112,9 +112,9 @@ describe("company mode switching", () => { }); threadIntegration.onApprovalCreated(approvalId, companyId, taskId, "deliverable_review", "Review deliverable"); - expect(db.threads.listThreadWaitStates(companyId).length).toBeGreaterThan(0); - expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(true); - expect(db.approvalsMod.getApprovalRecord(approvalId).state).toBe("pending"); + expect(db.core.threads.listThreadWaitStates(companyId).length).toBeGreaterThan(0); + expect(db.core.entity.getTask(taskId).requiresUserReview).toBe(true); + expect(db.core.approvals.getApprovalRecord(approvalId).state).toBe("pending"); const handlers = new Map(); registerCoreAdminHandlers({ @@ -122,7 +122,7 @@ describe("company mode switching", () => { handlers.set(channel, handler); }, db, - ensureCompanyExists: (id) => (db.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }), + ensureCompanyExists: (id) => (db.core.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }), publishDomainChanged: vi.fn(), handleApprovalResolved: (resolvedApprovalId, resolvedCompanyId, decision) => { threadIntegration.onApprovalResolved(resolvedApprovalId, resolvedCompanyId, decision); @@ -147,10 +147,10 @@ describe("company mode switching", () => { }) as { ok: boolean }; expect(result.ok).toBe(true); - expect(db.company.getCompanyExecutionMode(companyId)).toBe("ai_ceo"); - expect(db.threads.listThreadWaitStates(companyId)).toHaveLength(0); - expect(db.entityCrud.getTask(taskId).requiresUserReview).toBe(false); - expect(db.approvalsMod.getApprovalRecord(approvalId).state).toBe("approved"); + expect(db.core.company.getCompanyExecutionMode(companyId)).toBe("ai_ceo"); + expect(db.core.threads.listThreadWaitStates(companyId)).toHaveLength(0); + expect(db.core.entity.getTask(taskId).requiresUserReview).toBe(false); + expect(db.core.approvals.getApprovalRecord(approvalId).state).toBe("approved"); db.close(); }); diff --git a/tests/unit/database-module-groups.test.ts b/tests/unit/database-module-groups.test.ts new file mode 100644 index 00000000..e372a92e --- /dev/null +++ b/tests/unit/database-module-groups.test.ts @@ -0,0 +1,78 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { AppDatabase } from "../../src/main/database"; + +const FLAT_MODULE_KEYS = [ + "threads", + "company", + "agents", + "tasks", + "messages", + "runs", + "snap", + "settings", + "connectorsCrud", + "workspacesMod", + "approvalsMod", + "social", + "activityMod", + "skills", + "budget", + "entityCrud", + "documentsMod", + "automationMod", + "knowledgeMod", + "wakeup", + "backupMod", + "sessionEntries", + "inboxProjections", + "documentLifecycleEvents", + "credentialsTable", + "modelCapabilities", +]; + +describe("AppDatabase module groups", () => { + it("exposes domain modules through grouped namespaces", () => { + const dir = mkdtempSync(join(tmpdir(), "agentcompany-db-groups-")); + const db = new AppDatabase(dir); + try { + expect(Object.keys(db.core).sort()).toEqual([ + "activity", + "agents", + "approvals", + "backup", + "budget", + "company", + "connectors", + "credentials", + "documents", + "entity", + "knowledge", + "messages", + "modelCapabilities", + "runs", + "settings", + "skills", + "social", + "tasks", + "threads", + "workspaces", + ]); + expect(Object.keys(db.automation).sort()).toEqual(["rules", "wakeup"]); + expect(Object.keys(db.projection).sort()).toEqual([ + "documentLifecycleEvents", + "inbox", + "sessionEntries", + "snapshot", + ]); + for (const key of FLAT_MODULE_KEYS) { + expect(Object.prototype.hasOwnProperty.call(db, key)).toBe(false); + } + } finally { + db.close(); + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/unit/database.test.ts b/tests/unit/database.test.ts index 5228067c..77800b8c 100644 --- a/tests/unit/database.test.ts +++ b/tests/unit/database.test.ts @@ -49,12 +49,12 @@ describe("AppDatabase", () => { }); it("persists company-scoped entities and resolves secret env bindings", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Northline Systems", description: "Operator company", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary Workspace", localPath: "/tmp/northline", @@ -62,7 +62,7 @@ describe("AppDatabase", () => { repoRef: "main", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Chief Operator", role: "CEO", @@ -75,14 +75,14 @@ describe("AppDatabase", () => { capabilities: "Directs execution", budgetMonthlyUsd: 250, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Ship the desktop product", description: "Stabilize the local operator console.", ownerAgentId: agentId, status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Desktop Rebuild", @@ -91,7 +91,7 @@ describe("AppDatabase", () => { status: "in_progress", targetDate: null, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId, @@ -103,20 +103,20 @@ describe("AppDatabase", () => { status: "todo", }); - db.entityCrud.saveSecret({ + db.core.entity.saveSecret({ companyId, name: "vault-openai", description: "OpenAI API token", value: "sk-test-secret", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "OPENAI_API_KEY=vault-openai", notes: "Primary coding connector", }); - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); expect(snapshot.currentCompanyId).toBe(companyId); expect(snapshot.workspaces[0]?.id).toBe(workspaceId); @@ -124,71 +124,71 @@ describe("AppDatabase", () => { expect(snapshot.goals[0]?.id).toBe(goalId); expect(snapshot.projects[0]?.id).toBe(projectId); expect(snapshot.tasks[0]?.id).toBe(taskId); - expect(db.entityCrud.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({ + expect(db.core.entity.resolveSecretEnv("OPENAI_API_KEY=vault-openai", companyId)).toEqual({ OPENAI_API_KEY: "sk-test-secret", }); }); it("backs up and restores the profile", async () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Backup Test Co", description: "Backup validation company", status: "active", }); const backupDir = await mkdtemp(join(tmpdir(), "agentcompany-backup-")); - await db.backupMod.exportBackup(backupDir); - db.company.saveCompany({ + await db.core.backup.exportBackup(backupDir); + db.core.company.saveCompany({ id: companyId, name: "Mutated Company", description: "Changed after backup", status: "paused", }); - await db.backupMod.restoreFromBackup(backupDir); + await db.core.backup.restoreFromBackup(backupDir); - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); expect(snapshot.companies[0]?.name).toBe("Backup Test Co"); await rm(backupDir, { recursive: true, force: true }); }); it("scopes secret resolution to the owning company", () => { - const alphaCompanyId = db.company.saveCompany({ + const alphaCompanyId = db.core.company.saveCompany({ name: "Alpha Company", description: "Alpha", status: "active", }); - const betaCompanyId = db.company.saveCompany({ + const betaCompanyId = db.core.company.saveCompany({ name: "Beta Company", description: "Beta", status: "active", }); - db.entityCrud.saveSecret({ + db.core.entity.saveSecret({ companyId: alphaCompanyId, name: "shared-secret-name", description: "Alpha secret", value: "alpha-secret", }); - expect(db.entityCrud.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({ + expect(db.core.entity.resolveSecretEnv("API_KEY=shared-secret-name", alphaCompanyId)).toEqual({ API_KEY: "alpha-secret", }); // Beta company has no secret named "shared-secret-name", so resolveSecretEnv // falls back to using the raw value literally (allows direct env bindings). - expect(db.entityCrud.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({ + expect(db.core.entity.resolveSecretEnv("API_KEY=shared-secret-name", betaCompanyId)).toEqual({ API_KEY: "shared-secret-name", }); }); it("atomically checks out unclaimed work via CAS with run ID", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Checkout Company", description: "Atomic checkout validation", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Execution Workspace", localPath: "/tmp/checkout", @@ -196,7 +196,7 @@ describe("AppDatabase", () => { repoRef: "main", isPrimary: true, }); - const leadId = db.agents.saveAgent({ + const leadId = db.core.agents.saveAgent({ companyId, name: "Lead", role: "CEO", @@ -209,13 +209,13 @@ describe("AppDatabase", () => { capabilities: "Leads", budgetMonthlyUsd: 100, }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", notes: "Test connector", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Claim me", description: "First agent wins the checkout.", @@ -225,14 +225,14 @@ describe("AppDatabase", () => { status: "todo", }); - const { id: runId1 } = db.runs.createRun({ + const { id: runId1 } = db.core.runs.createRun({ companyId, taskId, agentId: leadId, workspaceId, connectorId: "openai-codex", }); - const { id: runId2 } = db.runs.createRun({ + const { id: runId2 } = db.core.runs.createRun({ companyId, taskId, agentId: leadId, @@ -240,20 +240,20 @@ describe("AppDatabase", () => { connectorId: "openai-codex", }); - expect(db.tasks.checkoutTask(taskId, runId1)).toBe(true); - expect(db.tasks.checkoutTask(taskId, runId2)).toBe(false); + expect(db.core.tasks.checkoutTask(taskId, runId1)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runId2)).toBe(false); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("in_progress"); }); it("auto-pauses an agent when a run exhausts the monthly budget", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Budget Company", description: "Budget hard stop validation", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Budget Workspace", localPath: "/tmp/budget", @@ -261,7 +261,7 @@ describe("AppDatabase", () => { repoRef: "main", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Budget Owner", role: "CTO", @@ -274,7 +274,7 @@ describe("AppDatabase", () => { capabilities: "Ships work", budgetMonthlyUsd: 10, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Spend budget", description: "One run should exhaust the allowance.", @@ -284,7 +284,7 @@ describe("AppDatabase", () => { status: "todo", }); - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, @@ -293,7 +293,7 @@ describe("AppDatabase", () => { }); // Simulate a per-message cost entry (written by CostWriter in prod). - db.runs.recordCostEntry({ + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, @@ -303,7 +303,7 @@ describe("AppDatabase", () => { unattributed: false, }); - const outcome = db.runs.finishRun({ + const outcome = db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Budget consumed", @@ -315,17 +315,17 @@ describe("AppDatabase", () => { }); expect(outcome.budgetHardStopped).toBe(true); - expect(db.entityCrud.getAgent(agentId).status).toBe("paused"); - expect(db.entityCrud.getAgent(agentId).spentMonthlyUsd).toBe(12); + expect(db.core.entity.getAgent(agentId).status).toBe("paused"); + expect(db.core.entity.getAgent(agentId).spentMonthlyUsd).toBe(12); }); it("persists explicit run usage status", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Usage Status Company", description: "Tracks run usage status", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Usage Workspace", localPath: "/tmp/usage-status", @@ -333,7 +333,7 @@ describe("AppDatabase", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Usage Agent", role: "Engineer", @@ -346,7 +346,7 @@ describe("AppDatabase", () => { capabilities: "Run tasks", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Usage task", description: "Exercise explicit usage status.", @@ -355,9 +355,9 @@ describe("AppDatabase", () => { priority: "medium", status: "in_progress", }); - const run = db.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" }); + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "openai-codex" }); - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "failed", summary: null, @@ -369,17 +369,17 @@ describe("AppDatabase", () => { usageStatus: "error_stop", }); - expect(db.runs.getRun(run.id).usageStatus).toBe("error_stop"); - expect(db.snap.listSnapshot().runs.find((record) => record.id === run.id)?.usageStatus).toBe("error_stop"); + expect(db.core.runs.getRun(run.id).usageStatus).toBe("error_stop"); + expect(db.projection.snapshot.listSnapshot().runs.find((record) => record.id === run.id)?.usageStatus).toBe("error_stop"); }); it("activates pending hires when the corresponding approval is granted", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Hiring Company", description: "Approval-driven hiring", status: "active", }); - const managerId = db.agents.saveAgent({ + const managerId = db.core.agents.saveAgent({ companyId, name: "Manager", role: "CEO", @@ -392,7 +392,7 @@ describe("AppDatabase", () => { capabilities: "Manages", budgetMonthlyUsd: 100, }); - const pendingHireId = db.agents.saveAgent({ + const pendingHireId = db.core.agents.saveAgent({ companyId, name: "Pending Hire", role: "IC", @@ -406,7 +406,7 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 50, }); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, relatedTaskId: null, requestedByAgentId: managerId, @@ -416,22 +416,22 @@ describe("AppDatabase", () => { payloadJson: JSON.stringify({ agentId: pendingHireId }), }); - db.approvalsMod.decideApproval({ + db.core.approvals.decideApproval({ approvalId, state: "approved", decisionNote: "Approved", }); - expect(db.entityCrud.getAgent(pendingHireId).status).toBe("idle"); + expect(db.core.entity.getAgent(pendingHireId).status).toBe("idle"); }); it("detects pending-approval agents via isAgentPendingApproval", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Gate Test Co", description: "Approval gate validation", status: "active", }); - const activeAgentId = db.agents.saveAgent({ + const activeAgentId = db.core.agents.saveAgent({ companyId, name: "Active Agent", role: "CTO", @@ -444,7 +444,7 @@ describe("AppDatabase", () => { capabilities: "Ships", budgetMonthlyUsd: 100, }); - const pendingAgentId = db.agents.saveAgent({ + const pendingAgentId = db.core.agents.saveAgent({ companyId, name: "Pending Agent", role: "IC", @@ -458,13 +458,13 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 50, }); - expect(db.agents.isAgentPendingApproval(activeAgentId)).toBe(false); - expect(db.agents.isAgentPendingApproval(pendingAgentId)).toBe(true); + expect(db.core.agents.isAgentPendingApproval(activeAgentId)).toBe(false); + expect(db.core.agents.isAgentPendingApproval(pendingAgentId)).toBe(true); }); it("runs forward-only migrations and creates schema_migrations table", () => { // The migration system runs during init() — verify the tracking table exists - const snapshot = db.snap.listSnapshot(); + const snapshot = db.projection.snapshot.listSnapshot(); expect(snapshot).toBeTruthy(); // Verify we can re-init without errors (migrations are idempotent) @@ -472,17 +472,17 @@ describe("AppDatabase", () => { db = new AppDatabase(tempDir); db.init(); - const snapshot2 = db.snap.listSnapshot(); + const snapshot2 = db.projection.snapshot.listSnapshot(); expect(snapshot2).toBeTruthy(); }); it("acquires and releases workspace locks", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Lock Test Co", description: "Workspace lock test", status: "active", }); - const wsId = db.workspacesMod.saveWorkspace({ + const wsId = db.core.workspaces.saveWorkspace({ companyId, name: "Lockable WS", localPath: "/tmp/lock-test", @@ -492,33 +492,33 @@ describe("AppDatabase", () => { }); // Acquire lock for run-1 - expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-1")).toBe(true); - let ws = db.workspacesMod.getWorkspace(wsId); + expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-1")).toBe(true); + let ws = db.core.workspaces.getWorkspace(wsId); expect(ws?.state).toBe("busy"); // Same run can re-acquire (idempotent) - expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-1")).toBe(true); + expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-1")).toBe(true); // Different run cannot acquire while run-1 holds the lock - expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-2")).toBe(false); + expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-2")).toBe(false); // Release lock - db.workspacesMod.releaseWorkspaceLock(wsId, "run-1"); - ws = db.workspacesMod.getWorkspace(wsId); + db.core.workspaces.releaseWorkspaceLock(wsId, "run-1"); + ws = db.core.workspaces.getWorkspace(wsId); expect(ws?.state).toBe("ready"); // Now run-2 can acquire - expect(db.workspacesMod.acquireWorkspaceLock(wsId, "run-2")).toBe(true); - db.workspacesMod.releaseWorkspaceLock(wsId, "run-2"); + expect(db.core.workspaces.acquireWorkspaceLock(wsId, "run-2")).toBe(true); + db.core.workspaces.releaseWorkspaceLock(wsId, "run-2"); }); it("releases all workspace locks on crash recovery", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Recovery Co", description: "Lock recovery test", status: "active", }); - const wsId = db.workspacesMod.saveWorkspace({ + const wsId = db.core.workspaces.saveWorkspace({ companyId, name: "Recovery WS", localPath: "/tmp/recovery-test", @@ -527,20 +527,20 @@ describe("AppDatabase", () => { isPrimary: true, }); - db.workspacesMod.acquireWorkspaceLock(wsId, "stale-run"); - expect(db.workspacesMod.getWorkspace(wsId)?.state).toBe("busy"); + db.core.workspaces.acquireWorkspaceLock(wsId, "stale-run"); + expect(db.core.workspaces.getWorkspace(wsId)?.state).toBe("busy"); - db.workspacesMod.releaseAllWorkspaceLocks(); - expect(db.workspacesMod.getWorkspace(wsId)?.state).toBe("ready"); + db.core.workspaces.releaseAllWorkspaceLocks(); + expect(db.core.workspaces.getWorkspace(wsId)?.state).toBe("ready"); }); it("purges expired run logs based on retention setting", async () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Purge Co", description: "Log purge test", status: "active", }); - const wsId = db.workspacesMod.saveWorkspace({ + const wsId = db.core.workspaces.saveWorkspace({ companyId, name: "PurgeWS", localPath: "/tmp/purge", @@ -548,7 +548,7 @@ describe("AppDatabase", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Purge Agent", role: "IC", @@ -561,7 +561,7 @@ describe("AppDatabase", () => { capabilities: "test", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Purge task", description: "test", @@ -571,11 +571,11 @@ describe("AppDatabase", () => { status: "todo", }); - const run = db.runs.createRun({ companyId, taskId, agentId, workspaceId: wsId, connectorId: "openai-codex" }); - await db.runs.appendRunLog(run.id, "stdout", "test log line\n"); + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId: wsId, connectorId: "openai-codex" }); + await db.core.runs.appendRunLog(run.id, "stdout", "test log line\n"); // Finish the run with a very old timestamp - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "done", @@ -587,10 +587,10 @@ describe("AppDatabase", () => { }); // Log should exist before purge - expect(db.runs.readRunLog(run.id)).toContain("test log line"); + expect(db.core.runs.readRunLog(run.id)).toContain("test log line"); // Set retention to 1 day and manually backdate the run's finished_at - db.settings.updateSettings({ logRetentionDays: 1 }); + db.core.settings.updateSettings({ logRetentionDays: 1 }); // Backdate finished_at to 10 days ago const tenDaysAgo = new Date(Date.now() - 10 * 86_400_000).toISOString(); // Direct SQL update for test only @@ -598,20 +598,20 @@ describe("AppDatabase", () => { .prepare("update runs set finished_at = ? where id = ?") .run(tenDaysAgo, run.id); - const purged = db.settings.purgeExpiredRunLogs(); + const purged = db.core.settings.purgeExpiredRunLogs(); expect(purged).toBe(1); // Log should be gone after purge - expect(db.runs.readRunLog(run.id)).toBe(""); + expect(db.core.runs.readRunLog(run.id)).toBe(""); }); it("populates related_agent_id on hire approvals", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Agent Approval Co", description: "test", status: "active", }); - const managerId = db.agents.saveAgent({ + const managerId = db.core.agents.saveAgent({ companyId, name: "Manager", role: "CEO", @@ -624,7 +624,7 @@ describe("AppDatabase", () => { capabilities: "manage", budgetMonthlyUsd: 100, }); - const hireId = db.agents.saveAgent({ + const hireId = db.core.agents.saveAgent({ companyId, name: "New Hire", role: "IC", @@ -638,7 +638,7 @@ describe("AppDatabase", () => { budgetMonthlyUsd: 50, }); - const approvalId = db.approvalsMod.requestApproval({ + const approvalId = db.core.approvals.requestApproval({ companyId, requestedByAgentId: managerId, type: "hire_agent", @@ -647,12 +647,12 @@ describe("AppDatabase", () => { payloadJson: JSON.stringify({ agentId: hireId }), }); - const approval = db.approvalsMod.getApprovalRecord(approvalId); + const approval = db.core.approvals.getApprovalRecord(approvalId); // related_agent_id should be populated from payloadJson extraction expect(approval.relatedAgentId).toBe(hireId); // Explicit relatedAgentId param should also work - const approvalId2 = db.approvalsMod.requestApproval({ + const approvalId2 = db.core.approvals.requestApproval({ companyId, requestedByAgentId: managerId, relatedAgentId: hireId, @@ -661,7 +661,7 @@ describe("AppDatabase", () => { impactSummary: "test", }); - const approval2 = db.approvalsMod.getApprovalRecord(approvalId2); + const approval2 = db.core.approvals.getApprovalRecord(approvalId2); expect(approval2.relatedAgentId).toBe(hireId); }); diff --git a/tests/unit/edge-case-budget.test.ts b/tests/unit/edge-case-budget.test.ts index 92a4bb46..2611625d 100644 --- a/tests/unit/edge-case-budget.test.ts +++ b/tests/unit/edge-case-budget.test.ts @@ -20,18 +20,18 @@ import { createBudgetEnforcementService } from "../../src/main/budget-enforcemen // --------------------------------------------------------------------------- function scaffold(db: AppDatabase) { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Budget Edge Case Co", description: "Budget exhaustion mid-run edge case tests (D-05.2)", status: "active", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "gemini-cli", model: "gemini-pro", envBindingText: "", notes: "Test connector", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: "/tmp/budget-edge-test", @@ -39,7 +39,7 @@ function scaffold(db: AppDatabase) { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Worker Agent", role: "developer", @@ -52,14 +52,14 @@ function scaffold(db: AppDatabase) { capabilities: "Executes tasks", budgetMonthlyUsd: 100, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Ship feature", description: "Build budget edge case test", ownerAgentId: agentId, status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Budget Project", @@ -68,7 +68,7 @@ function scaffold(db: AppDatabase) { status: "in_progress", targetDate: null, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId, @@ -119,7 +119,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Create company budget policy: 500 cents ($5), hard stop enabled - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -128,18 +128,18 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Start Run-1 (the in-flight run) - const run1 = db.runs.createRun({ + const run1 = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run1.id); - db.runs.markRunStatus(run1.id, "running", ""); + db.core.tasks.checkoutTask(taskId, run1.id); + db.core.runs.markRunStatus(run1.id, "running", ""); // Create a second task and run that exhausts budget WHILE Run-1 is in-flight - const taskId2 = db.tasks.saveTask({ + const taskId2 = db.core.tasks.saveTask({ companyId, title: "Other Task", description: "Another task that uses up budget", @@ -148,18 +148,18 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { priority: "medium", status: "todo", }); - const run0 = db.runs.createRun({ + const run0 = db.core.runs.createRun({ companyId, taskId: taskId2, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId2, run0.id); - db.runs.markRunStatus(run0.id, "running", ""); + db.core.tasks.checkoutTask(taskId2, run0.id); + db.core.runs.markRunStatus(run0.id, "running", ""); // Complete Run-0 with $6 cost (exceeds $5 budget) - db.runs.recordCostEntry({ runId: run0.id, companyId, agentId, taskId: taskId2, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); - db.runs.finishRun({ + db.core.runs.recordCostEntry({ runId: run0.id, companyId, agentId, taskId: taskId2, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); + db.core.runs.finishRun({ runId: run0.id, status: "succeeded", summary: "Expensive run", @@ -172,8 +172,8 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { // Now complete Run-1 (the in-flight run) -- it should succeed normally // Per D-04/D-05: in-flight runs are unaffected by budget exhaustion - db.runs.recordCostEntry({ runId: run1.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.5, unattributed: false }); - db.runs.finishRun({ + db.core.runs.recordCostEntry({ runId: run1.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 0.5, unattributed: false }); + db.core.runs.finishRun({ runId: run1.id, status: "succeeded", summary: "In-flight run completed", @@ -185,7 +185,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Verify Run-1 completed successfully - const finishedRun = db.runs.getRun(run1.id); + const finishedRun = db.core.runs.getRun(run1.id); expect(finishedRun.status).toBe("succeeded"); // Verify Run-1's cost was recorded (total spend now $6.50) @@ -205,7 +205,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Create company budget policy: 500 cents ($5) - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -214,17 +214,17 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Complete a run that exceeds budget - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run.id); - db.runs.markRunStatus(run.id, "running", ""); - db.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, run.id); + db.core.runs.markRunStatus(run.id, "running", ""); + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Done", @@ -253,7 +253,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Create company budget policy with known ID - const policyId = db.budget.saveBudgetPolicy({ + const policyId = db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -262,17 +262,17 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Exhaust budget - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run.id); - db.runs.markRunStatus(run.id, "running", ""); - db.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, run.id); + db.core.runs.markRunStatus(run.id, "running", ""); + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 6.0, unattributed: false }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Done", @@ -301,7 +301,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Company-level: plenty of room (10000 cents = $100) - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -310,7 +310,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Agent-level: tight budget (100 cents = $1) - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "agent", scopeId: agentId, @@ -319,17 +319,17 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Complete a run costing $1.50 (150 cents > 100 agent limit) - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run.id); - db.runs.markRunStatus(run.id, "running", ""); - db.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 1.5, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, run.id); + db.core.runs.markRunStatus(run.id, "running", ""); + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 1.5, unattributed: false }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Done", @@ -358,7 +358,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Create policy with 300 cents ($3) budget - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -371,17 +371,17 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { expect(before.allowed).toBe(true); // Complete a run costing $4 (400 cents > 300 limit) - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run.id); - db.runs.markRunStatus(run.id, "running", ""); - db.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 4.0, unattributed: false }); - db.runs.finishRun({ + db.core.tasks.checkoutTask(taskId, run.id); + db.core.runs.markRunStatus(run.id, "running", ""); + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 4.0, unattributed: false }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Done", @@ -408,7 +408,7 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Create tight budget - db.budget.saveBudgetPolicy({ + db.core.budget.saveBudgetPolicy({ companyId, scopeType: "company", scopeId: companyId, @@ -417,19 +417,19 @@ describe("Edge Case: Budget Exhaustion Mid-Run (D-05.2)", () => { }); // Run that will exceed budget on completion - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli", }); - db.tasks.checkoutTask(taskId, run.id); - db.runs.markRunStatus(run.id, "running", ""); + db.core.tasks.checkoutTask(taskId, run.id); + db.core.runs.markRunStatus(run.id, "running", ""); // Complete with cost that exceeds budget -- cost entry is still recorded - db.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 5.0, unattributed: false }); - db.runs.finishRun({ + db.core.runs.recordCostEntry({ runId: run.id, companyId, agentId, taskId, connectorId: "gemini-cli", amountUsd: 5.0, unattributed: false }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Done", diff --git a/tests/unit/edge-case-concurrent.test.ts b/tests/unit/edge-case-concurrent.test.ts index f578193e..8efab2d6 100644 --- a/tests/unit/edge-case-concurrent.test.ts +++ b/tests/unit/edge-case-concurrent.test.ts @@ -28,7 +28,7 @@ type RawDb = { // --------------------------------------------------------------------------- function createTestCompany(db: AppDatabase): string { - return db.company.saveCompany({ + return db.core.company.saveCompany({ name: "Edge Case Company", description: "Concurrent checkout edge case tests (D-05.1)", status: "active", @@ -36,7 +36,7 @@ function createTestCompany(db: AppDatabase): string { } function createTestConnector(db: AppDatabase): void { - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "gemini-cli", model: "gemini-pro", envBindingText: "", @@ -45,7 +45,7 @@ function createTestConnector(db: AppDatabase): void { } function createTestWorkspace(db: AppDatabase, companyId: string): string { - return db.workspacesMod.saveWorkspace({ + return db.core.workspaces.saveWorkspace({ companyId, name: "Test Workspace", localPath: "/tmp/concurrent-test", @@ -56,7 +56,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string { } function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string { - return db.agents.saveAgent({ + return db.core.agents.saveAgent({ companyId, name, role: "IC", @@ -72,7 +72,7 @@ function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string } function createTestTask(db: AppDatabase, companyId: string, workspaceId: string): string { - return db.tasks.saveTask({ + return db.core.tasks.saveTask({ companyId, title: "Concurrent Test Task", description: "A task for testing concurrent checkout edge cases", @@ -110,14 +110,14 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B"); const taskId = createTestTask(db, companyId, workspaceId); - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - const { id: runIdB } = db.runs.createRun({ + const { id: runIdB } = db.core.runs.createRun({ companyId, taskId, agentId: agentB, @@ -126,15 +126,15 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { }); // Agent-A claims first - const resultA = db.tasks.checkoutTask(taskId, runIdA); + const resultA = db.core.tasks.checkoutTask(taskId, runIdA); expect(resultA).toBe(true); // Agent-B tries to claim the same task - const resultB = db.tasks.checkoutTask(taskId, runIdB); + const resultB = db.core.tasks.checkoutTask(taskId, runIdB); expect(resultB).toBe(false); // Task status must be in_progress (set by first successful checkout) - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("in_progress"); }); @@ -146,14 +146,14 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const agentB = createTestAgent(db, companyId, workspaceId, "Agent-B"); const taskId1 = createTestTask(db, companyId, workspaceId); - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId: taskId1, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - const { id: runIdB1 } = db.runs.createRun({ + const { id: runIdB1 } = db.core.runs.createRun({ companyId, taskId: taskId1, agentId: agentB, @@ -162,24 +162,24 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { }); // Agent-A wins first task - expect(db.tasks.checkoutTask(taskId1, runIdA)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId1, runIdA)).toBe(true); // Agent-B loses - expect(db.tasks.checkoutTask(taskId1, runIdB1)).toBe(false); + expect(db.core.tasks.checkoutTask(taskId1, runIdB1)).toBe(false); // Agent-B can still checkout a DIFFERENT task const taskId2 = createTestTask(db, companyId, workspaceId); - const { id: runIdB2 } = db.runs.createRun({ + const { id: runIdB2 } = db.core.runs.createRun({ companyId, taskId: taskId2, agentId: agentB, workspaceId, connectorId: "gemini-cli", }); - const resultB2 = db.tasks.checkoutTask(taskId2, runIdB2); + const resultB2 = db.core.tasks.checkoutTask(taskId2, runIdB2); expect(resultB2).toBe(true); // Verify second task is now in_progress - const task2 = db.entityCrud.getTask(taskId2); + const task2 = db.core.entity.getTask(taskId2); expect(task2.status).toBe("in_progress"); }); @@ -192,28 +192,28 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const taskId = createTestTask(db, companyId, workspaceId); // Agent-A checks out successfully - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true); // Task is now in_progress. A new run from Agent-B tries to checkout - const { id: runIdB } = db.runs.createRun({ + const { id: runIdB } = db.core.runs.createRun({ companyId, taskId, agentId: agentB, workspaceId, connectorId: "gemini-cli", }); - const result = db.tasks.checkoutTask(taskId, runIdB); + const result = db.core.tasks.checkoutTask(taskId, runIdB); expect(result).toBe(false); // Confirm status is still in_progress - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); }); it("after lock release, CAS guard still checks task status", () => { @@ -225,17 +225,17 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const taskId = createTestTask(db, companyId, workspaceId); // Agent-A checks out task - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true); // Release the lock (sets checkout_run_id = NULL) - db.tasks.releaseTaskLock(taskId, runIdA); + db.core.tasks.releaseTaskLock(taskId, runIdA); // Verify lock fields are cleared const row = (db as unknown as RawDb).db @@ -246,14 +246,14 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { // Task is still in_progress though, so a new checkout fails // (CAS checks status IN ('todo', 'backlog')) - const { id: runIdB } = db.runs.createRun({ + const { id: runIdB } = db.core.runs.createRun({ companyId, taskId, agentId: agentB, workspaceId, connectorId: "gemini-cli", }); - const result = db.tasks.checkoutTask(taskId, runIdB); + const result = db.core.tasks.checkoutTask(taskId, runIdB); expect(result).toBe(false); }); @@ -264,14 +264,14 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const agentA = createTestAgent(db, companyId, workspaceId, "Agent-A"); const taskId = createTestTask(db, companyId, workspaceId); - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true); // Backdate execution_locked_at to well past the stale timeout (db as unknown as RawDb).db @@ -300,14 +300,14 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { const taskId = createTestTask(db, companyId, workspaceId); // Agent-A checks out, then lock goes stale - const { id: runIdA } = db.runs.createRun({ + const { id: runIdA } = db.core.runs.createRun({ companyId, taskId, agentId: agentA, workspaceId, connectorId: "gemini-cli", }); - expect(db.tasks.checkoutTask(taskId, runIdA)).toBe(true); + expect(db.core.tasks.checkoutTask(taskId, runIdA)).toBe(true); // Backdate to make stale (db as unknown as RawDb).db @@ -319,19 +319,19 @@ describe("Edge Case: Concurrent Task Checkout (D-05.1)", () => { expect(recovered.length).toBe(1); // Verify task is back to "todo" - expect(db.entityCrud.getTask(taskId).status).toBe("todo"); + expect(db.core.entity.getTask(taskId).status).toBe("todo"); // Agent-B can now checkout the recovered task - const { id: runIdB } = db.runs.createRun({ + const { id: runIdB } = db.core.runs.createRun({ companyId, taskId, agentId: agentB, workspaceId, connectorId: "gemini-cli", }); - const result = db.tasks.checkoutTask(taskId, runIdB); + const result = db.core.tasks.checkoutTask(taskId, runIdB); expect(result).toBe(true); - expect(db.entityCrud.getTask(taskId).status).toBe("in_progress"); + expect(db.core.entity.getTask(taskId).status).toBe("in_progress"); }); it("STALE_LOCK_TIMEOUT_MS matches expected 15 minute value", () => { diff --git a/tests/unit/edge-case-session.test.ts b/tests/unit/edge-case-session.test.ts index 84f7eb5d..7155db63 100644 --- a/tests/unit/edge-case-session.test.ts +++ b/tests/unit/edge-case-session.test.ts @@ -21,21 +21,21 @@ import type { ConnectorId } from "@shared/types"; // --------------------------------------------------------------------------- function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Session Edge Case Co", description: "Session rotation edge case tests (D-05.3)", status: "active", }); // Always save gemini-cli (needed for most tests) - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "gemini-cli", model: "gemini-pro", envBindingText: "", notes: "Test connector - no native context management", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: "/tmp/session-edge-test", @@ -43,7 +43,7 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Worker Agent", role: "developer", @@ -56,14 +56,14 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") { capabilities: "Executes tasks", budgetMonthlyUsd: 100, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Ship feature", description: "Build session rotation edge case test", ownerAgentId: agentId, status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Session Project", @@ -72,7 +72,7 @@ function scaffold(db: AppDatabase, connectorId: ConnectorId = "gemini-cli") { status: "in_progress", targetDate: null, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId, @@ -114,7 +114,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); // Create a session - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", @@ -122,11 +122,11 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Simulate runs until we reach the maxRuns threshold (20) for (let i = 0; i < 20; i++) { - db.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`); + db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`); } // Get the updated session from DB - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.runCount).toBe(20); // matches maxRuns threshold in session-management-service @@ -140,16 +140,16 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const { companyId, agentId, taskId } = scaffold(db); const sessionService = createSessionManagementService({ db, logger: mockLogger }); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Accumulate tokens past the 500_000 maxTokens threshold in one big call - db.tasks.incrementSessionUsage(sessionId, 300000, 250000, "big-run"); + db.core.tasks.incrementSessionUsage(sessionId, 300000, 250000, "big-run"); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); const totalTokens = session!.totalInputTokens + session!.totalOutputTokens; expect(totalTokens).toBe(550000); @@ -163,7 +163,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const { companyId, agentId, taskId, workspaceId } = scaffold(db); const sessionService = createSessionManagementService({ db, logger: mockLogger }); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", @@ -171,18 +171,18 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Accumulate enough to trigger rotation for (let i = 0; i < 20; i++) { - db.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`); + db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`); } // Create a completed run for history (so generateHandoff has run data) - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli" as ConnectorId, }); - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Completed initial implementation", @@ -193,7 +193,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { unattributedCost: false, }); - const session = db.tasks.getSessionById(sessionId)!; + const session = db.core.tasks.getSessionById(sessionId)!; // generateHandoff is agent-scoped: it walks the agent's runs across // all tasks + taskless heartbeats, not a single task. const handoff = sessionService.generateHandoff(session); @@ -203,7 +203,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { expect(handoff).toContain("## Recent Run History"); // Archive the old session with handoff - db.tasks.archiveSession(sessionId, "max_runs", handoff); + db.core.tasks.archiveSession(sessionId, "max_runs", handoff); // Verify archived session has handoff stored const archivedRows = db.queryAll<{ handoffMarkdown: string | null }>( @@ -217,27 +217,27 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const { companyId, agentId, taskId } = scaffold(db); // Create initial session - const oldSessionId = db.tasks.createNewSession({ + const oldSessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Add usage to the old session - db.tasks.incrementSessionUsage(oldSessionId, 300000, 250000, "run-final"); + db.core.tasks.incrementSessionUsage(oldSessionId, 300000, 250000, "run-final"); // Archive the old session - db.tasks.archiveSession(oldSessionId, "max_tokens", "# Session Handoff\nPrevious work summary."); + db.core.tasks.archiveSession(oldSessionId, "max_tokens", "# Session Handoff\nPrevious work summary."); // Create new session - const newSessionId = db.tasks.createNewSession({ + const newSessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Get the new active session - const newSession = db.tasks.getSessionById(newSessionId); + const newSession = db.core.tasks.getSessionById(newSessionId); expect(newSession).not.toBeNull(); expect(newSession!.id).toBe(newSessionId); expect(newSession!.id).not.toBe(oldSessionId); @@ -252,7 +252,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const { companyId, agentId, taskId } = scaffold(db, "gemini-cli"); const sessionService = createSessionManagementService({ db, logger: mockLogger }); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "claude-pro", @@ -260,10 +260,10 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Accumulate way past all thresholds for (let i = 0; i < 25; i++) { - db.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); + db.core.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); } - const session = db.tasks.getSessionById(sessionId)!; + const session = db.core.tasks.getSessionById(sessionId)!; // Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly. const result = sessionService.evaluateRotation(session); expect(result.shouldRotate).toBe(true); @@ -274,7 +274,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const { companyId, agentId, taskId } = scaffold(db, "gemini-cli"); const sessionService = createSessionManagementService({ db, logger: mockLogger }); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "openai-codex", @@ -282,10 +282,10 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { // Accumulate way past all thresholds for (let i = 0; i < 25; i++) { - db.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); + db.core.tasks.incrementSessionUsage(sessionId, 30000, 20000, `run-${i}`); } - const session = db.tasks.getSessionById(sessionId)!; + const session = db.core.tasks.getSessionById(sessionId)!; // Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly. const result = sessionService.evaluateRotation(session); expect(result.shouldRotate).toBe(true); @@ -297,22 +297,22 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { const sessionService = createSessionManagementService({ db, logger: mockLogger }); // Create session and add usage - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); - db.tasks.incrementSessionUsage(sessionId, 300000, 200000, "run-1"); + db.core.tasks.incrementSessionUsage(sessionId, 300000, 200000, "run-1"); // Create a completed run for run history - const run = db.runs.createRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli" as ConnectorId, }); - db.runs.finishRun({ + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Previous work done", @@ -324,7 +324,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => { }); // Rotate the session (archives old, creates new) - const oldSession = db.tasks.getSessionById(sessionId)!; + const oldSession = db.core.tasks.getSessionById(sessionId)!; sessionService.rotateSession(oldSession); // After rotation, the handoff should be retrievable for the next run diff --git a/tests/unit/failure-escalation.test.ts b/tests/unit/failure-escalation.test.ts index ee6364f8..ad77b4c3 100644 --- a/tests/unit/failure-escalation.test.ts +++ b/tests/unit/failure-escalation.test.ts @@ -22,15 +22,17 @@ const RETRY_COOLDOWN_MS = 30_000; function createMockDeps(): EscalationDeps { return { db: { - tasks: { - getTaskRetryCount: vi.fn().mockReturnValue(0), - incrementTaskRetryCount: vi.fn(), - resetTaskRetryCount: vi.fn(), - advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_progress" }), - }, - agents: { - getChainOfCommand: vi.fn().mockReturnValue([]), - getCeoForCompany: vi.fn().mockReturnValue(null), + core: { + tasks: { + getTaskRetryCount: vi.fn().mockReturnValue(0), + incrementTaskRetryCount: vi.fn(), + resetTaskRetryCount: vi.fn(), + advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_progress" }), + }, + agents: { + getChainOfCommand: vi.fn().mockReturnValue([]), + getCeoForCompany: vi.fn().mockReturnValue(null), + }, }, }, wakeAgentIfPossible: vi.fn().mockReturnValue("wake-id"), @@ -62,15 +64,15 @@ describe("Failure Escalation Pipeline", () => { describe("Retry path (count < MAX_RETRY_ATTEMPTS)", () => { it("retries on first failure (retry_count=0)", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); const result = handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, ); expect(result.action).toBe("retry"); - expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); - expect(deps.db.tasks.advanceTaskStatus).toHaveBeenCalledWith( + expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.advanceTaskStatus).toHaveBeenCalledWith( "task-1", "todo", expect.stringContaining("attempt 1/2"), @@ -79,15 +81,15 @@ describe("Failure Escalation Pipeline", () => { }); it("retries on second failure (retry_count=1)", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(1); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(1); const result = handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, ); expect(result.action).toBe("retry"); - expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); - expect(deps.db.tasks.advanceTaskStatus).toHaveBeenCalledWith( + expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.advanceTaskStatus).toHaveBeenCalledWith( "task-1", "todo", expect.stringContaining("attempt 2/2"), @@ -96,7 +98,7 @@ describe("Failure Escalation Pipeline", () => { }); it("calls wakeAgentIfPossible via scheduleRetry callback", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, @@ -111,8 +113,8 @@ describe("Failure Escalation Pipeline", () => { describe("Escalation path (count >= MAX_RETRY_ATTEMPTS)", () => { it("escalates to chain[0] manager after max retries", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockReturnValue([ + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockReturnValue([ { id: "manager-1", name: "Manager Alpha" }, ]); @@ -121,32 +123,32 @@ describe("Failure Escalation Pipeline", () => { ); expect(result.action).toBe("escalated"); - expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("manager-1", "company-1", "report_failed"); expect(deps.notify).not.toHaveBeenCalled(); }); it("falls through to CEO when chain is empty", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockReturnValue([]); - (deps.db.agents.getCeoForCompany as ReturnType).mockReturnValue({ id: "ceo-1", name: "CEO Boss" }); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockReturnValue([]); + (deps.db.core.agents.getCeoForCompany as ReturnType).mockReturnValue({ id: "ceo-1", name: "CEO Boss" }); const result = handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, ); expect(result.action).toBe("escalated"); - expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); expect(deps.wakeAgentIfPossible).toHaveBeenCalledWith("ceo-1", "company-1", "report_failed"); expect(deps.notify).not.toHaveBeenCalled(); }); it("falls through to CEO when chain lookup throws", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockImplementation(() => { + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockImplementation(() => { throw new Error("Agent deleted"); }); - (deps.db.agents.getCeoForCompany as ReturnType).mockReturnValue({ id: "ceo-1", name: "CEO Boss" }); + (deps.db.core.agents.getCeoForCompany as ReturnType).mockReturnValue({ id: "ceo-1", name: "CEO Boss" }); const result = handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, @@ -157,9 +159,9 @@ describe("Failure Escalation Pipeline", () => { }); it("notifies user when both chain and CEO are unavailable", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockReturnValue([]); - (deps.db.agents.getCeoForCompany as ReturnType).mockReturnValue(null); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockReturnValue([]); + (deps.db.core.agents.getCeoForCompany as ReturnType).mockReturnValue(null); const result = handleFailureEscalation( deps, RUN, TASK, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, @@ -177,9 +179,9 @@ describe("Failure Escalation Pipeline", () => { }); it("uses taskId as fallback when task is null", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockReturnValue([]); - (deps.db.agents.getCeoForCompany as ReturnType).mockReturnValue(null); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockReturnValue([]); + (deps.db.core.agents.getCeoForCompany as ReturnType).mockReturnValue(null); const result = handleFailureEscalation( deps, RUN, null, "failed", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, @@ -195,19 +197,19 @@ describe("Failure Escalation Pipeline", () => { describe("Timed out runs follow same path", () => { it("retries timed_out runs the same as failed runs", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(0); const result = handleFailureEscalation( deps, RUN, TASK, "timed_out", MAX_RETRY_ATTEMPTS, RETRY_COOLDOWN_MS, scheduleRetry, ); expect(result.action).toBe("retry"); - expect(deps.db.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.incrementTaskRetryCount).toHaveBeenCalledWith("task-1"); }); it("escalates timed_out runs after max retries", () => { - (deps.db.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); - (deps.db.agents.getChainOfCommand as ReturnType).mockReturnValue([ + (deps.db.core.tasks.getTaskRetryCount as ReturnType).mockReturnValue(2); + (deps.db.core.agents.getChainOfCommand as ReturnType).mockReturnValue([ { id: "manager-1", name: "Manager Alpha" }, ]); @@ -216,18 +218,18 @@ describe("Failure Escalation Pipeline", () => { ); expect(result.action).toBe("escalated"); - expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); }); }); describe("Success resets retry count", () => { it("resets retry_count on successful completion", () => { handleSuccessReset(deps.db, "task-1"); - expect(deps.db.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); + expect(deps.db.core.tasks.resetTaskRetryCount).toHaveBeenCalledWith("task-1"); }); it("handles reset errors gracefully", () => { - (deps.db.tasks.resetTaskRetryCount as ReturnType).mockImplementation(() => { + (deps.db.core.tasks.resetTaskRetryCount as ReturnType).mockImplementation(() => { throw new Error("DB error"); }); diff --git a/tests/unit/internal-tool-selector.test.ts b/tests/unit/internal-tool-selector.test.ts index 3ced90da..11fb0bc0 100644 --- a/tests/unit/internal-tool-selector.test.ts +++ b/tests/unit/internal-tool-selector.test.ts @@ -36,20 +36,22 @@ function makeDb( approvals: Record> = {}, ): AppDatabase { return { - entityCrud: { - getAgent: vi.fn((agentId: string) => { - const found = [agent, ...reports].find((candidate) => candidate.id === agentId); - if (!found) throw new Error(`Agent not found: ${agentId}`); - return found; - }), - }, - agents: { - hasAgentPermission: vi.fn(() => false), - getDirectReports: vi.fn(() => reports), - getChainOfCommand: vi.fn(() => []), - }, - approvalsMod: { - listCompanyApprovalsByType: vi.fn((_companyId: string, type: string) => approvals[type] ?? []), + core: { + entity: { + getAgent: vi.fn((agentId: string) => { + const found = [agent, ...reports].find((candidate) => candidate.id === agentId); + if (!found) throw new Error(`Agent not found: ${agentId}`); + return found; + }), + }, + agents: { + hasAgentPermission: vi.fn(() => false), + getDirectReports: vi.fn(() => reports), + getChainOfCommand: vi.fn(() => []), + }, + approvals: { + listCompanyApprovalsByType: vi.fn((_companyId: string, type: string) => approvals[type] ?? []), + }, }, } as unknown as AppDatabase; } diff --git a/tests/unit/ipc-agent-graph-handlers.test.ts b/tests/unit/ipc-agent-graph-handlers.test.ts index 6e21705e..a66e03ea 100644 --- a/tests/unit/ipc-agent-graph-handlers.test.ts +++ b/tests/unit/ipc-agent-graph-handlers.test.ts @@ -31,20 +31,20 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => { const db = new AppDatabase(profileDir); db.init(); - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", version: "test", lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Handler Test Co", description: "Tests saveAgent model validation", status: "active", }); - db.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); - const workspaceId = db.workspacesMod.saveWorkspace({ + db.core.settings.setDisabledModels(companyId, "openai-codex", [{ id: "gpt-5.4", label: "gpt-5.4" }]); + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -52,7 +52,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -72,7 +72,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => { handlers.set(channel, handler); }, db, - ensureCompanyExists: (id) => (db.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }), + ensureCompanyExists: (id) => (db.core.settings.hasCompany(id) ? null : { ok: false, error: { code: "COMPANY_NOT_FOUND", message: "missing" } }), findInvalidReference: () => null, publishDomainChanged: vi.fn(), handleApprovalResolved: vi.fn(), diff --git a/tests/unit/ipc-core-admin-handlers.test.ts b/tests/unit/ipc-core-admin-handlers.test.ts index c4c4e343..cdb4004b 100644 --- a/tests/unit/ipc-core-admin-handlers.test.ts +++ b/tests/unit/ipc-core-admin-handlers.test.ts @@ -63,7 +63,7 @@ describe("ipc-core-admin-handlers connector saves", () => { }) as { ok: boolean; error?: { code: string; message: string } }; expect(result.ok).toBe(true); - expect(db.connectorsCrud.getConnector("anthropic").model).toBe(anthropicModel); + expect(db.core.connectors.getConnector("anthropic").model).toBe(anthropicModel); db.close(); }); diff --git a/tests/unit/knowledge-provenance.test.ts b/tests/unit/knowledge-provenance.test.ts index 043a7adb..a340aebb 100644 --- a/tests/unit/knowledge-provenance.test.ts +++ b/tests/unit/knowledge-provenance.test.ts @@ -22,13 +22,13 @@ describe("knowledge provenance", () => { const db = new AppDatabase(profileDir); db.init(); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Knowledge Provenance Co", description: "Tests sourceRunId updates on knowledge entry edits", status: "active", }); - const knowledgeId = db.knowledgeMod.saveKnowledgeEntry({ + const knowledgeId = db.core.knowledge.saveKnowledgeEntry({ companyId, category: "lesson_learned", topic: "Original lesson", @@ -37,7 +37,7 @@ describe("knowledge provenance", () => { sourceRunId: null, }); - db.knowledgeMod.saveKnowledgeEntry({ + db.core.knowledge.saveKnowledgeEntry({ id: knowledgeId, companyId, category: "lesson_learned", diff --git a/tests/unit/message-dispatch.test.ts b/tests/unit/message-dispatch.test.ts index 9db78551..c2a36691 100644 --- a/tests/unit/message-dispatch.test.ts +++ b/tests/unit/message-dispatch.test.ts @@ -19,7 +19,7 @@ afterEach(async () => { }); function createScaffold(db: AppDatabase) { - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", @@ -27,13 +27,13 @@ function createScaffold(db: AppDatabase) { lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Message Dispatch Co", description: "Tests board wake and wait-state resolution", status: "active", executionMode: "ai_cofounder", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: "/tmp/message-dispatch", @@ -41,7 +41,7 @@ function createScaffold(db: AppDatabase) { repoRef: "", isPrimary: true, }); - const ceoId = db.agents.saveAgent({ + const ceoId = db.core.agents.saveAgent({ companyId, name: "CEO", role: "CEO", @@ -54,7 +54,7 @@ function createScaffold(db: AppDatabase) { capabilities: "Lead the company", budgetMonthlyUsd: 250, }); - const ctoId = db.agents.saveAgent({ + const ctoId = db.core.agents.saveAgent({ companyId, name: "CTO", role: "CTO", @@ -68,7 +68,7 @@ function createScaffold(db: AppDatabase) { budgetMonthlyUsd: 200, department: "engineering", }); - const cmoId = db.agents.saveAgent({ + const cmoId = db.core.agents.saveAgent({ companyId, name: "CMO", role: "CMO", @@ -82,7 +82,7 @@ function createScaffold(db: AppDatabase) { budgetMonthlyUsd: 200, department: "marketing", }); - const pendingId = db.agents.saveAgent({ + const pendingId = db.core.agents.saveAgent({ companyId, name: "Pending Recruit", role: "IC", @@ -96,7 +96,7 @@ function createScaffold(db: AppDatabase) { budgetMonthlyUsd: 100, department: "engineering", }); - const terminatedId = db.agents.saveAgent({ + const terminatedId = db.core.agents.saveAgent({ companyId, name: "Former COO", role: "COO", @@ -111,7 +111,7 @@ function createScaffold(db: AppDatabase) { department: "operations", }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Review architecture plan", description: "Need board feedback in the thread.", @@ -191,9 +191,9 @@ describe("message dispatch", () => { if (!result.ok) throw new Error("Expected dispatch to succeed."); expect(new Set(result.data.wakeTargets)).toEqual(new Set([ceoId, ctoId])); - const ceoInbox = db.messages.listAgentInboxUnread(companyId, ceoId); + const ceoInbox = db.core.messages.listAgentInboxUnread(companyId, ceoId); expect(ceoInbox.map((entry) => entry.id)).toContain(result.data.id); - const ctoInbox = db.messages.listAgentInboxUnread(companyId, ctoId); + const ctoInbox = db.core.messages.listAgentInboxUnread(companyId, ctoId); expect(ctoInbox.map((entry) => entry.id)).toContain(result.data.id); db.close(); @@ -230,9 +230,9 @@ describe("message dispatch", () => { // The message is still visible to CTO's inbox — they were the // addressed recipient — but CTO is not required to answer. - const ctoInbox = db.messages.listAgentInboxUnread(companyId, ctoId); + const ctoInbox = db.core.messages.listAgentInboxUnread(companyId, ctoId); expect(ctoInbox.map((entry) => entry.id)).toContain(result.data.id); - const ceoInbox = db.messages.listAgentInboxUnread(companyId, ceoId); + const ceoInbox = db.core.messages.listAgentInboxUnread(companyId, ceoId); expect(ceoInbox.map((entry) => entry.id)).toContain(result.data.id); db.close(); @@ -282,7 +282,7 @@ describe("message dispatch", () => { }); threadIntegration.onTaskCreated(taskId, companyId); - const thread = db.threads.listThreads({ + const thread = db.core.threads.listThreads({ companyId, type: "task", relatedEntityType: "task", @@ -294,21 +294,21 @@ describe("message dispatch", () => { throw new Error("Expected task thread to exist."); } - const replyWait = db.threads.setThreadWaitState({ + const replyWait = db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_reply", relatedApprovalId: null, reason: "Reply needed", }); - const reviewWait = db.threads.setThreadWaitState({ + const reviewWait = db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_review", relatedApprovalId: null, reason: "Review needed", }); - const approvalWait = db.threads.setThreadWaitState({ + const approvalWait = db.core.threads.setThreadWaitState({ threadId: thread.id, companyId, waitingFor: "user_approval", @@ -328,7 +328,7 @@ describe("message dispatch", () => { body: "Board-wide update", }); - let unresolved = db.threads.listThreadWaitStates(companyId); + let unresolved = db.core.threads.listThreadWaitStates(companyId); expect(unresolved.some((entry) => entry.id === replyWait.id)).toBe(false); expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(true); @@ -346,7 +346,7 @@ describe("message dispatch", () => { body: "Specific reply for the thread", }); - unresolved = db.threads.listThreadWaitStates(companyId); + unresolved = db.core.threads.listThreadWaitStates(companyId); expect(unresolved.some((entry) => entry.id === reviewWait.id)).toBe(false); expect(unresolved.some((entry) => entry.id === approvalWait.id)).toBe(true); diff --git a/tests/unit/message-search.test.ts b/tests/unit/message-search.test.ts index 23353674..773096e2 100644 --- a/tests/unit/message-search.test.ts +++ b/tests/unit/message-search.test.ts @@ -26,13 +26,13 @@ describe("AppDatabase.searchMessages", () => { db = new AppDatabase(tempDir); db.init(); - companyId = db.company.saveCompany({ + companyId = db.core.company.saveCompany({ name: "Search Test Corp", description: "Company for testing message search", status: "active", }); - agentA = db.agents.saveAgent({ + agentA = db.core.agents.saveAgent({ companyId, name: "Agent Alpha", role: "Engineer", @@ -46,7 +46,7 @@ describe("AppDatabase.searchMessages", () => { budgetMonthlyUsd: 100, }); - agentB = db.agents.saveAgent({ + agentB = db.core.agents.saveAgent({ companyId, name: "Agent Beta", role: "Designer", @@ -61,7 +61,7 @@ describe("AppDatabase.searchMessages", () => { }); // Seed messages across different channels - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentA, toAgentId: agentB, @@ -71,7 +71,7 @@ describe("AppDatabase.searchMessages", () => { priority: "normal", }); - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentB, toAgentId: agentA, @@ -81,7 +81,7 @@ describe("AppDatabase.searchMessages", () => { priority: "normal", }); - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentA, channel: "company", @@ -90,7 +90,7 @@ describe("AppDatabase.searchMessages", () => { priority: "normal", }); - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentA, channel: "department", @@ -100,7 +100,7 @@ describe("AppDatabase.searchMessages", () => { priority: "urgent", }); - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentB, channel: "department", @@ -117,21 +117,21 @@ describe("AppDatabase.searchMessages", () => { }); it("returns messages where body contains the query string", () => { - const results = db.messages.searchMessages(companyId, "velocity"); + const results = db.core.messages.searchMessages(companyId, "velocity"); expect(results.length).toBe(1); expect(results[0].companyId).toBe(companyId); expect(results[0].body.toLowerCase()).toContain("velocity"); }); it("returns messages where subject contains the query string", () => { - const results = db.messages.searchMessages(companyId, "retrospective"); + const results = db.core.messages.searchMessages(companyId, "retrospective"); expect(results.length).toBe(1); expect(results[0].subject).toBe("Sprint retrospective"); }); it("escapes LIKE wildcards so % and _ match literally", () => { // Insert a message with literal % and _ characters - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentA, channel: "company", @@ -141,25 +141,25 @@ describe("AppDatabase.searchMessages", () => { }); // Search for the literal "95%" - const percentResults = db.messages.searchMessages(companyId, "95%"); + const percentResults = db.core.messages.searchMessages(companyId, "95%"); expect(percentResults.length).toBe(1); expect(percentResults[0].body).toContain("95%"); // Search for the literal "disk_io" - const underscoreResults = db.messages.searchMessages(companyId, "disk_io"); + const underscoreResults = db.core.messages.searchMessages(companyId, "disk_io"); expect(underscoreResults.length).toBe(1); expect(underscoreResults[0].body).toContain("disk_io"); }); it("scopes to a specific channel when channel param is provided", () => { - const results = db.messages.searchMessages(companyId, "approvals", { channel: "department" }); + const results = db.core.messages.searchMessages(companyId, "approvals", { channel: "department" }); expect(results.length).toBe(1); expect(results[0].channel).toBe("department"); expect(results[0].body).toContain("approvals"); }); it("scopes to channelTargetId when provided alongside channel", () => { - const results = db.messages.searchMessages(companyId, "updated", { + const results = db.core.messages.searchMessages(companyId, "updated", { channel: "department", channelTargetId: "design", }); @@ -168,14 +168,14 @@ describe("AppDatabase.searchMessages", () => { }); it("returns empty array when no matches", () => { - const results = db.messages.searchMessages(companyId, "xyznonexistentquery"); + const results = db.core.messages.searchMessages(companyId, "xyznonexistentquery"); expect(results).toEqual([]); }); it("respects the limit parameter (default 50)", () => { // Insert 60 messages for (let i = 0; i < 60; i++) { - db.messages.sendAgentMessage({ + db.core.messages.sendAgentMessage({ companyId, fromAgentId: agentA, channel: "company", @@ -186,16 +186,16 @@ describe("AppDatabase.searchMessages", () => { } // Default limit should be 50 - const defaultResults = db.messages.searchMessages(companyId, "flood test"); + const defaultResults = db.core.messages.searchMessages(companyId, "flood test"); expect(defaultResults.length).toBe(50); // Explicit limit of 10 - const limitedResults = db.messages.searchMessages(companyId, "flood test", { limit: 10 }); + const limitedResults = db.core.messages.searchMessages(companyId, "flood test", { limit: 10 }); expect(limitedResults.length).toBe(10); }); it("returns results ordered by created_at DESC (most recent first)", () => { - const results = db.messages.searchMessages(companyId, "migration"); + const results = db.core.messages.searchMessages(companyId, "migration"); expect(results.length).toBeGreaterThanOrEqual(2); for (let i = 1; i < results.length; i++) { diff --git a/tests/unit/resolve-workspace.test.ts b/tests/unit/resolve-workspace.test.ts index 660306d3..1849a90f 100644 --- a/tests/unit/resolve-workspace.test.ts +++ b/tests/unit/resolve-workspace.test.ts @@ -32,18 +32,18 @@ import { */ function seedCompany(db: AppDatabase) { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Parallel Co", description: "Regression fixture", status: "active", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", notes: "fixture", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Default", localPath: "/tmp/fixture", @@ -97,7 +97,7 @@ describe("resolveWorkspaceWithLockMode", () => { it("persisted requiresFilesystem=false stays lock-free for engineering-assigned tasks", () => { const { companyId, workspaceId } = seedCompany(db); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Engineering Lead", role: "CTO", @@ -111,7 +111,7 @@ describe("resolveWorkspaceWithLockMode", () => { capabilities: "Architecture", budgetMonthlyUsd: 100, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Review launch plan", description: "Read the plan and respond with strategic comments.", @@ -121,8 +121,8 @@ describe("resolveWorkspaceWithLockMode", () => { status: "todo", requiresFilesystem: false, }); - const task = db.entityCrud.getTask(taskId); - const agent = db.entityCrud.getAgent(agentId); + const task = db.core.entity.getTask(taskId); + const agent = db.core.entity.getAgent(agentId); const result = resolveWorkspaceWithLockMode(db, task, agent, companyId); expect(task.requiresFilesystem).toBe(false); expect(result.source).toBe("none"); @@ -132,13 +132,13 @@ describe("resolveWorkspaceWithLockMode", () => { it("requiresFilesystem + project primary workspace → exclusive on project workspace", () => { const { companyId } = seedCompany(db); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, name: "Platform", description: "", status: "active", }); - const projectWsId = db.workspacesMod.saveWorkspace({ + const projectWsId = db.core.workspaces.saveWorkspace({ companyId, name: "Project repo", localPath: "/tmp/project-ws", @@ -185,7 +185,7 @@ describe("resolveWorkspaceWithLockMode", () => { it("task.workspaceId wins over agent.workspaceId even when both are set", () => { const { companyId, workspaceId: agentWsId } = seedCompany(db); - const taskWsId = db.workspacesMod.saveWorkspace({ + const taskWsId = db.core.workspaces.saveWorkspace({ companyId, name: "Task-only", localPath: "/tmp/task-ws", @@ -205,7 +205,7 @@ describe("resolveWorkspaceWithLockMode", () => { }); it("filesystem-requiring task with no resolvable workspace → throws WorkspaceConfigurationError", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Empty Co", description: "", status: "active", @@ -221,7 +221,7 @@ describe("resolveWorkspaceWithLockMode", () => { }); it("non-coding role with no workspace bindings → none (never throws, never falls back to shared lock)", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Empty Co", description: "", status: "active", @@ -271,7 +271,7 @@ describe("resolveWorkspaceWithLockMode", () => { }); it("lockMode=none observationPath is null when company has no workspace at all", () => { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "No-workspace Co", description: "", status: "active", diff --git a/tests/unit/run-context-input-failures.test.ts b/tests/unit/run-context-input-failures.test.ts index 4692d26d..2453f345 100644 --- a/tests/unit/run-context-input-failures.test.ts +++ b/tests/unit/run-context-input-failures.test.ts @@ -28,19 +28,19 @@ function createLogger() { } function createCompanyRunFixture(db: AppDatabase, profileDir: string) { - db.connectorsCrud.updateConnectorHealth({ + db.core.connectors.updateConnectorHealth({ id: "openai-codex", status: "ready", authState: "authenticated", version: "test", lastError: null, }); - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Context Integrity Co", description: "Validates run context failures", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: profileDir, @@ -48,7 +48,7 @@ function createCompanyRunFixture(db: AppDatabase, profileDir: string) { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Runtime Engineer", role: "Engineer", @@ -61,7 +61,7 @@ function createCompanyRunFixture(db: AppDatabase, profileDir: string) { capabilities: "Ships runtime fixes", budgetMonthlyUsd: 250, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, title: "Assemble safe run context", description: "The worker must start only with a complete context.", @@ -110,7 +110,7 @@ function createHarness( runDispatch.resolveWorkspace, runDispatch.getRunEligibilityFailure, runDispatch.dispatchRunToWorker, - (filter) => db.runs.hasActiveRun(filter), + (filter) => db.core.runs.hasActiveRun(filter), ); return { enqueueWorkerRun, emitEvent, logger, taskRunner }; } @@ -145,9 +145,9 @@ describe("run context input failures", () => { throw new Error("Expected the task runner to persist a failed run."); } expect(enqueueWorkerRun).not.toHaveBeenCalled(); - const run = db.runs.getRun(result.data); - const task = db.entityCrud.getTask(taskId); - const workspace = db.workspacesMod.getWorkspace(task.workspaceId); + const run = db.core.runs.getRun(result.data); + const task = db.core.entity.getTask(taskId); + const workspace = db.core.workspaces.getWorkspace(task.workspaceId); expect(run.status).toBe("failed"); expect(run.errorMessage).toContain("run.skills.fetch_failed"); expect(task.status).toBe("todo"); @@ -172,8 +172,8 @@ describe("run context input failures", () => { throw new Error("Expected the task runner to persist a failed run."); } expect(enqueueWorkerRun).not.toHaveBeenCalled(); - const run = db.runs.getRun(result.data); - const task = db.entityCrud.getTask(taskId); + const run = db.core.runs.getRun(result.data); + const task = db.core.entity.getTask(taskId); expect(run.status).toBe("failed"); expect(run.errorMessage).toContain("run.session_handoff.load_failed"); expect(task.status).toBe("todo"); diff --git a/tests/unit/session-management.test.ts b/tests/unit/session-management.test.ts index 99d4752c..27b51f3c 100644 --- a/tests/unit/session-management.test.ts +++ b/tests/unit/session-management.test.ts @@ -21,12 +21,12 @@ import type { ConnectorId, TaskSessionRecord } from "@shared/types"; // Helpers — create a minimal company + agent + task scaffold // --------------------------------------------------------------------------- function scaffold(db: AppDatabase) { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Session Test Co", description: "Testing session management", status: "active", }); - const workspaceId = db.workspacesMod.saveWorkspace({ + const workspaceId = db.core.workspaces.saveWorkspace({ companyId, name: "Primary", localPath: "/tmp/session-test", @@ -34,7 +34,7 @@ function scaffold(db: AppDatabase) { repoRef: "", isPrimary: true, }); - const agentId = db.agents.saveAgent({ + const agentId = db.core.agents.saveAgent({ companyId, name: "Worker Agent", role: "developer", @@ -47,14 +47,14 @@ function scaffold(db: AppDatabase) { capabilities: "Writes code", budgetMonthlyUsd: 100, }); - const goalId = db.entityCrud.saveGoal({ + const goalId = db.core.entity.saveGoal({ companyId, title: "Ship feature", description: "Build session management", ownerAgentId: agentId, status: "active", }); - const projectId = db.entityCrud.saveProject({ + const projectId = db.core.entity.saveProject({ companyId, goalId, name: "Session Project", @@ -63,7 +63,7 @@ function scaffold(db: AppDatabase) { status: "in_progress", targetDate: null, }); - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId, projectId, goalId, @@ -96,19 +96,19 @@ describe("task_sessions CRUD", () => { }); it("getSessionById returns null for an unknown id", () => { - const session = db.tasks.getSessionById("nonexistent"); + const session = db.core.tasks.getSessionById("nonexistent"); expect(session).toBeNull(); }); it("getSessionById returns TaskSessionRecord when valid session exists", () => { const { companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "claude-pro", }); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.id).toBe(sessionId); expect(session!.companyId).toBe(companyId); @@ -124,23 +124,23 @@ describe("task_sessions CRUD", () => { it("incrementSessionUsage atomically updates counters", () => { const { companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "claude-pro", }); - db.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-001"); + db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-001"); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session!.runCount).toBe(1); expect(session!.totalInputTokens).toBe(1000); expect(session!.totalOutputTokens).toBe(500); expect(session!.lastRunId).toBe("run-001"); // Second increment accumulates - db.tasks.incrementSessionUsage(sessionId, 2000, 800, "run-002"); - const session2 = db.tasks.getSessionById(sessionId); + db.core.tasks.incrementSessionUsage(sessionId, 2000, 800, "run-002"); + const session2 = db.core.tasks.getSessionById(sessionId); expect(session2!.runCount).toBe(2); expect(session2!.totalInputTokens).toBe(3000); expect(session2!.totalOutputTokens).toBe(1300); @@ -149,15 +149,15 @@ describe("task_sessions CRUD", () => { it("archiveSession sets rotated_at, rotation_reason, handoff_markdown", () => { const { companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "claude-pro", }); - db.tasks.archiveSession(sessionId, "max_runs", "# Handoff\nSession reached 20 runs."); + db.core.tasks.archiveSession(sessionId, "max_runs", "# Handoff\nSession reached 20 runs."); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session!.rotatedAt).toBeTruthy(); expect(session!.rotationReason).toBe("max_runs"); expect(session!.handoffMarkdown).toBe("# Handoff\nSession reached 20 runs."); @@ -166,7 +166,7 @@ describe("task_sessions CRUD", () => { it("createNewSession returns an id; adapter type persists", () => { const { companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", @@ -175,7 +175,7 @@ describe("task_sessions CRUD", () => { expect(typeof sessionId).toBe("string"); expect(sessionId.length).toBeGreaterThan(0); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session).not.toBeNull(); expect(session!.id).toBe(sessionId); expect(session!.adapterType).toBe("gemini-cli"); @@ -185,8 +185,8 @@ describe("task_sessions CRUD", () => { const { taskId, companyId, agentId, workspaceId } = scaffold(db); // Create some runs - const run1 = db.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude-pro" }); - db.runs.finishRun({ + const run1 = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude-pro" }); + db.core.runs.finishRun({ runId: run1.id, status: "succeeded", summary: "First run done", @@ -197,8 +197,8 @@ describe("task_sessions CRUD", () => { unattributedCost: false, }); - const run2 = db.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude-pro" }); - db.runs.finishRun({ + const run2 = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "claude-pro" }); + db.core.runs.finishRun({ runId: run2.id, status: "failed", summary: "Second run failed", @@ -209,7 +209,7 @@ describe("task_sessions CRUD", () => { unattributedCost: false, }); - const runs = db.tasks.getRecentRunsForTask(taskId, 5); + const runs = db.core.tasks.getRecentRunsForTask(taskId, 5); expect(runs.length).toBe(2); // Most recent first expect(runs[0].id).toBe(run2.id); @@ -401,7 +401,7 @@ describe("session management service", () => { describe("token accumulation", () => { it("accumulateTokenUsage calls db.incrementSessionUsage with correct args", () => { const { taskId, companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "claude-pro", @@ -410,7 +410,7 @@ describe("session management service", () => { svc.accumulateTokenUsage(sessionId, 5000, 2000, "run-abc"); - const session = db.tasks.getSessionById(sessionId); + const session = db.core.tasks.getSessionById(sessionId); expect(session!.runCount).toBe(1); expect(session!.totalInputTokens).toBe(5000); expect(session!.totalOutputTokens).toBe(2000); @@ -424,15 +424,15 @@ describe("session management service", () => { describe("handoff generation", () => { it("generateHandoff produces markdown with task title and run history", () => { const { taskId, companyId, agentId, workspaceId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Create a completed run for history - const run = db.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli" as ConnectorId }); - db.runs.finishRun({ + const run = db.core.runs.createRun({ companyId, taskId, agentId, workspaceId, connectorId: "gemini-cli" as ConnectorId }); + db.core.runs.finishRun({ runId: run.id, status: "succeeded", summary: "Implemented initial logic", @@ -444,9 +444,9 @@ describe("session management service", () => { }); // Increment session usage to have stats - db.tasks.incrementSessionUsage(sessionId, 10000, 5000, run.id); + db.core.tasks.incrementSessionUsage(sessionId, 10000, 5000, run.id); - const session = db.tasks.getSessionById(sessionId)!; + const session = db.core.tasks.getSessionById(sessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); // Handoff is now agent-scoped: it walks the agent's recent runs // (across tasks + heartbeats) rather than a single task. The task @@ -468,16 +468,16 @@ describe("session management service", () => { describe("session rotation", () => { it("rotateSession archives old session, creates new, copies session_params", () => { const { taskId, companyId, agentId } = scaffold(db); - const oldSessionId = db.tasks.createNewSession({ + const oldSessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Add some usage - db.tasks.incrementSessionUsage(oldSessionId, 300000, 200000, "run-10"); + db.core.tasks.incrementSessionUsage(oldSessionId, 300000, 200000, "run-10"); - const oldSession = db.tasks.getSessionById(oldSessionId)!; + const oldSession = db.core.tasks.getSessionById(oldSessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); const newSessionId = svc.rotateSession(oldSession); @@ -493,7 +493,7 @@ describe("session management service", () => { expect(archivedRows[0].handoffMarkdown).toContain("# Session Handoff"); // New session is a distinct row with fresh counters. - const newSession = db.tasks.getSessionById(newSessionId)!; + const newSession = db.core.tasks.getSessionById(newSessionId)!; expect(newSession.id).toBe(newSessionId); expect(newSession.runCount).toBe(0); expect(newSession.totalInputTokens).toBe(0); @@ -510,13 +510,13 @@ describe("session management service", () => { const svc = createSessionManagementService({ db, logger: mockLogger }); // Create and rotate a session - const session1Id = db.tasks.createNewSession({ + const session1Id = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); - db.tasks.incrementSessionUsage(session1Id, 100000, 50000, "run-1"); - const session1 = db.tasks.getSessionById(session1Id)!; + db.core.tasks.incrementSessionUsage(session1Id, 100000, 50000, "run-1"); + const session1 = db.core.tasks.getSessionById(session1Id)!; svc.rotateSession(session1); const handoff = svc.getHandoffContext(taskId); @@ -526,7 +526,7 @@ describe("session management service", () => { it("getHandoffContext returns null when no rotated sessions exist", () => { const { taskId, companyId, agentId } = scaffold(db); - db.tasks.createNewSession({ + db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", @@ -544,16 +544,16 @@ describe("session management service", () => { describe("usage delta", () => { it("getUsageDelta returns token difference between snapshot and current totals", () => { const { taskId, companyId, agentId } = scaffold(db); - const sessionId = db.tasks.createNewSession({ + const sessionId = db.core.tasks.createNewSession({ companyId, agentId, adapterType: "gemini-cli", }); // Pre-populate some usage - db.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-1"); + db.core.tasks.incrementSessionUsage(sessionId, 1000, 500, "run-1"); - const session = db.tasks.getSessionById(sessionId)!; + const session = db.core.tasks.getSessionById(sessionId)!; const svc = createSessionManagementService({ db, logger: mockLogger }); // Snapshot current state diff --git a/tests/unit/task-quality-gates-sweep.test.ts b/tests/unit/task-quality-gates-sweep.test.ts index b101d870..28619417 100644 --- a/tests/unit/task-quality-gates-sweep.test.ts +++ b/tests/unit/task-quality-gates-sweep.test.ts @@ -14,16 +14,18 @@ describe("sweepStaleReviews", () => { function makeSweepDeps(overrides: Partial = {}): SweepStaleReviewsDeps { return { db: { - tasks: { - listReviewTasksForSweep: vi.fn().mockReturnValue([]), - advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }), - }, - agents: { - getManagerForAgent: vi.fn().mockReturnValue(null), - getChainOfCommand: vi.fn().mockReturnValue([]), - }, - approvalsMod: { - findPendingApprovalForTask: vi.fn().mockReturnValue(null), + core: { + tasks: { + listReviewTasksForSweep: vi.fn().mockReturnValue([]), + advanceTaskStatus: vi.fn().mockReturnValue({ previousStatus: "in_review" }), + }, + agents: { + getManagerForAgent: vi.fn().mockReturnValue(null), + getChainOfCommand: vi.fn().mockReturnValue([]), + }, + approvals: { + findPendingApprovalForTask: vi.fn().mockReturnValue(null), + }, }, } as unknown as AppDatabase, getCompanyExecutionMode: vi.fn().mockReturnValue("ai_ceo"), @@ -49,14 +51,14 @@ describe("sweepStaleReviews", () => { getManagerForAgent: ReturnType; getChainOfCommand: ReturnType; }; - approvalsMod: { + approvals: { findPendingApprovalForTask: ReturnType; }; }; it("escalates to manager's manager when task in_review for 35 minutes", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" }, @@ -81,7 +83,7 @@ describe("sweepStaleReviews", () => { it("does not escalate when task in_review for only 25 minutes", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Build login" }, @@ -95,12 +97,12 @@ describe("sweepStaleReviews", () => { it("notifies the human when the reviewer has no higher manager", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" }, ]); - dbMock.approvalsMod.findPendingApprovalForTask.mockReturnValueOnce(null); + dbMock.approvals.findPendingApprovalForTask.mockReturnValueOnce(null); dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "ceo-1", name: "CEO", companyId: "company-1" }); dbMock.agents.getChainOfCommand.mockReturnValueOnce([]); @@ -119,14 +121,14 @@ describe("sweepStaleReviews", () => { it("auto-approves pending approval linked to a stale review task", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" }, ]); dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" }); dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]); - dbMock.approvalsMod.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); + dbMock.approvals.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); sweepStaleReviews("company-1", deps, NOW); @@ -144,14 +146,14 @@ describe("sweepStaleReviews", () => { it("swallows resolveApprovalDecision races gracefully", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" }, ]); dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" }); dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]); - dbMock.approvalsMod.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); + dbMock.approvals.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); (deps.resolveApprovalDecision as ReturnType).mockImplementationOnce(() => { throw new Error("Approval already decided"); }); @@ -161,14 +163,14 @@ describe("sweepStaleReviews", () => { it("advances task status to done after escalation", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Build login" }, ]); dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" }); dbMock.agents.getChainOfCommand.mockReturnValueOnce([{ id: "higher-1", name: "Higher" }]); - dbMock.approvalsMod.findPendingApprovalForTask.mockReturnValueOnce(null); + dbMock.approvals.findPendingApprovalForTask.mockReturnValueOnce(null); sweepStaleReviews("company-1", deps, NOW); @@ -182,7 +184,7 @@ describe("sweepStaleReviews", () => { it("skips tasks without an assignee", () => { const deps = makeSweepDeps(); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: null, updatedAt: THIRTY_FIVE_MIN_AGO, title: "Orphan task" }, @@ -202,7 +204,7 @@ describe("sweepStaleReviews", () => { const deps = makeSweepDeps({ getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"), }); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: TWENTY_FIVE_MIN_AGO, title: "Review me" }, @@ -220,12 +222,12 @@ describe("sweepStaleReviews", () => { const deps = makeSweepDeps({ getCompanyExecutionMode: vi.fn().mockReturnValue("ai_cofounder"), }); - const dbMock = deps.db as unknown as SweepDbMock; + const dbMock = (deps.db as unknown as { core: SweepDbMock }).core; dbMock.tasks.listReviewTasksForSweep.mockReturnValueOnce([ { id: "task-1", assigneeAgentId: "agent-1", updatedAt: THIRTY_FIVE_MIN_AGO, title: "Review me" }, ]); - dbMock.approvalsMod.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); + dbMock.approvals.findPendingApprovalForTask.mockReturnValueOnce({ id: "approval-1", type: "deliverable_review" }); dbMock.agents.getManagerForAgent.mockReturnValueOnce({ id: "manager-1", name: "Manager", companyId: "company-1" }); const count = sweepStaleReviews("company-1", deps, NOW); diff --git a/tests/unit/task-quality-gates.test.ts b/tests/unit/task-quality-gates.test.ts index a585d74f..4b8609e8 100644 --- a/tests/unit/task-quality-gates.test.ts +++ b/tests/unit/task-quality-gates.test.ts @@ -151,12 +151,14 @@ describe("ACCEPTANCE_CRITERIA_KEYWORDS", () => { describe("checkCapabilityMatch", () => { function makeMockDb(agent: { name: string; capabilities: string }, skillAssignments: string[] = [], skills: Record = {}) { return { - entityCrud: { - getAgent: (_id: string) => agent, - }, - skills: { - listAgentSkillAssignments: (_id: string) => skillAssignments, - getCompanySkill: (_companyId: string, skillId: string) => skills[skillId] ?? null, + core: { + entity: { + getAgent: (_id: string) => agent, + }, + skills: { + listAgentSkillAssignments: (_id: string) => skillAssignments, + getCompanySkill: (_companyId: string, skillId: string) => skills[skillId] ?? null, + }, }, }; } diff --git a/tests/unit/task-update-transaction.test.ts b/tests/unit/task-update-transaction.test.ts index f4965f4c..341018c1 100644 --- a/tests/unit/task-update-transaction.test.ts +++ b/tests/unit/task-update-transaction.test.ts @@ -15,18 +15,18 @@ vi.mock("electron", () => ({ import { AppDatabase } from "../../src/main/database"; function seedCompany(db: AppDatabase) { - const companyId = db.company.saveCompany({ + const companyId = db.core.company.saveCompany({ name: "Review Co", description: "Task update transaction tests", status: "active", }); - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", notes: "fixture", }); - const managerId = db.agents.saveAgent({ + const managerId = db.core.agents.saveAgent({ companyId, name: "Manager", role: "VP Engineering", @@ -40,7 +40,7 @@ function seedCompany(db: AppDatabase) { capabilities: "Review", budgetMonthlyUsd: 100, }); - const workerId = db.agents.saveAgent({ + const workerId = db.core.agents.saveAgent({ companyId, name: "Worker", role: "Engineer", @@ -58,7 +58,7 @@ function seedCompany(db: AppDatabase) { } function createReviewTask(db: AppDatabase, input: { companyId: string; assigneeAgentId: string }) { - const taskId = db.tasks.saveTask({ + const taskId = db.core.tasks.saveTask({ companyId: input.companyId, title: "Ship review packet", description: "Prepare a review packet.", @@ -67,7 +67,7 @@ function createReviewTask(db: AppDatabase, input: { companyId: string; assigneeA status: "todo", requiresFilesystem: false, }); - db.tasks.setTaskRequiresUserReview(taskId, true); + db.core.tasks.setTaskRequiresUserReview(taskId, true); return taskId; } @@ -91,7 +91,7 @@ describe("updateTaskWithReviewSideEffects", () => { const taskId = createReviewTask(db, { companyId, assigneeAgentId: managerId }); expect(() => - db.tasks.updateTaskWithReviewSideEffects({ + db.core.tasks.updateTaskWithReviewSideEffects({ taskId, companyId, actorAgentId: managerId, @@ -120,18 +120,18 @@ describe("updateTaskWithReviewSideEffects", () => { }), ).toThrow(/cannot enter in_review/); - const task = db.entityCrud.getTask(taskId); + const task = db.core.entity.getTask(taskId); expect(task.status).toBe("todo"); expect(task.requiresUserReview).toBe(true); - expect(db.snap.listSnapshot().approvals).toHaveLength(0); - expect(db.activityMod.listComments(taskId)).toHaveLength(0); + expect(db.projection.snapshot.listSnapshot().approvals).toHaveLength(0); + expect(db.core.activity.listComments(taskId)).toHaveLength(0); }); it("commits review approval, flag clear, status, main row, comment, and activity together", () => { const { companyId, managerId, workerId } = seedCompany(db); const taskId = createReviewTask(db, { companyId, assigneeAgentId: workerId }); - const result = db.tasks.updateTaskWithReviewSideEffects({ + const result = db.core.tasks.updateTaskWithReviewSideEffects({ taskId, companyId, actorAgentId: managerId, @@ -159,8 +159,8 @@ describe("updateTaskWithReviewSideEffects", () => { }, }); - const task = db.entityCrud.getTask(taskId); - const approvals = db.snap.listSnapshot().approvals; + const task = db.core.entity.getTask(taskId); + const approvals = db.projection.snapshot.listSnapshot().approvals; expect(result.previousStatus).toBe("todo"); expect(result.newStatus).toBe("in_review"); expect(result.reviewRequested).toBe(true); @@ -170,7 +170,7 @@ describe("updateTaskWithReviewSideEffects", () => { expect(task.requiresUserReview).toBe(false); expect(approvals).toHaveLength(1); expect(approvals[0]?.type).toBe("deliverable_review"); - expect(db.activityMod.listComments(taskId).map((comment) => comment.body)).toEqual([ + expect(db.core.activity.listComments(taskId).map((comment) => comment.body)).toEqual([ "Deliverable submitted for board review.", "Ready for board review.", ]); diff --git a/tests/unit/wakeup-lifecycle.test.ts b/tests/unit/wakeup-lifecycle.test.ts index b9c11d55..f4788e33 100644 --- a/tests/unit/wakeup-lifecycle.test.ts +++ b/tests/unit/wakeup-lifecycle.test.ts @@ -19,7 +19,7 @@ import { AppDatabase } from "../../src/main/database"; // --------------------------------------------------------------------------- function createTestCompany(db: AppDatabase): string { - return db.company.saveCompany({ + return db.core.company.saveCompany({ name: "Wakeup Test Company", description: "Wakeup lifecycle tests", status: "active", @@ -27,7 +27,7 @@ function createTestCompany(db: AppDatabase): string { } function createTestConnector(db: AppDatabase): void { - db.connectorsCrud.saveConnector({ + db.core.connectors.saveConnector({ id: "openai-codex", model: "gpt-5", envBindingText: "", @@ -36,7 +36,7 @@ function createTestConnector(db: AppDatabase): void { } function createTestWorkspace(db: AppDatabase, companyId: string): string { - return db.workspacesMod.saveWorkspace({ + return db.core.workspaces.saveWorkspace({ companyId, name: "Wakeup Workspace", localPath: "/tmp/wakeup-workspace", @@ -47,7 +47,7 @@ function createTestWorkspace(db: AppDatabase, companyId: string): string { } function createTestAgent(db: AppDatabase, companyId: string, workspaceId: string, name = "Agent"): string { - return db.agents.saveAgent({ + return db.core.agents.saveAgent({ companyId, name, role: "IC", @@ -87,9 +87,9 @@ describe("wakeup lifecycle", () => { const workspaceId = createTestWorkspace(db, companyId); const agentId = createTestAgent(db, companyId, workspaceId); - db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "New task assigned"); + db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "New task assigned"); - const wakes = db.wakeup.listWakeupRequests(companyId); + const wakes = db.automation.wakeup.listWakeupRequests(companyId); expect(wakes.length).toBe(1); expect(wakes[0].agentId).toBe(agentId); expect(wakes[0].status).toBe("queued"); @@ -107,10 +107,10 @@ describe("wakeup lifecycle", () => { const workspaceId = createTestWorkspace(db, companyId); const agentId = createTestAgent(db, companyId, workspaceId); - db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "First task"); - db.wakeup.enqueueWakeupRequest(companyId, agentId, "task.status_changed", "Second trigger"); + db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, "task.created", "First task"); + db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, "task.status_changed", "Second trigger"); - const wakes = db.wakeup.listWakeupRequests(companyId); + const wakes = db.automation.wakeup.listWakeupRequests(companyId); // D-07: Per-agent scope -- only one wakeup row should exist expect(wakes.length).toBe(1); expect(wakes[0].coalescedCount).toBe(1); @@ -128,15 +128,15 @@ describe("wakeup lifecycle", () => { const agent1Id = createTestAgent(db, companyId, workspaceId, "Agent1"); const agent2Id = createTestAgent(db, companyId, workspaceId, "Agent2"); - db.wakeup.enqueueWakeupRequest(companyId, agent1Id, "task.created", "Agent1 task"); - db.wakeup.enqueueWakeupRequest(companyId, agent2Id, "task.created", "Agent2 task"); + db.automation.wakeup.enqueueWakeupRequest(companyId, agent1Id, "task.created", "Agent1 task"); + db.automation.wakeup.enqueueWakeupRequest(companyId, agent2Id, "task.created", "Agent2 task"); // List all for company - const allWakes = db.wakeup.listWakeupRequests(companyId); + const allWakes = db.automation.wakeup.listWakeupRequests(companyId); expect(allWakes.length).toBe(2); // Filter by agentId - const agent1Wakes = db.wakeup.listWakeupRequests(companyId, { agentId: agent1Id }); + const agent1Wakes = db.automation.wakeup.listWakeupRequests(companyId, { agentId: agent1Id }); expect(agent1Wakes.length).toBe(1); expect(agent1Wakes[0].agentId).toBe(agent1Id); });