refactor(db): group database domain modules

This commit is contained in:
ZacharyZhang-NY committed 2026-04-22 19:24:25 -04:00
1 parent e43a89096d
commit 6da0a58769
160 files changed
+2124 -1999

No files matched your search

+9 -9
View File
@@ -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)) {
@@ -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<typeof db.documentsMod.updateWorkProduct>[2] = {
const patch: Parameters<typeof db.core.documents.updateWorkProduct>[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,
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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) {
@@ -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",
@@ -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<string, unknown> : {};
@@ -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",
+3 -3
View File
@@ -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
@@ -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(
@@ -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,
+12 -12
View File
@@ -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<typeof s> => 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}` });
});
+13 -13
View File
@@ -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<string, string>();
// 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 },
+16 -16
View File
@@ -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<string, unknown> : {};
@@ -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",
+18 -18
View File
@@ -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<string, unknown>;
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<string, unknown>;
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 });
});
+26 -26
View File
@@ -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) {
+7 -7
View File
@@ -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}`,
+11 -11
View File
@@ -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 });
});
+14 -14
View File
@@ -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;
+3 -3
View File
@@ -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);
+17 -17
View File
@@ -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<string, unknown>> } | 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",
+13 -13
View File
@@ -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<string, unknown>;
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<string, number> = {};
for (const t of tasks) {
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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}`,
@@ -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<K extends "notify_agent" | "send_message">({
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");
}
@@ -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",
+2 -2
View File
@@ -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;
+11 -11
View File
@@ -42,9 +42,9 @@ export function handleTaskStatusChange(
const { db, wakeAgentIfPossible } = deps;
const { dispatchAutomation } = callbacks;
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
let task: ReturnType<typeof db.core.entity.getTask> | 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<typeof db.entityCrud.getTask> | null = null;
let task: ReturnType<typeof db.core.entity.getTask> | 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");
}
@@ -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<RunCompletionDeps["db"]["entityCrud"]["getAgent"]>,
agent: ReturnType<RunCompletionDeps["db"]["core"]["entity"]["getAgent"]>,
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<typeof db.entityCrud.getTask> | null = null;
let task: ReturnType<typeof db.core.entity.getTask> | 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,
+14 -14
View File
@@ -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<void> {
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",
+4 -4
View File
@@ -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({
@@ -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}`,
);
+2 -2
View File
@@ -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<typeof deps.db.settings.belongsToCompany>[0], id, companyId),
deps.db.core.settings.belongsToCompany(table as Parameters<typeof deps.db.core.settings.belongsToCompany>[0], id, companyId),
}),
notificationService,
});
@@ -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)) {
@@ -15,9 +15,9 @@ export interface RecoverScheduledPostsDeps {
export function recoverScheduledPosts(deps: RecoverScheduledPostsDeps): Set<ReturnType<typeof setTimeout>> {
const timers = new Set<ReturnType<typeof setTimeout>>();
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 {
@@ -14,7 +14,7 @@ export async function runStartupHealthChecks(
logger: Logger,
): Promise<void> {
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)),
);
+2 -2
View File
@@ -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();
+10 -10
View File
@@ -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<typeof db.approvalsMod.getApprovalRecord> | null {
function getApprovalOrNull(approvalId: string): ReturnType<typeof db.core.approvals.getApprovalRecord> | 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(),
+15 -15
View File
@@ -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,
+6 -6
View File
@@ -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;
+8 -8
View File
@@ -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",
+6 -4
View File
@@ -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),
};
}
+6 -6
View File
@@ -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,
+125 -92
View File
@@ -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<typeof buildThreadMethods>;
company: ReturnType<typeof buildCompanyMethods>;
agents: ReturnType<typeof buildAgentMethods>;
tasks: ReturnType<typeof buildTaskMethods>;
messages: ReturnType<typeof buildMessageMethods>;
runs: ReturnType<typeof buildRunMethods>;
settings: ReturnType<typeof buildSettingsMethods>;
connectors: ReturnType<typeof buildConnectorMethods>;
workspaces: ReturnType<typeof buildWorkspaceMethods>;
approvals: ReturnType<typeof buildApprovalMethods>;
social: ReturnType<typeof buildSocialMethods>;
activity: ReturnType<typeof buildActivityMethods>;
skills: ReturnType<typeof buildSkillMethods>;
budget: ReturnType<typeof buildBudgetMethods>;
entity: ReturnType<typeof buildEntityCrudMethods>;
documents: ReturnType<typeof buildDocumentMethods>;
knowledge: ReturnType<typeof buildKnowledgeMethods>;
backup: ReturnType<typeof buildBackupMethods>;
credentials: ReturnType<typeof buildCredentialMethods>;
modelCapabilities: ReturnType<typeof buildModelCapabilityMethods>;
}
export interface AppDatabaseAutomationModules {
rules: ReturnType<typeof buildAutomationMethods>;
wakeup: ReturnType<typeof buildWakeupMethods>;
}
export interface AppDatabaseProjectionModules {
snapshot: ReturnType<typeof buildSnapshotMethods>;
sessionEntries: ReturnType<typeof buildSessionEntryMethods>;
inbox: ReturnType<typeof buildInboxProjectionMethods>;
documentLifecycleEvents: ReturnType<typeof buildDocumentLifecycleEventMethods>;
}
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<typeof buildThreadMethods>;
public readonly company: ReturnType<typeof buildCompanyMethods>;
public readonly agents: ReturnType<typeof buildAgentMethods>;
public readonly tasks: ReturnType<typeof buildTaskMethods>;
public readonly messages: ReturnType<typeof buildMessageMethods>;
public readonly runs: ReturnType<typeof buildRunMethods>;
public readonly snap: ReturnType<typeof buildSnapshotMethods>;
public readonly settings: ReturnType<typeof buildSettingsMethods>;
public readonly connectorsCrud: ReturnType<typeof buildConnectorMethods>;
public readonly workspacesMod: ReturnType<typeof buildWorkspaceMethods>;
public readonly approvalsMod: ReturnType<typeof buildApprovalMethods>;
public readonly social: ReturnType<typeof buildSocialMethods>;
public readonly activityMod: ReturnType<typeof buildActivityMethods>;
public readonly skills: ReturnType<typeof buildSkillMethods>;
public readonly budget: ReturnType<typeof buildBudgetMethods>;
public readonly entityCrud: ReturnType<typeof buildEntityCrudMethods>;
public readonly documentsMod: ReturnType<typeof buildDocumentMethods>;
public readonly automationMod: ReturnType<typeof buildAutomationMethods>;
public readonly knowledgeMod: ReturnType<typeof buildKnowledgeMethods>;
public readonly wakeup: ReturnType<typeof buildWakeupMethods>;
public readonly backupMod: ReturnType<typeof buildBackupMethods>;
public readonly sessionEntries: ReturnType<typeof buildSessionEntryMethods>;
public readonly inboxProjections: ReturnType<typeof buildInboxProjectionMethods>;
public readonly documentLifecycleEvents: ReturnType<typeof buildDocumentLifecycleEventMethods>;
public readonly credentialsTable: ReturnType<typeof buildCredentialMethods>;
public readonly modelCapabilities: ReturnType<typeof buildModelCapabilityMethods>;
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 = <T>(sql: string, ...params: SQLInputValue[]) => this.readAll<T>(sql, ...params);
const tx = <T>(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 = <T>(sql: string, ...params: SQLInputValue[]) => this.readAll<T>(sql, ...params);
const tx = <T>(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 {
+15 -15
View File
@@ -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<AppDatabaseBase["entityCrud"]["createHireRequest"]>[0]) {
return this.entityCrud.createHireRequest(input, {
saveAgent: (agentInput) => this.agents.saveAgent(agentInput),
requestApproval: (approvalInput) => this.approvalsMod.requestApproval(approvalInput),
createHireRequest(input: Parameters<AppDatabaseBase["core"]["entity"]["createHireRequest"]>[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),
});
}
+9 -9
View File
@@ -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<string, unknown>) : {};
} 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)}`,
+10 -10
View File
@@ -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(
+9 -9
View File
@@ -27,12 +27,12 @@ const REVIEW_WAIT_STATE_TYPES = new Set(["user_reply", "user_review", "user_appr
function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: string): Set<string> {
const agentIds = new Set<string>();
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<string>): 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);
}
}
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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",
+5 -5
View File
@@ -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,
+10 -10
View File
@@ -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);
+9 -9
View File
@@ -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,
+5 -5
View File
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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);
});
+15 -15
View File
@@ -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);
});
+17 -17
View File
@@ -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",
+20 -20
View File
@@ -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);
+15 -15
View File
@@ -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) {
+14 -14
View File
@@ -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`,
+4 -4
View File
@@ -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);
});
+2 -2
View File
@@ -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));
});
}
+5 -5
View File
@@ -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);
});
}
+2 -2
View File
@@ -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),
});
}
+17 -17
View File
@@ -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<string>();
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<NormalizedAgentMessageInput, "companyId" | "fromAgentId" | "body">,
): 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");
+13 -13
View File
@@ -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",
+36 -36
View File
@@ -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<AppDatabase["entityCrud"]["getTask"]> | null = null;
let task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | 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;
}
+1 -1
View File
@@ -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);
+5 -5
View File
@@ -32,7 +32,7 @@ const SUBJECT_PREVIEW_LEN = 80;
export function resolveInitialUserTurn(
params: {
trigger: string | null;
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | 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
@@ -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");
+3 -3
View File
@@ -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}).`,
+7 -7
View File
@@ -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,
+11 -11
View File
@@ -51,8 +51,8 @@ export function createHeartbeatScheduler(
lastTickAt: string,
) {
try {
const existing = db.agents.getRuntimeState(agentId) as Record<string, unknown>;
db.agents.setRuntimeState(agentId, companyId, {
const existing = db.core.agents.getRuntimeState(agentId) as Record<string, unknown>;
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<string, unknown>;
const existing = db.core.agents.getRuntimeState(agentId) as Record<string, unknown>;
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);
@@ -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)`);
}
@@ -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) {
@@ -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();
+23 -23
View File
@@ -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",
+23 -23
View File
@@ -20,13 +20,13 @@ import { logAppError } from "../errors/index.js";
export interface RunDispatchParams {
runId: string;
companyId: string;
agent: ReturnType<AppDatabase["entityCrud"]["getAgent"]>;
agent: ReturnType<AppDatabase["core"]["entity"]["getAgent"]>;
// `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<AppDatabase["entityCrud"]["getTask"]> | null;
connector: ReturnType<AppDatabase["connectorsCrud"]["getConnector"]>;
workspace: ReturnType<AppDatabase["workspacesMod"]["getWorkspace"]>;
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null;
connector: ReturnType<AppDatabase["core"]["connectors"]["getConnector"]>;
workspace: ReturnType<AppDatabase["core"]["workspaces"]["getWorkspace"]>;
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<AppDatabase["snap"]["listSnapshot"]>;
snapshot: ReturnType<AppDatabase["projection"]["snapshot"]["listSnapshot"]>;
trigger: string;
/**
* Model id resolved by `pickModelForAgent` at dispatch time. When the
@@ -75,7 +75,7 @@ function assembleApiEnv(
trigger: string,
deps: Pick<TurnContextDeps, "getApiPort" | "createRunToken">,
) {
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<AppDatabase["tasks"]["listAgentTasks"]>,
reviewTasks: ReturnType<AppDatabase["tasks"]["listAgentTasks"]>,
assignedTasks: ReturnType<AppDatabase["core"]["tasks"]["listAgentTasks"]>,
reviewTasks: ReturnType<AppDatabase["core"]["tasks"]["listAgentTasks"]>,
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<string, unknown>;
@@ -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,
};
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -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 });
@@ -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);
+1 -1
View File
@@ -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) {
@@ -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) {
+4 -4
View File
@@ -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;
+3 -3
View File
@@ -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);
}
+8 -8
View File
@@ -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<SkillRecord> {
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<DesktopResult<void>> {
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<void> {
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<void> {
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<DesktopResult<SkillUpdateStatus>> {
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") {
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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,
+5 -5
View File
@@ -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(
+16 -14
View File
@@ -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));
+39 -39
View File
@@ -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;
+11 -11
View File
@@ -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;
+1 -1
View File
@@ -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,
+4 -4
View File
@@ -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);
}
}
}
+14 -14
View File
@@ -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<string>();
const affectedCompanyIds = new Map<string, string>();
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);
}
}
}
+5 -5
View File
@@ -111,14 +111,14 @@ export interface WorkerServiceDependencies {
dispatchRunToWorker: (params: {
runId: string;
companyId: string;
agent: ReturnType<AppDatabase["entityCrud"]["getAgent"]>;
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
connector: ReturnType<AppDatabase["connectorsCrud"]["getConnector"]>;
workspace: ReturnType<AppDatabase["workspacesMod"]["getWorkspace"]>;
agent: ReturnType<AppDatabase["core"]["entity"]["getAgent"]>;
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null;
connector: ReturnType<AppDatabase["core"]["connectors"]["getConnector"]>;
workspace: ReturnType<AppDatabase["core"]["workspaces"]["getWorkspace"]>;
workspaceMode: "exclusive" | "shared" | "none";
companyName: string;
companyDescription: string;
snapshot: ReturnType<AppDatabase["snap"]["listSnapshot"]>;
snapshot: ReturnType<AppDatabase["projection"]["snapshot"]["listSnapshot"]>;
trigger: string;
}) => void;
processRunCompletion: (run: RunRecord, event: z.infer<typeof workerMessageSchema> & { type: "run-finished" }) => void;
@@ -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;
@@ -7,13 +7,13 @@ import { isUsageStatusUnattributed } from "../../run-usage-status";
import { decideRunAttempt, runAttemptKey } from "../../run-attempt-policy";
type RunFinishedEvent = Extract<WorkerMessageEvent, { type: "run-finished" }>;
type RunRecord = ReturnType<HandlerDeps["db"]["runs"]["getRun"]>;
type TaskRecord = ReturnType<HandlerDeps["db"]["entityCrud"]["getTask"]>;
type RunRecord = ReturnType<HandlerDeps["db"]["core"]["runs"]["getRun"]>;
type TaskRecord = ReturnType<HandlerDeps["db"]["core"]["entity"]["getTask"]>;
export interface RunFinishedContext {
run: RunRecord;
task: TaskRecord | null;
runOutcome: ReturnType<HandlerDeps["db"]["runs"]["finishRun"]>;
runOutcome: ReturnType<HandlerDeps["db"]["core"]["runs"]["finishRun"]>;
}
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<HandlerDeps["db"]["entityCrud"]["getAgent"]>;
let agentRecord: ReturnType<HandlerDeps["db"]["core"]["entity"]["getAgent"]>;
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<HandlerDeps["db"]["entityCrud"]["getAgent"]> | null = null;
let updatedAgent: ReturnType<HandlerDeps["db"]["core"]["entity"]["getAgent"]> | 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;
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff. Show more