Codemod db pass-through calls to domain modules: tasks + agents (C1 part 1/3)

Rewrite ~525 call sites from `db.<method>()` to `db.<module>.<method>()`
for tasks and agents domains (including `getAgent`/`findAgent`/`getTask`
which live on the `entityCrud` field). Delete the corresponding
pass-through methods on AppDatabaseBase. Structural types that narrowed
`db` to a bag of flat methods (CapabilityMatchDb, EscalationDeps,
SweepStaleReviewsDeps) are reshaped to match the new namespace. Internal
callback references inside database.ts/database-base.ts now invoke the
owning module directly (e.g. `this.agents.saveAgent`) rather than the
deleted pass-through.

The codemod lives at scripts/codemod/remove-appdatabase-passthrough.mjs
and filters by owning module + optional explicit method list. It is
idempotent and does not re-rewrite already-namespaced calls.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 13:17:01 -04:00
1 parent 411294afa6
commit 5e9b7acbc6
95 files changed
+1123 -793

No files matched your search

+8 -8
View File
@@ -6,7 +6,7 @@ function safeGetAgent(db: AppDatabase, agentId: string | null | undefined) {
return null;
}
try {
return db.getAgent(agentId);
return db.entityCrud.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.hasAgentPermission(agentId, "organization_manage");
return db.agents.hasAgentPermission(agentId, "organization_manage");
}
function managesAgent(db: AppDatabase, managerId: string, targetAgentId: string): boolean {
if (managerId === targetAgentId) {
return true;
}
return db.getChainOfCommand(targetAgentId).some((agent) => agent.id === managerId);
return db.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.getDirectReports(agentId, companyId).length > 0) {
if (db.agents.getDirectReports(agentId, companyId).length > 0) {
return true;
}
return hasManagementAuthority(agent);
@@ -128,9 +128,9 @@ function getDepartmentLeadCandidateId(
.filter((candidate) => candidate.id !== excludeAgentId)
.map((candidate) => ({
id: candidate.id,
hasReports: db.getDirectReports(candidate.id, companyId).length > 0,
hasReports: db.agents.getDirectReports(candidate.id, companyId).length > 0,
leadershipScore: hasManagementAuthority(candidate) ? 1 : 0,
chainDepth: db.getChainOfCommand(candidate.id).length,
chainDepth: db.agents.getChainOfCommand(candidate.id).length,
createdAt: candidate.createdAt,
}))
.filter((candidate) => candidate.hasReports || candidate.leadershipScore > 0)
@@ -161,7 +161,7 @@ export function resolveHireReportsTo(
}
if (!department) {
return requester?.id ?? db.getCeoForCompany(companyId)?.id ?? null;
return requester?.id ?? db.agents.getCeoForCompany(companyId)?.id ?? null;
}
if (requester && (requester.department === department || !requester.reportsTo)) {
@@ -177,7 +177,7 @@ export function resolveHireReportsTo(
return requester.id;
}
return db.getCeoForCompany(companyId)?.id ?? null;
return db.agents.getCeoForCompany(companyId)?.id ?? null;
}
function normalizeComparableText(value: string): string {
+2 -2
View File
@@ -266,7 +266,7 @@ export class RuntimeBridge {
*/
private resolveAgentName(agentId: string): string {
try {
const name = this.deps.db.findAgentName(agentId);
const name = this.deps.db.agents.findAgentName(agentId);
if (name && name.length > 0) return name;
} catch (error) {
this.deps.logger.warn(
@@ -384,7 +384,7 @@ export class RuntimeBridge {
) {
if (envelope.taskId) {
try {
const task = this.deps.db.getTask(envelope.taskId);
const task = this.deps.db.entityCrud.getTask(envelope.taskId);
return ensureTaskThread(this.deps.db, envelope.taskId, envelope.companyId, task.title);
} catch (error) {
this.deps.logger.warn(
+5 -5
View File
@@ -12,7 +12,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
const targetAgentId = requireParam(params, "agentId");
const targetAgent = (() => {
try {
return db.getAgent(targetAgentId);
return db.entityCrud.getAgent(targetAgentId);
} catch (err) {
log.warn(`[api/agent-control] getAgent failed for ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
return null;
@@ -52,7 +52,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
const targetAgentId = requireParam(params, "agentId");
let targetAgent;
try {
targetAgent = db.getAgent(targetAgentId);
targetAgent = db.entityCrud.getAgent(targetAgentId);
} catch (err) {
log.warn(`[api/agent-control] getAgent failed for heartbeat ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
targetAgent = null;
@@ -86,7 +86,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
const agentId = requireParam(params, "agentId");
let agent;
try {
agent = db.getAgent(agentId);
agent = db.entityCrud.getAgent(agentId);
} catch (error) {
log.warn(`[api/agent-skills:GET] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
agent = null;
@@ -118,7 +118,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
}
let agent;
try {
agent = db.getAgent(agentId);
agent = db.entityCrud.getAgent(agentId);
} catch (error) {
log.warn(`[api/agent-skills:POST] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
agent = null;
@@ -142,7 +142,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
const skillId = requireParam(params, "skillId");
let agent;
try {
agent = db.getAgent(agentId);
agent = db.entityCrud.getAgent(agentId);
} catch (error) {
log.warn(`[api/agent-skills:DELETE] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
agent = null;
+6 -6
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.getAgent(token.agentId);
const agent = db.entityCrud.getAgent(token.agentId);
if (!agent || agent.companyId !== token.companyId) {
server.json(res, 404, { error: "Agent not found" });
return;
}
const directReports = db.listDirectReportsForStandup(token.companyId, token.agentId);
const directReports = db.agents.listDirectReportsForStandup(token.companyId, token.agentId);
const myTasks = db.listAssigneeTasksForStandup(token.companyId, token.agentId);
const myTasks = db.tasks.listAssigneeTasksForStandup(token.companyId, token.agentId);
const reportTasks = db.listTasksForAssignees(
const reportTasks = db.tasks.listTasksForAssignees(
token.companyId,
directReports.map((r) => r.id),
);
const reportStatusRows = directReports.length > 0
? db.listReportStatusesForStandup(token.companyId, token.agentId)
? db.agents.listReportStatusesForStandup(token.companyId, token.agentId)
: [];
const tasksAwaitingMyReview = reportTasks.filter((t) => t.status === "in_review");
@@ -46,7 +46,7 @@ export function registerAgentStandupRoute(server: AgentApiServer): void {
const unreadInbox = db.listAgentInboxUnread(token.companyId, token.agentId);
const senderIds = new Set(unreadInbox.map((m) => m.fromAgentId));
const senderNames = senderIds.size > 0
? new Map(db.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name]))
? new Map(db.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
+11 -11
View File
@@ -17,9 +17,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.getAgent(token.agentId);
const chainOfCommand = db.getChainOfCommand(token.agentId);
const directReports = db.getDirectReports(token.agentId, token.companyId);
const agent = db.entityCrud.getAgent(token.agentId);
const chainOfCommand = db.agents.getChainOfCommand(token.agentId);
const directReports = db.agents.getDirectReports(token.agentId, token.companyId);
server.json(res, 200, { agent, chainOfCommand, directReports });
});
@@ -40,7 +40,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.addRoute("GET", "/api/agents/:agentId/metrics", (_req, res, params, token) => {
const agentId = requireParam(params, "agentId");
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (!agent || agent.companyId !== token.companyId) {
server.json(res, 404, { error: "Agent not found" });
return;
@@ -57,7 +57,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.addRoute("GET", "/api/agents/:agentId", (_req, res, params, token) => {
let agent;
try {
agent = db.getAgent(requireParam(params, "agentId"));
agent = db.entityCrud.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" });
@@ -67,12 +67,12 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.json(res, 404, { error: "Agent not found" });
return;
}
const directReports = db.getDirectReports(agent.id, token.companyId);
const directReports = db.agents.getDirectReports(agent.id, token.companyId);
const reportsTo = agent.reportsTo;
const manager = reportsTo
? (() => {
try {
return db.getAgent(reportsTo);
return db.entityCrud.getAgent(reportsTo);
} catch (error) {
log.warn(`[api/agents] manager lookup failed for ${reportsTo}: ${error instanceof Error ? error.message : String(error)}`);
return null;
@@ -91,7 +91,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.json(res, 403, { error: "Company mismatch" });
return;
}
const agents = db.listAgentsForApi(params.companyId);
const agents = db.agents.listAgentsForApi(params.companyId);
server.json(res, 200, agents);
});
@@ -157,7 +157,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
resolvedReportsTo &&
resolvedReportsTo !== token.agentId &&
!canManageOrganization(db, companyId, token.agentId) &&
!db.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId)
!db.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;
@@ -181,7 +181,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
}
// Prevent duplicate agents: reject if a non-terminated agent with the same name already exists
const existingAgents = db.listExistingAgentsForHireCheck(companyId);
const existingAgents = db.agents.listExistingAgentsForHireCheck(companyId);
const duplicateByName = existingAgents.find(
(a) => a.name.toLowerCase() === input.name.toLowerCase(),
);
@@ -208,7 +208,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
const managerLabel = resolvedReportsTo
? (() => {
try {
return db.getAgent(resolvedReportsTo).name;
return db.entityCrud.getAgent(resolvedReportsTo).name;
} catch (err) {
log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
return resolvedReportsTo;
+1 -1
View File
@@ -183,7 +183,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
body: commentBody,
});
// Wake mentioned agents in comment
const mentionedIds = db.findMentionedAgentIds(approval.companyId, commentBody);
const mentionedIds = db.agents.findMentionedAgentIds(approval.companyId, commentBody);
for (const mentionedId of mentionedIds) {
if (mentionedId !== token.agentId) {
onWakeAgent(mentionedId, approval.companyId, "approval_comment_mentioned");
+5 -5
View File
@@ -228,7 +228,7 @@ export function registerFileRoutes(server: AgentApiServer) {
server.addRoute("GET", "/api/issues/:issueId/work-products", (_req, res, params, token) => {
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
const products = db.listWorkProductsForTask(task.id, task.companyId);
server.json(res, 200, products);
@@ -240,7 +240,7 @@ export function registerFileRoutes(server: AgentApiServer) {
server.addRoute("POST", "/api/issues/:issueId/work-products", (_req, res, params, token, body) => {
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.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; }
@@ -294,7 +294,7 @@ export function registerFileRoutes(server: AgentApiServer) {
server.addRoute("GET", "/api/issues/:issueId/documents", (_req, res, params, token) => {
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
const links = db.listDocumentsForTask(task.id, task.companyId);
server.json(res, 200, links);
@@ -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.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.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) {
@@ -329,7 +329,7 @@ export function registerFileRoutes(server: AgentApiServer) {
server.addRoute("DELETE", "/api/issues/:issueId/documents/:key", (_req, res, params, token) => {
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.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.unlinkDocumentFromTask(task.id, key, task.companyId);
+1 -1
View File
@@ -133,7 +133,7 @@ export function registerMeetingRoutes(server: AgentApiServer) {
);
continue;
}
db.saveTask({
db.tasks.saveTask({
companyId: token.companyId,
title: item.title,
description: item.description ?? `Action item from meeting: ${data.title ?? existing.title}`,
+5 -5
View File
@@ -20,12 +20,12 @@ export function registerMessageRoutes(server: AgentApiServer) {
return;
}
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) {
server.json(res, 404, { error: "Task not found" });
return;
}
const agent = db.getAgent(token.agentId);
const agent = db.entityCrud.getAgent(token.agentId);
const comment = db.addComment({
companyId: task.companyId,
taskId: task.id,
@@ -44,14 +44,14 @@ export function registerMessageRoutes(server: AgentApiServer) {
wokenForComment.add(task.assigneeAgentId);
}
const chain = db.getChainOfCommand(token.agentId);
const chain = db.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.findMentionedAgentIds(task.companyId, input.body);
const mentionedIds = db.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.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) {
server.json(res, 404, { error: "Task not found" });
return;
+20 -20
View File
@@ -38,17 +38,17 @@ export function registerTaskRoutes(server: AgentApiServer) {
const statuses = statusFilter ? statusFilter.split(",") : ["todo", "in_progress", "blocked"];
if (assigneeAgentId) {
const tasks = db.listAgentTasks(assigneeAgentId, companyId, statuses);
const tasks = db.tasks.listAgentTasks(assigneeAgentId, companyId, statuses);
server.json(res, 200, tasks);
} else {
const tasks = db.listTasksByStatuses(companyId, statuses);
const tasks = db.tasks.listTasksByStatuses(companyId, statuses);
server.json(res, 200, tasks);
}
});
server.addRoute("GET", "/api/issues/:issueId", (_req, res, params, token) => {
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) {
server.json(res, 404, { error: "Task not found" });
return;
@@ -73,7 +73,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
}
let task;
try {
task = db.getTask(requireParam(params, "issueId"));
task = db.entityCrud.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" });
@@ -88,7 +88,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
return;
}
const previousStatus = task.status;
const success = db.checkoutTask(task.id, token.runId);
const success = db.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;
@@ -108,7 +108,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
const update = parsedUpdate.data;
try {
const task = db.getTask(requireParam(params, "issueId"));
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
if (task.companyId !== token.companyId) {
server.json(res, 404, { error: "Task not found" });
return;
@@ -134,7 +134,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
taskId: task.id,
limit: 10,
});
const reviewerAgent = db.getAgent(token.agentId);
const reviewerAgent = db.entityCrud.getAgent(token.agentId);
const docTitles = docs.map((d) => d.title).join(", ");
db.requestApproval({
companyId: task.companyId,
@@ -145,11 +145,11 @@ export function registerTaskRoutes(server: AgentApiServer) {
impactSummary: `${reviewerAgent.name} reviewed and approved. Awaiting your final review.`.slice(0, 400),
payloadJson: JSON.stringify({ documentIds: docs.map((d) => d.id), documents: docs }),
});
db.setTaskRequiresUserReview(task.id, false);
db.tasks.setTaskRequiresUserReview(task.id, false);
if (task.assigneeAgentId) {
const prodAgent = db.getAgent(task.assigneeAgentId);
const prodAgent = db.entityCrud.getAgent(task.assigneeAgentId);
if (prodAgent.heartbeatEnabled) {
db.setHeartbeat(task.assigneeAgentId, false, prodAgent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(task.assigneeAgentId, false, prodAgent.heartbeatIntervalSec || 120);
}
}
db.addComment({ companyId: task.companyId, taskId: task.id, authorAgentId: null, authorName: "System", body: "Deliverable submitted for board review." });
@@ -169,7 +169,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
// advanceTaskStatus checks previousStatus !== newStatus internally,
// so it must run before saveTask writes the new status.
if (update.status && update.status !== task.status) {
db.advanceTaskStatus(task.id, update.status);
db.tasks.advanceTaskStatus(task.id, update.status);
}
const input: Record<string, unknown> = {
@@ -185,10 +185,10 @@ export function registerTaskRoutes(server: AgentApiServer) {
assigneeAgentId: newAssignee,
workspaceId: task.workspaceId,
};
db.saveTask(input as Parameters<typeof db.saveTask>[0]);
db.tasks.saveTask(input as Parameters<typeof db.tasks.saveTask>[0]);
if (update.comment) {
const agent = db.getAgent(token.agentId);
const agent = db.entityCrud.getAgent(token.agentId);
db.addComment({
companyId: task.companyId,
taskId: task.id,
@@ -219,7 +219,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
if (update.status && ["done", "in_review"].includes(update.status)) {
if (task.parentId) {
try {
const parentTask = db.getTask(task.parentId);
const parentTask = db.entityCrud.getTask(task.parentId);
if (parentTask.assigneeAgentId && parentTask.assigneeAgentId !== token.agentId) {
onWakeAgent(parentTask.assigneeAgentId, task.companyId, "subtask_completed");
}
@@ -233,7 +233,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.getChainOfCommand(token.agentId);
const chain = db.agents.getChainOfCommand(token.agentId);
if (chain.length > 0) {
onWakeAgent(chain[0].id, task.companyId, "task_review_requested");
}
@@ -241,7 +241,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
}
if (update.status === "blocked") {
const chain = db.getChainOfCommand(token.agentId);
const chain = db.agents.getChainOfCommand(token.agentId);
if (chain.length > 0) {
onWakeAgent(chain[0].id, task.companyId, "report_blocked");
}
@@ -261,7 +261,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
onWakeAgent(task.assigneeAgentId, task.companyId, "comment");
wokenForComment.add(task.assigneeAgentId);
}
const chain = db.getChainOfCommand(token.agentId);
const chain = db.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");
}
@@ -333,7 +333,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
let id: string;
try {
id = db.saveTask({
id = db.tasks.saveTask({
companyId,
title: input.title,
description: input.description ?? "",
@@ -352,9 +352,9 @@ export function registerTaskRoutes(server: AgentApiServer) {
// Auto-infer task_type from assignee's department
if (input.assigneeAgentId) {
const assignee = db.getAgent(input.assigneeAgentId);
const assignee = db.entityCrud.getAgent(input.assigneeAgentId);
const inferredType = inferTaskType(assignee.department);
db.setTaskType(id, inferredType);
db.tasks.setTaskType(id, inferredType);
}
db.addActivity({
+13 -13
View File
@@ -76,7 +76,7 @@ export function executeAutomationAction(
switch (action) {
case "assign_task": {
if (ctx.taskId) {
const assignedId = db.autoAssignTask(ctx.taskId, ctx.companyId);
const assignedId = db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
if (assignedId) {
wakeAgentIfPossible(assignedId, ctx.companyId, "assignment");
}
@@ -90,7 +90,7 @@ export function executeAutomationAction(
});
if (unassigned.length > 0) {
const task = unassigned[0]!;
db.saveTask({
db.tasks.saveTask({
id: task.id,
companyId: task.companyId,
projectId: task.projectId,
@@ -120,7 +120,7 @@ export function executeAutomationAction(
logger.warn(`[automation] Skipping create_task — validation failed: ${taskValidation.errors.join("; ")}`);
break;
}
const taskId = db.saveTask({
const taskId = db.tasks.saveTask({
companyId: ctx.companyId,
projectId: ctx.projectId ?? cfg.projectId ?? null,
title,
@@ -147,7 +147,7 @@ export function executeAutomationAction(
let channelTargetId: string | null = cfg.channelTargetId ?? cfg.department ?? ctx.department ?? null;
if (channel === "department" && !channelTargetId && fromAgentId) {
try {
const sender = db.getAgent(fromAgentId);
const sender = db.entityCrud.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));
@@ -195,7 +195,7 @@ export function executeAutomationAction(
if (!targetAgentId && ctx.agentId) {
const triggerType = rule.trigger ?? "";
if (triggerType === "run_completed" || triggerType === "run_failed") {
const manager = db.getManagerForAgent(ctx.agentId);
const manager = db.agents.getManagerForAgent(ctx.agentId);
targetAgentId = manager?.id ?? ctx.agentId;
} else {
targetAgentId = ctx.agentId;
@@ -221,8 +221,8 @@ export function executeAutomationAction(
case "update_task_status": {
if (!ctx.taskId) break;
const newStatus = (config.status as string) ?? "todo";
const previousTask = db.getTask(ctx.taskId);
db.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
const previousTask = db.entityCrud.getTask(ctx.taskId);
db.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
publishDomainChanged();
if (previousTask.status !== newStatus && rule.trigger !== "task_status_changed") {
handleTaskStatusChange(ctx.taskId, ctx.companyId, previousTask.status, newStatus);
@@ -234,17 +234,17 @@ export function executeAutomationAction(
if (!targetAgentId) break;
let agentName = "an agent";
try {
agentName = db.getAgent(targetAgentId).name;
agentName = db.entityCrud.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.getTask(ctx.taskId).title;
if (ctx.taskId) taskTitle = db.entityCrud.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.getManagerForAgent(targetAgentId);
const manager = db.agents.getManagerForAgent(targetAgentId);
const escalationBody = (config.body as string) ?? `Task "${taskTitle}" needs your attention. ${agentName} has flagged this as requiring manager intervention.`;
const wakeTrigger = deriveEscalationWakeTrigger(ctx.newStatus ?? null, rule.trigger ?? null);
if (manager) {
@@ -277,7 +277,7 @@ export function executeAutomationAction(
case "cross_department_notify": {
const targetDepartment = rule.targetDepartment ?? (config.department as string);
if (!targetDepartment) break;
const departmentAgents = db.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
const departmentAgents = db.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
const senderAgentId = ctx.agentId ?? departmentAgents[0]?.id;
if (senderAgentId) {
const dispatchResult = dispatchAgentMessage(
@@ -352,7 +352,7 @@ export function executeAutomationAction(
}
case "reassign_task": {
if (!ctx.taskId) break;
const newAssignee = db.autoAssignTask(ctx.taskId, ctx.companyId);
const newAssignee = db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
if (newAssignee) {
wakeAgentIfPossible(newAssignee, ctx.companyId, "assignment");
}
@@ -361,7 +361,7 @@ export function executeAutomationAction(
case "run_task": {
const targetTaskId = (config.taskId as string) ?? ctx.taskId;
if (!targetTaskId) break;
const task = db.getTask(targetTaskId);
const task = db.entityCrud.getTask(targetTaskId);
if (!task.assigneeAgentId) break;
wakeAgentIfPossible(task.assigneeAgentId, ctx.companyId, "assignment");
break;
@@ -30,7 +30,7 @@ function recordReviewFeedbackKnowledge(
if (!approval.relatedTaskId) return;
if (decision === "approved") return;
const task = db.getTask(approval.relatedTaskId);
const task = db.entityCrud.getTask(approval.relatedTaskId);
const feedback = approval.decisionNote?.trim() || "Board requested revisions before approval.";
const topic = `Review feedback: ${task.title}`;
const content = [
@@ -95,7 +95,7 @@ export function handleApprovalResolved(
});
if (approval.type === "hire_agent" && decision === "approved" && approval.relatedAgentId) {
const agent = db.getAgent(approval.relatedAgentId);
const agent = db.entityCrud.getAgent(approval.relatedAgentId);
if (agent.status === "idle") {
// Deterministic default skill kit. The CEO's operating prompt
@@ -136,7 +136,7 @@ export function handleApprovalResolved(
});
wakeAgentIfPossible(agent.id, companyId, "assignment");
const ceo = db.getCeoForCompany(companyId);
const ceo = db.agents.getCeoForCompany(companyId);
if (ceo && ceo.id !== agent.id) {
wakeAgentIfPossible(ceo.id, companyId, "assignment");
}
@@ -174,27 +174,27 @@ export function handleApprovalResolved(
}
if (approval.relatedTaskId) {
const task = db.getTask(approval.relatedTaskId);
const task = db.entityCrud.getTask(approval.relatedTaskId);
if (task.assigneeAgentId) {
const agent = db.getAgent(task.assigneeAgentId);
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
if (!agent.heartbeatEnabled) {
db.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
}
}
if (decision === "approved") {
db.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
db.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
handleTaskStatusChange(task.id, companyId, task.status, "done");
db.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.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`);
db.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.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`);
db.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.getAgent(approval.requestedByAgentId);
const agent = db.entityCrud.getAgent(approval.requestedByAgentId);
if (!agent.heartbeatEnabled) {
db.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
}
if (decision !== "approved") {
+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.getTask(ctx.taskId) : null;
const task = ctx.taskId ? db.entityCrud.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.getAgent(ctx.agentId);
const agent = db.entityCrud.getAgent(ctx.agentId);
if (agent.budgetMonthlyUsd > 0) {
const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
if (utilization < (conditions.budgetThresholdPct as number) / 100) return false;
+10 -10
View File
@@ -41,9 +41,9 @@ export function handleTaskStatusChange(
const { db, wakeAgentIfPossible } = deps;
const { dispatchAutomation } = callbacks;
let task: ReturnType<typeof db.getTask> | null = null;
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
try {
task = db.getTask(taskId);
task = db.entityCrud.getTask(taskId);
} catch (error) {
deps.logger.warn(`[automation] handleTaskStatusChange: task ${taskId} not found:`, error);
return;
@@ -68,7 +68,7 @@ export function handleTaskStatusChange(
};
try {
if (task.assigneeAgentId) {
const agent = db.getAgent(task.assigneeAgentId);
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
context.department = agent.department ?? undefined;
}
} catch (error) {
@@ -80,7 +80,7 @@ export function handleTaskStatusChange(
if (newStatus === "blocked") {
dispatchAutomation("task_blocked", context);
if (task.assigneeAgentId) {
const manager = db.getManagerForAgent(task.assigneeAgentId);
const manager = db.agents.getManagerForAgent(task.assigneeAgentId);
if (manager) {
wakeAgentIfPossible(manager.id, companyId, "report_blocked");
}
@@ -89,14 +89,14 @@ export function handleTaskStatusChange(
if (newStatus === "done" && task.parentId) {
try {
const siblings = db.listChildTasks(task.parentId, companyId);
const siblings = db.tasks.listChildTasks(task.parentId, companyId);
const allDone = siblings.every((entry) => entry.id === taskId ? true : entry.status === "done");
if (allDone) {
const parent = db.getTask(task.parentId);
const parent = db.entityCrud.getTask(task.parentId);
if (parent.assigneeAgentId) {
wakeAgentIfPossible(parent.assigneeAgentId, companyId, "subtask_completed");
} else {
const assignedId = db.autoAssignTask(parent.id, companyId);
const assignedId = db.tasks.autoAssignTask(parent.id, companyId);
if (assignedId) {
wakeAgentIfPossible(assignedId, companyId, "subtask_completed");
}
@@ -118,9 +118,9 @@ export function handleTaskCreated(
const { db, wakeAgentIfPossible } = deps;
const { dispatchAutomation } = callbacks;
let task: ReturnType<typeof db.getTask> | null = null;
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
try {
task = db.getTask(taskId);
task = db.entityCrud.getTask(taskId);
} catch (error) {
deps.logger.warn(`[automation] handleTaskCreated: task ${taskId} not found:`, error);
return;
@@ -134,7 +134,7 @@ export function handleTaskCreated(
});
if (!task.assigneeAgentId) {
const assignedId = db.autoAssignTask(taskId, companyId);
const assignedId = db.tasks.autoAssignTask(taskId, companyId);
if (assignedId) {
wakeAgentIfPossible(assignedId, companyId, "assignment");
}
@@ -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.setTaskRequiresUserReview(taskId, false);
const agent = db.getAgent(agentId);
db.tasks.setTaskRequiresUserReview(taskId, false);
const agent = db.entityCrud.getAgent(agentId);
if (agent.heartbeatEnabled) {
db.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120);
logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — awaiting user review.`);
}
notify({
@@ -228,7 +228,7 @@ function createDeliverableReviewApproval(
*/
function decideTerminalStatus(
db: RunCompletionDeps["db"],
agent: ReturnType<RunCompletionDeps["db"]["getAgent"]>,
agent: ReturnType<RunCompletionDeps["db"]["entityCrud"]["getAgent"]>,
companyId: string,
): "in_review" | "done" {
if (agent.reportsTo !== null) return "in_review";
@@ -247,7 +247,7 @@ function advanceToDone(
statusMessage: string,
): void {
const { db, publishDomainChanged } = deps;
db.advanceTaskStatus(taskId, "done", statusMessage);
db.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.getAgent(run.agentId);
const agent = db.entityCrud.getAgent(run.agentId);
if (agent.reportsTo === null) {
db.setTaskRequiresUserReview(taskId, true);
db.tasks.setTaskRequiresUserReview(taskId, true);
}
db.advanceTaskStatus(taskId, "in_review", statusMessage);
db.tasks.advanceTaskStatus(taskId, "in_review", statusMessage);
callbacks.handleTaskStatusChange(taskId, run.companyId, "in_progress", "in_review");
const company = db.listSnapshot().companies.find((c) => c.id === run.companyId);
if (company?.reviewDeliverables) {
const importantDocs = findImportantDocsForRun(db, run.id, run.companyId);
if (importantDocs.length > 0) {
db.setTaskRequiresUserReview(taskId, true);
db.tasks.setTaskRequiresUserReview(taskId, true);
if (agent.heartbeatEnabled) {
db.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120);
logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — deliverable flagged for review.`);
}
const chain = db.getChainOfCommand(agent.id);
const chain = db.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.getAgent(run.agentId);
const agent = deps.db.entityCrud.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.getTask> | null = null;
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
try {
task = db.getTask(run.taskId);
task = db.entityCrud.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.advanceTaskStatus(
db.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.getAgent(run.agentId);
const chain = db.getChainOfCommand(agent.id);
const agent = db.entityCrud.getAgent(run.agentId);
const chain = db.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.getAgent(run.agentId);
const agent = db.entityCrud.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.listChildTasks(task.parentId, run.companyId);
const siblings = db.tasks.listChildTasks(task.parentId, run.companyId);
const allSiblingsDoneOrReview = siblings.every((entry) => entry.status === "done" || entry.status === "in_review");
if (allSiblingsDoneOrReview) {
const parent = db.getTask(task.parentId);
const parent = db.entityCrud.getTask(task.parentId);
if (parent.assigneeAgentId) {
wakeAgentIfPossible(parent.assigneeAgentId, run.companyId, "subtask_completed");
} else {
const assignedId = db.autoAssignTask(parent.id, run.companyId);
const assignedId = db.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.getAgent(run.agentId);
const agent = db.entityCrud.getAgent(run.agentId);
if (agent.budgetMonthlyUsd > 0) {
const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
if (utilization >= 0.8) {
+3 -3
View File
@@ -59,7 +59,7 @@ export function resolveWorkflowStepActor(
companyId: string,
): AgentRecord | null {
if (step.assigneeAgentId) {
const agent = db.getAgent(step.assigneeAgentId);
const agent = db.entityCrud.getAgent(step.assigneeAgentId);
if (agent.companyId !== companyId) {
throw new Error(`Workflow step assignee ${step.assigneeAgentId} does not belong to company ${companyId}.`);
}
@@ -67,9 +67,9 @@ export function resolveWorkflowStepActor(
}
if (step.assigneeDepartment) {
const idleAgents = db.getIdleAgentsForDepartment(companyId, step.assigneeDepartment);
const idleAgents = db.agents.getIdleAgentsForDepartment(companyId, step.assigneeDepartment);
if (idleAgents.length > 0) {
return db.getAgent(idleAgents[0]!.id);
return db.entityCrud.getAgent(idleAgents[0]!.id);
}
}
@@ -49,10 +49,10 @@ export function recoverShutdownInterruptedTasks(deps: RecoverInterruptedTasksDep
for (const run of shutdownRuns) {
try {
if (run.taskId) {
const task = deps.db.getTask(run.taskId);
const task = deps.db.entityCrud.getTask(run.taskId);
if (task.status === "in_progress" || task.status === "blocked") {
deps.db.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart.");
deps.db.releaseTaskLock(run.taskId, run.id);
deps.db.tasks.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart.");
deps.db.tasks.releaseTaskLock(run.taskId, run.id);
}
}
if (!resumedAgents.has(run.agentId)) {
+2 -2
View File
@@ -75,7 +75,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.getTask(taskId);
const task = deps.db.entityCrud.getTask(taskId);
deps.checkpointEngine.onTaskStarted(taskId, companyId, task.assigneeAgentId);
} catch (error) {
log.warn(
@@ -118,7 +118,7 @@ export function wireApiServer(deps: WireApiServerDeps): AgentApiServer {
},
input,
),
onAutoAssignTask: (taskId, companyId) => deps.db.autoAssignTask(taskId, companyId),
onAutoAssignTask: (taskId, companyId) => deps.db.tasks.autoAssignTask(taskId, companyId),
onHumanMessage: (companyId, toAgentId, threadId, waitStateId) => {
deps.threadIntegration.onHumanMessage(companyId, toAgentId, threadId, waitStateId);
deps.publishDomainChanged();
+1 -1
View File
@@ -31,7 +31,7 @@ function getScopeLabel(
): string {
if (policy.scopeType === "agent") {
try {
const agent = db.getAgent(policy.scopeId);
const agent = db.entityCrud.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));
+2 -2
View File
@@ -48,8 +48,8 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
const mode = getCompanyExecutionMode(db, companyId);
if (mode !== "ai_cofounder") return null;
const task = db.getTask(taskId);
const taskType = db.getTaskType(taskId);
const task = db.entityCrud.getTask(taskId);
const taskType = db.tasks.getTaskType(taskId);
// Only create plan checkpoints for non-code task types
if (!taskType || !CHECKPOINT_TASK_TYPES.has(taskType)) return null;
+3 -83
View File
@@ -123,7 +123,7 @@ export class AppDatabaseBase {
addComment: (input) => this.addComment(input),
});
this.agents = buildAgentMethods(getDb, ra, tx, {
getAgent: (id) => this.getAgent(id),
getAgent: (id) => this.entityCrud.getAgent(id),
wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo),
recordConfigRevision: (input) => this.recordConfigRevision(input),
cancelRunForDeletion: (runId, taskId, workspaceId, summary) => this.runs.cancelRunForDeletion(runId, taskId, workspaceId, summary),
@@ -134,14 +134,14 @@ export class AppDatabaseBase {
addActivity: (input) => this.addActivity(input),
});
this.messages = buildMessageMethods(getDb, ra, {
getAgent: (id) => this.getAgent(id),
getAgent: (id) => this.entityCrud.getAgent(id),
addActivity: (input) => this.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.getAgent(id),
getAgent: (id) => this.entityCrud.getAgent(id),
});
this.social = buildSocialMethods(getDb, ra, tx);
this.skills = buildSkillMethods(getDb, ra);
@@ -270,86 +270,6 @@ export class AppDatabaseBase {
getConnector(id: ConnectorId) { return this.connectorsCrud.getConnector(id); }
findConnector(id: ConnectorId) { return this.connectorsCrud.findConnector(id); }
// ─── Agents ────────────────────────────────────────────────────────────────
saveAgent(input: { id?: string | null; companyId: string; name: string; role: string; title: string; department?: string | null; status: AgentStatus; reportsTo?: string | null; connectorId: ConnectorId; workspaceId?: string | null; model?: string | null; metadataJson?: string; capabilities: string; budgetMonthlyUsd: number; roleLevel?: import("@shared/types").AgentRoleLevel; modelStrategy?: import("@shared/types").AgentModelStrategy }) { return this.agents.saveAgent(input); }
deleteAgent(id: string, companyId: string) { return this.agents.deleteAgent(id, companyId); }
getAgent(id: string) { return this.entityCrud.getAgent(id); }
findAgent(id: string) { return this.entityCrud.findAgent(id); }
setHeartbeat(agentId: string, enabled: boolean, intervalSec: number) { return this.agents.setHeartbeat(agentId, enabled, intervalSec); }
markHeartbeatAt(agentId: string) { return this.agents.markHeartbeatAt(agentId); }
resetMonthlyBudgets(companyId: string) { return this.agents.resetMonthlyBudgets(companyId); }
listHeartbeatEligibleAgents(companyId: string) { return this.agents.listHeartbeatEligibleAgents(companyId); }
getChainOfCommand(agentId: string) { return this.agents.getChainOfCommand(agentId); }
getDirectReports(agentId: string, companyId: string) { return this.agents.getDirectReports(agentId, companyId); }
getCeoForCompany(companyId: string) { return this.agents.getCeoForCompany(companyId); }
listActiveAgentsForCompany(companyId: string) { return this.agents.listActiveAgentsForCompany(companyId); }
listAgentsForApi(companyId: string) { return this.agents.listAgentsForApi(companyId); }
listDirectReportsForStandup(companyId: string, managerId: string) { return this.agents.listDirectReportsForStandup(companyId, managerId); }
listReportStatusesForStandup(companyId: string, managerId: string) { return this.agents.listReportStatusesForStandup(companyId, managerId); }
listExistingAgentsForHireCheck(companyId: string) { return this.agents.listExistingAgentsForHireCheck(companyId); }
hasAgentAssignedTasks(agentId: string, companyId: string) { return this.agents.hasAgentAssignedTasks(agentId, companyId); }
updateAgentStatus(agentId: string, status: string) { return this.agents.updateAgentStatus(agentId, status); }
isAgentPendingApproval(agentId: string) { return this.agents.isAgentPendingApproval(agentId); }
findMentionedAgentIds(companyId: string, text: string) { return this.agents.findMentionedAgentIds(companyId, text); }
getIdleAgentsForDepartment(companyId: string, department: string) { return this.agents.getIdleAgentsForDepartment(companyId, department); }
getManagerForAgent(agentId: string) { return this.agents.getManagerForAgent(agentId); }
getAgentPermissions(agentId: string) { return this.agents.getAgentPermissions(agentId); }
setAgentPermission(agentId: string, companyId: string, permissionKey: string, enabled: boolean) { return this.agents.setAgentPermission(agentId, companyId, permissionKey, enabled); }
hasAgentPermission(agentId: string, permissionKey: string) { return this.agents.hasAgentPermission(agentId, permissionKey); }
getRuntimeState(agentId: string) { return this.agents.getRuntimeState(agentId); }
setRuntimeState(agentId: string, companyId: string, state: Record<string, unknown>) { return this.agents.setRuntimeState(agentId, companyId, state); }
getHeartbeatPolicy(agentId: string) { return this.agents.getHeartbeatPolicy(agentId); }
setHeartbeatPolicy(agentId: string, companyId: string, policy: { enabled?: boolean; intervalSec?: number; cooldownSec?: number; maxConcurrentRuns?: number; wakeOnDemand?: boolean }) { return this.agents.setHeartbeatPolicy(agentId, companyId, policy); }
countActiveRunsForAgent(agentId: string, companyId: string) { return this.agents.countActiveRunsForAgent(agentId, companyId); }
listDepartmentAgentIds(companyId: string, department: string) { return this.agents.listDepartmentAgentIds(companyId, department); }
listCompanyBroadcastAgentIds(companyId: string) { return this.agents.listCompanyBroadcastAgentIds(companyId); }
listIncidentPingableAgentIds(companyId: string) { return this.agents.listIncidentPingableAgentIds(companyId); }
findAgentName(agentId: string) { return this.agents.findAgentName(agentId); }
listAgentNamesByIds(companyId: string, agentIds: readonly string[]) { return this.agents.listAgentNamesByIds(companyId, agentIds); }
listActiveAgentIdsFromIds(companyId: string, agentIds: readonly string[]) { return this.agents.listActiveAgentIdsFromIds(companyId, agentIds); }
// ─── Tasks ─────────────────────────────────────────────────────────────────
saveTask(input: { id?: string | null; companyId: string; projectId?: string | null; goalId?: string | null; parentId?: string | null; title: string; description: string; assigneeAgentId?: string | null; workspaceId?: string | null; priority: string; status: string }) { return this.tasks.saveTask(input); }
getTask(id: string) { return this.entityCrud.getTask(id); }
deleteTask(id: string, companyId: string) { return this.tasks.deleteTask(id, companyId); }
getSessionById(sessionId: string) { return this.tasks.getSessionById(sessionId); }
incrementSessionUsage(sessionId: string, inputTokens: number, outputTokens: number, runId: string) { return this.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId); }
archiveSession(sessionId: string, reason: string, handoffMarkdown: string) { return this.tasks.archiveSession(sessionId, reason, handoffMarkdown); }
createNewSession(input: { companyId: string; agentId: string; adapterType: string }) { return this.tasks.createNewSession(input); }
ensureAgentSession(input: { agentId: string; companyId: string; adapterType: string }) { return this.tasks.ensureAgentSession(input); }
setAgentSessionId(agentId: string, sessionId: string) { return this.tasks.setAgentSessionId(agentId, sessionId); }
getRecentRunsForTask(taskId: string, limit: number) { return this.tasks.getRecentRunsForTask(taskId, limit); }
getRecentActivityForTask(taskId: string, limit: number) { return this.tasks.getRecentActivityForTask(taskId, limit); }
checkoutTask(taskId: string, runId: string) { return this.tasks.checkoutTask(taskId, runId); }
releaseTaskLock(taskId: string, runId: string) { return this.tasks.releaseTaskLock(taskId, runId); }
renewTaskLease(taskId: string, runId: string) { return this.tasks.renewTaskLease(taskId, runId); }
listAgentTasks(agentId: string, companyId: string, statuses: string[]) { return this.tasks.listAgentTasks(agentId, companyId, statuses); }
listTasksByStatuses(companyId: string, statuses: string[]) { return this.tasks.listTasksByStatuses(companyId, statuses); }
listAssigneeTasksForStandup(companyId: string, agentId: string) { return this.tasks.listAssigneeTasksForStandup(companyId, agentId); }
listTasksForAssignees(companyId: string, assigneeIds: string[]) { return this.tasks.listTasksForAssignees(companyId, assigneeIds); }
listReviewTasksForManager(managerId: string, companyId: string) { return this.tasks.listReviewTasksForManager(managerId, companyId); }
countActiveTasksForCompany(companyId: string) { return this.tasks.countActiveTasksForCompany(companyId); }
countAdvancingTasksForCompany(companyId: string) { return this.tasks.countAdvancingTasksForCompany(companyId); }
listChildTasks(parentId: string, companyId: string) { return this.tasks.listChildTasks(parentId, companyId); }
advanceTaskStatus(taskId: string, newStatus: string, comment?: string) { return this.tasks.advanceTaskStatus(taskId, newStatus, comment); }
setTaskRequiresUserReview(taskId: string, value: boolean) { return this.tasks.setTaskRequiresUserReview(taskId, value); }
hasPendingDeliverableReviews(companyId: string) { return this.tasks.hasPendingDeliverableReviews(companyId); }
hasAgentPendingDeliverableReview(agentId: string, companyId: string) { return this.tasks.hasAgentPendingDeliverableReview(agentId, companyId); }
hasAgentTaskAwaitingReview(agentId: string, companyId: string) { return this.tasks.hasAgentTaskAwaitingReview(agentId, companyId); }
autoAssignTask(taskId: string, companyId: string) { return this.tasks.autoAssignTask(taskId, companyId); }
getTaskRetryCount(taskId: string) { return this.tasks.getTaskRetryCount(taskId); }
incrementTaskRetryCount(taskId: string) { return this.tasks.incrementTaskRetryCount(taskId); }
resetTaskRetryCount(taskId: string) { return this.tasks.resetTaskRetryCount(taskId); }
retryInterruptedTask(taskId: string, _agentId: string, _companyId: string) { return this.tasks.retryInterruptedTask(taskId, _agentId, _companyId); }
dismissInterruptedTask(taskId: string) { return this.tasks.dismissInterruptedTask(taskId); }
markInterruptedTaskFailed(taskId: string) { return this.tasks.markInterruptedTaskFailed(taskId); }
setTaskType(taskId: string, taskType: string) { return this.tasks.setTaskType(taskId, taskType); }
listProjectTaskAssigneeIds(projectId: string, companyId: string) { return this.tasks.listProjectTaskAssigneeIds(projectId, companyId); }
listReviewTasksForSweep(companyId: string) { return this.tasks.listReviewTasksForSweep(companyId); }
getTaskType(taskId: string) { return this.tasks.getTaskType(taskId); }
// ─── Runs ──────────────────────────────────────────────────────────────────
getRun(id: string) { return this.runs.getRun(id); }
+5 -5
View File
@@ -77,8 +77,8 @@ export class AppDatabase extends AppDatabaseBase {
countActiveGoalsForCompany(companyId: string) { return this.entityCrud.countActiveGoalsForCompany(companyId); }
createHireRequest(input: { companyId: string; requestedByAgentId?: string | null; name: string; role: string; title: string; department?: string | null; reportsTo?: string | null; connectorId: ConnectorId; workspaceId?: string | null; model?: string | null; metadataJson?: string; capabilities: string; budgetMonthlyUsd: number; payloadSummary: string; impactSummary: string; payloadJson?: string | null }) {
return this.entityCrud.createHireRequest(input, {
saveAgent: (agentInput) => this.saveAgent(agentInput),
requestApproval: (approvalInput) => this.requestApproval(approvalInput),
saveAgent: (agentInput) => this.agents.saveAgent(agentInput),
requestApproval: (approvalInput) => this.approvalsMod.requestApproval(approvalInput),
});
}
@@ -110,9 +110,9 @@ export class AppDatabase extends AppDatabaseBase {
getConfigRevision(revisionId: string, companyId: string) { return this.documentsMod.getConfigRevision(revisionId, companyId); }
rollbackConfigRevision(agentId: string, revisionId: string, companyId: string) {
return this.documentsMod.rollbackConfigRevision(agentId, revisionId, companyId, {
getConfigRevision: (rid, cid) => this.getConfigRevision(rid, cid),
getAgent: (id) => this.getAgent(id),
recordConfigRevision: (input) => this.recordConfigRevision(input),
getConfigRevision: (rid, cid) => this.documentsMod.getConfigRevision(rid, cid),
getAgent: (id) => this.entityCrud.getAgent(id),
recordConfigRevision: (input) => this.documentsMod.recordConfigRevision(input),
});
}
+3 -3
View File
@@ -190,7 +190,7 @@ function maybeRaiseDeliverableReviewApproval(
const companySettings = deps.db.getCompanyApprovalSettings(input.companyId);
if (!companySettings?.reviewDeliverables) return null;
if (deps.db.getCompanyExecutionMode(input.companyId) === "ai_cofounder") return null;
if (deps.db.hasPendingDeliverableReviews(input.companyId)) return null;
if (deps.db.tasks.hasPendingDeliverableReviews(input.companyId)) return null;
const approvalId = deps.db.requestApproval({
companyId: input.companyId,
@@ -207,9 +207,9 @@ function maybeRaiseDeliverableReviewApproval(
if (input.authorAgentId) {
try {
const agent = deps.db.getAgent(input.authorAgentId);
const agent = deps.db.entityCrud.getAgent(input.authorAgentId);
if (agent.heartbeatEnabled) {
deps.db.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120);
deps.db.agents.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120);
}
} catch (error) {
deps.logger.warn(
+5 -5
View File
@@ -32,7 +32,7 @@ function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: strin
const thread = db.getThread(waitState.threadId, companyId);
if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue;
try {
const task = db.getTask(thread.relatedEntityId);
const task = db.entityCrud.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.getTask(approval.relatedTaskId);
const task = db.entityCrud.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.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (agent.companyId !== companyId) continue;
if (["terminated", "pending_approval"].includes(agent.status)) continue;
if (!agent.heartbeatEnabled) {
db.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120);
db.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));
@@ -156,7 +156,7 @@ export function reconcileExecutionModeChange({
companyId,
);
for (const task of tasks) {
db.setTaskRequiresUserReview(task.id, false);
db.tasks.setTaskRequiresUserReview(task.id, false);
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ export async function notifyHireApproved(
try {
let agent;
try {
agent = db.getAgent(input.agentId);
agent = db.entityCrud.getAgent(input.agentId);
} catch (err) {
const classified = classifyUnknownError(err, {
kind: "recoverable",
+5 -5
View File
@@ -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.findAgent(parsed.id) : null;
const existingAgent = parsed.id ? db.entityCrud.findAgent(parsed.id) : null;
if (!existingAgent || existingAgent.companyId !== parsed.companyId) {
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
}
@@ -101,7 +101,7 @@ export function registerCoreAgentGraphHandlers({
}
let id: string;
try {
id = db.saveAgent({
id = db.agents.saveAgent({
id: parsed.id ?? null,
companyId: parsed.companyId,
name: parsed.name,
@@ -188,7 +188,7 @@ export function registerCoreAgentGraphHandlers({
const managerLabel = resolvedReportsTo
? (() => {
try {
return db.getAgent(resolvedReportsTo).name;
return db.entityCrud.getAgent(resolvedReportsTo).name;
} catch (err) {
logger.warn(`[ipc/agent-graph] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
return resolvedReportsTo;
@@ -271,14 +271,14 @@ export function registerCoreAgentGraphHandlers({
if (!db.belongsToCompany("agents", parsed.id, parsed.companyId)) {
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
}
const agent = db.getAgent(parsed.id);
const agent = db.entityCrud.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.deleteAgent(parsed.id, parsed.companyId);
db.agents.deleteAgent(parsed.id, parsed.companyId);
db.addActivity({
companyId: parsed.companyId,
actor: "board",
+2 -2
View File
@@ -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.getTask(parsed.taskId);
const task = db.entityCrud.getTask(parsed.taskId);
if (task.assigneeAgentId) {
wakeAgentIfPossible(task.assigneeAgentId, task.companyId, "comment");
}
@@ -211,7 +211,7 @@ export function registerCoreOperationHandlers({
if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) {
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
}
db.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec);
db.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec);
db.addActivity({
companyId: parsed.companyId,
actor: "board",
+2 -2
View File
@@ -166,7 +166,7 @@ export function registerCoreReadHandlers({
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
try {
const task = db.getTask(parsed.id);
const task = db.entityCrud.getTask(parsed.id);
if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company.");
return ok(task);
} catch (err) {
@@ -180,7 +180,7 @@ export function registerCoreReadHandlers({
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
try {
const agent = db.getAgent(parsed.id);
const agent = db.entityCrud.getAgent(parsed.id);
if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company.");
return ok(agent);
} catch (err) {
+4 -4
View File
@@ -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.getTask(taskId);
const existingTask = db.entityCrud.getTask(taskId);
previousStatus = existingTask.status;
previousAssigneeAgentId = existingTask.assigneeAgentId;
} catch (err) {
@@ -124,7 +124,7 @@ export function registerCoreTaskGraphHandlers({
}
let id: string;
try {
id = db.saveTask(parsed);
id = db.tasks.saveTask(parsed);
} catch (error) {
return fail("INVALID_TASK_STATE", error instanceof Error ? error.message : "Task could not be saved.");
}
@@ -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.setTaskRequiresUserReview(id, false);
db.tasks.setTaskRequiresUserReview(id, false);
} catch (error) {
logger.warn(
`[ipc-task] setTaskRequiresUserReview(false) failed for task ${id}: ${error instanceof Error ? error.message : String(error)}`,
@@ -175,7 +175,7 @@ export function registerCoreTaskGraphHandlers({
});
const deleteHandlers = [
{ channel: IPC_CHANNELS.deleteTask, table: "tasks", entity: "task", deleteFn: (id: string, companyId: string) => db.deleteTask(id, companyId) },
{ 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.deleteGoal(id, companyId) },
{ channel: IPC_CHANNELS.deleteProject, table: "projects", entity: "project", deleteFn: (id: string, companyId: string) => db.deleteProject(id, companyId) },
{ channel: IPC_CHANNELS.deleteWorkspace, table: "workspaces", entity: "workspace", deleteFn: (id: string, companyId: string) => db.deleteWorkspace(id, companyId) },
+2 -2
View File
@@ -36,7 +36,7 @@ export function registerRecoveryHandlers({
if (!db.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
return fail("TASK_NOT_FOUND", "Task not found in the current company.");
}
db.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId);
db.tasks.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId);
try {
wakeAgentIfPossible(parsed.agentId, parsed.companyId, "retry_interrupted");
} catch (err) {
@@ -53,7 +53,7 @@ export function registerRecoveryHandlers({
if (!db.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
return fail("TASK_NOT_FOUND", "Task not found in the current company.");
}
db.markInterruptedTaskFailed(parsed.taskId);
db.tasks.markInterruptedTaskFailed(parsed.taskId);
publishDomainChanged();
return ok(null);
});
+9 -9
View File
@@ -204,14 +204,14 @@ function resolveProjectWakeTargets(
const project = db.getProjectForUpdate(input.channelTargetId, input.companyId);
const participantIds = new Set<string>();
if (project?.leadAgentId) participantIds.add(project.leadAgentId);
for (const assigneeId of db.listProjectTaskAssigneeIds(input.channelTargetId, input.companyId)) {
for (const assigneeId of db.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.listActiveAgentIdsFromIds(input.companyId, ids);
return db.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.findMentionedAgentIds(input.companyId, input.body);
const mentioned = db.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.listDepartmentAgentIds(input.companyId, input.channelTargetId)) {
for (const agentId of db.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.listCompanyBroadcastAgentIds(input.companyId)) {
for (const agentId of db.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.listIncidentPingableAgentIds(input.companyId)) {
for (const agentId of db.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.getAgent("__board__")`,
// set makes `wakeAgentIfPossible` call `db.entityCrud.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);
@@ -330,7 +330,7 @@ export function dispatchAgentMessage(
!normalized.fromHumanUser &&
(message.channel !== "direct" || message.toAgentId === HUMAN_SENDER_ID)
) {
const senderName = deps.db.findAgentName(message.fromAgentId) ?? "Agent";
const senderName = deps.db.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.listAgentNamesByIds(normalized.companyId, wakeTargets);
const agentNameRows = deps.db.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;
+5 -5
View File
@@ -85,9 +85,9 @@ export function createOnboardingService({
}: OnboardingDependencies) {
function updateAgentWorkspaceBinding(agentId: string, workspaceId: string | null) {
if (!workspaceId) return;
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (agent.workspaceId === workspaceId) return;
db.saveAgent({
db.agents.saveAgent({
id: agent.id,
companyId: agent.companyId,
name: agent.name,
@@ -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.saveTask({
const taskId = db.tasks.saveTask({
companyId,
projectId,
goalId: activeGoal?.id ?? null,
@@ -260,7 +260,7 @@ export function createOnboardingService({
}
const ceoLocale = getLocale();
const ceoId = db.saveAgent({
const ceoId = db.agents.saveAgent({
companyId,
name: "CEO",
role: "CEO",
@@ -279,7 +279,7 @@ export function createOnboardingService({
modelStrategy: "explicit",
roleLevel: "c_suite",
});
db.setHeartbeat(ceoId, true, 120);
db.agents.setHeartbeat(ceoId, true, 120);
db.setCurrentCompany(companyId);
seedDefaultAutomationRules(companyId);
+16 -16
View File
@@ -55,9 +55,9 @@ export function createHeartbeat(
} = deps;
function ensureAgentHeartbeatEnabled(agentId: string) {
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (!agent.heartbeatEnabled) {
db.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120);
db.agents.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120);
}
}
@@ -68,7 +68,7 @@ export function createHeartbeat(
if (!hasWorkerReady()) return SKIPPED("worker_not_ready");
const companyStatus = db.getCompanyStatus(companyId);
if (companyStatus !== "active") return SKIPPED("company_inactive");
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (agent.companyId !== companyId) {
logger.warn(`[heartbeat] Refusing cross-company run for agent ${agentId}: ${agent.companyId} !== ${companyId}`);
return SKIPPED("cross_company");
@@ -112,10 +112,10 @@ 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.getHeartbeatPolicy(agent.id);
const heartbeatPolicy = db.agents.getHeartbeatPolicy(agent.id);
// maxConcurrentRuns check: count active runs for this agent
const activeRunCount = db.countActiveRunsForAgent(agent.id, companyId);
const activeRunCount = db.agents.countActiveRunsForAgent(agent.id, companyId);
if (activeRunCount >= heartbeatPolicy.maxConcurrentRuns) return SKIPPED("max_concurrent_runs");
// Cooldown check: ensure enough time has passed since last heartbeat
@@ -129,7 +129,7 @@ export function createHeartbeat(
if (!heartbeatPolicy.wakeOnDemand && trigger !== "timer" && trigger !== "bootstrap") {
return SKIPPED("wake_on_demand_disabled");
}
if (db.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval");
if (db.agents.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval");
const companyMode = db.getCompanyExecutionMode(companyId);
const approvalSettings = db.getCompanyApprovalSettings(companyId);
@@ -140,7 +140,7 @@ export function createHeartbeat(
if (
shouldEnforceHumanReview &&
!isMessageWake &&
(db.hasAgentPendingDeliverableReview(agentId, companyId) || db.hasAgentTaskAwaitingReview(agentId, companyId))
(db.tasks.hasAgentPendingDeliverableReview(agentId, companyId) || db.tasks.hasAgentTaskAwaitingReview(agentId, companyId))
) {
return SKIPPED("awaiting_user_review");
}
@@ -150,23 +150,23 @@ export function createHeartbeat(
shouldEnforceHumanReview &&
!isMessageWake &&
!agent.reportsTo &&
db.hasPendingDeliverableReviews(companyId)
db.tasks.hasPendingDeliverableReviews(companyId)
) {
return SKIPPED("pending_deliverable_review");
}
const assignedTasks = isCommunicationWake
? []
: db.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]);
: db.tasks.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]);
const isTimer = trigger === "timer";
const candidateTasks = assignedTasks;
const candidateTask = candidateTasks.find((entry) => !entry.activeRunId) ?? null;
const runId = randomUUID();
let task: ReturnType<AppDatabase["getTask"]> | null = null;
let task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null = null;
if (candidateTask) {
const checkoutSuccess = db.checkoutTask(candidateTask.id, runId);
const checkoutSuccess = db.tasks.checkoutTask(candidateTask.id, runId);
if (!checkoutSuccess) {
db.addActivity({
companyId,
@@ -178,7 +178,7 @@ export function createHeartbeat(
});
return SKIPPED("checkout_race_lost");
}
task = db.getTask(candidateTask.id);
task = db.entityCrud.getTask(candidateTask.id);
} else {
const hasActiveAssignedRun = candidateTasks.some(
(entry) => entry.activeRunId && hasActiveRun({ taskId: entry.id, companyId }),
@@ -186,9 +186,9 @@ export function createHeartbeat(
if (hasActiveAssignedRun) return SKIPPED("max_concurrent_runs");
const isCeo = !agent.reportsTo;
const isManager = db.getDirectReports(agent.id, companyId).length > 0;
const isManager = db.agents.getDirectReports(agent.id, companyId).length > 0;
const goalCount = db.countActiveGoalsForCompany(companyId);
const taskCount = db.countActiveTasksForCompany(companyId);
const taskCount = db.tasks.countActiveTasksForCompany(companyId);
const companyNeedsBootstrap = goalCount > 0 && taskCount < goalCount;
if (isTimer && !isCeo && !isManager && !companyNeedsBootstrap) {
@@ -232,7 +232,7 @@ export function createHeartbeat(
resolvedModel: assignment.modelId,
});
db.markHeartbeatAt(agentId);
db.agents.markHeartbeatAt(agentId);
// Actor audit: record what triggered this run
try {
@@ -266,7 +266,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.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (agent.companyId !== companyId) {
logger.warn(`[wake] Refusing cross-company wake for agent ${agentId}: ${agent.companyId} !== ${companyId}`);
return null;
+4 -4
View File
@@ -30,7 +30,7 @@ const SUBJECT_PREVIEW_LEN = 80;
export function resolveInitialUserTurn(
params: {
trigger: string | null;
task: ReturnType<AppDatabase["getTask"]> | null;
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
companyId: string;
agentId: string;
},
@@ -61,9 +61,9 @@ export function resolveInitialUserTurn(
// mode prompt.
if (trigger === "timer" || trigger === "bootstrap" || trigger === "app_restart_resume") {
try {
const agent = deps.db.getAgent(agentId);
const agent = deps.db.entityCrud.getAgent(agentId);
if (!agent.reportsTo) {
const activeReports = deps.db
const activeReports = deps.db.agents
.getDirectReports(agentId, companyId)
.filter((r) => r.status !== "pending_approval" && r.status !== "terminated");
if (activeReports.length === 0) {
@@ -145,7 +145,7 @@ function buildGenericWakeTurn(trigger: string | null): string {
function resolveAgentLabel(db: AppDatabase, agentId: string): string {
try {
const name = db.findAgentName(agentId);
const name = db.agents.findAgentName(agentId);
if (name && name.length > 0) return name;
} catch {
// fall through
@@ -23,8 +23,8 @@ export function createHeartbeatScheduler(
function persistSkip(agentId: string, companyId: string, reason: string) {
try {
const existing = db.getRuntimeState(agentId) as Record<string, unknown>;
db.setRuntimeState(agentId, companyId, {
const existing = db.agents.getRuntimeState(agentId) as Record<string, unknown>;
db.agents.setRuntimeState(agentId, companyId, {
...existing,
heartbeatSkip: { reason, at: new Date().toISOString() },
});
@@ -37,11 +37,11 @@ export function createHeartbeatScheduler(
function clearSkip(agentId: string, companyId: string) {
try {
const existing = db.getRuntimeState(agentId) as Record<string, unknown>;
const existing = db.agents.getRuntimeState(agentId) as Record<string, unknown>;
if (!("heartbeatSkip" in existing)) return;
const next = { ...existing };
delete next.heartbeatSkip;
db.setRuntimeState(agentId, companyId, next);
db.agents.setRuntimeState(agentId, companyId, next);
} catch (error) {
logger.warn(
`[heartbeat] Failed to clear skip reason for agent ${agentId}: ${error instanceof Error ? error.message : String(error)}`,
@@ -72,7 +72,7 @@ export function createHeartbeatScheduler(
for (const company of activeCompanies) {
try {
const eligibleAgents = db.listHeartbeatEligibleAgents(company.id);
const eligibleAgents = db.agents.listHeartbeatEligibleAgents(company.id);
for (const agent of eligibleAgents) {
if (hasActiveRun({ agentId: agent.id, companyId: company.id })) continue;
@@ -103,9 +103,9 @@ export function createHeartbeatScheduler(
// regular timer interval. countAdvancingTasksForCompany
// counts only todo + in_progress so bootstrapping still
// fires once the team stalls.
const advancingCount = db.countAdvancingTasksForCompany(company.id);
const advancingCount = db.tasks.countAdvancingTasksForCompany(company.id);
if (goalCount > 0 && advancingCount === 0) {
const ceo = db.getCeoForCompany(company.id);
const ceo = db.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);
@@ -34,7 +34,7 @@ export function createHousekeepingScheduler(
const currentMonth = new Date().toISOString().slice(0, 7);
const lastResetMonth = db.getLastBudgetResetMonth(company.id) ?? "";
if (currentMonth !== lastResetMonth) {
db.resetMonthlyBudgets(company.id);
db.agents.resetMonthlyBudgets(company.id);
db.setLastBudgetResetMonth(company.id, currentMonth);
logger.info(`[housekeeping] Reset monthly budgets for company ${company.name} (${currentMonth})`);
}
@@ -17,9 +17,9 @@ export function createIdleAutomationScheduler(deps: OrchestrationDependencies) {
for (const company of activeCompanies) {
try {
const companyAgents = db.listActiveAgentsForCompany(company.id);
const companyAgents = db.agents.listActiveAgentsForCompany(company.id);
for (const agent of companyAgents) {
if (!db.hasAgentAssignedTasks(agent.id, company.id)) {
if (!db.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) {
+6 -6
View File
@@ -37,13 +37,13 @@ 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.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
const companyStatus = db.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.getAgent(task.assigneeAgentId);
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
if (agent.companyId !== task.companyId) {
return fail("AGENT_COMPANY_MISMATCH", "The assigned agent does not belong to the task company.");
}
@@ -75,7 +75,7 @@ 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.isAgentPendingApproval(agent.id)) {
if (db.agents.isAgentPendingApproval(agent.id)) {
return fail("AGENT_PENDING_APPROVAL", "This agent is pending hire approval and cannot run tasks.");
}
const blockingApproval = db.getBlockingApproval(task.companyId, agent.id, task.id);
@@ -88,7 +88,7 @@ export function createTaskRunner(
// CAS checkout — atomic claim (EXEC-01, EXEC-02)
const runId = randomUUID();
const checkoutSuccess = db.checkoutTask(task.id, runId);
const checkoutSuccess = db.tasks.checkoutTask(task.id, runId);
if (!checkoutSuccess) {
return fail("CHECKOUT_RACE_LOST", "Another run claimed this task. Refresh and try again.");
}
@@ -180,8 +180,8 @@ export function createTaskRunner(
}
try {
const task = run.taskId ? db.getTask(run.taskId) : null;
const agent = db.getAgent(run.agentId);
const task = run.taskId ? db.entityCrud.getTask(run.taskId) : null;
const agent = db.entityCrud.getAgent(run.agentId);
const connector = db.getConnector(run.connectorId);
const eligibilityFailure = getRunEligibilityFailure(agent, connector, task?.projectId ?? null);
if (eligibilityFailure) {
+9 -9
View File
@@ -18,11 +18,11 @@ import { resolveInitialUserTurn } from "./initial-user-turn";
export interface RunDispatchParams {
runId: string;
companyId: string;
agent: ReturnType<AppDatabase["getAgent"]>;
agent: ReturnType<AppDatabase["entityCrud"]["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["getTask"]> | null;
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
connector: ReturnType<AppDatabase["getConnector"]>;
workspace: ReturnType<AppDatabase["getWorkspace"]>;
workspaceMode: WorkspaceLockMode;
@@ -81,8 +81,8 @@ function assembleApiEnv(
}
function assembleRecentComments(
assignedTasks: ReturnType<AppDatabase["listAgentTasks"]>,
reviewTasks: ReturnType<AppDatabase["listAgentTasks"]>,
assignedTasks: ReturnType<AppDatabase["tasks"]["listAgentTasks"]>,
reviewTasks: ReturnType<AppDatabase["tasks"]["listAgentTasks"]>,
snapshot: RunDispatchParams["snapshot"],
) {
const relevantTaskIds = new Set([
@@ -150,7 +150,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.ensureAgentSession({
const agentSessionId = db.tasks.ensureAgentSession({
agentId: agent.id,
companyId,
adapterType: connectorId,
@@ -273,8 +273,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.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]);
const reviewTasks = db.listReviewTasksForManager(agent.id, companyId);
const assignedTasks = db.tasks.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]);
const reviewTasks = db.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);
@@ -291,8 +291,8 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
executionMode: db.getCompanyExecutionMode(companyId) as ExecutionMode,
goals: snapshot.goals.filter((goal) => goal.companyId === companyId),
projects: snapshot.projects.filter((project) => project.companyId === companyId),
directReports: db.getDirectReports(agent.id, companyId),
chainOfCommand: db.getChainOfCommand(agent.id),
directReports: db.agents.getDirectReports(agent.id, companyId),
chainOfCommand: db.agents.getChainOfCommand(agent.id),
assignedTasks,
allAgents: snapshot.agents.filter((entry) => entry.companyId === companyId),
pendingApprovals: filterPendingApprovals(snapshot.approvals, companyId, agent.id),
+2 -2
View File
@@ -54,7 +54,7 @@ function shouldProduceKnowledge(
);
}
try {
const agent = db.getAgent(agentId);
const agent = db.entityCrud.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.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
topic = task.title;
} catch (error) {
logger.warn(
@@ -93,9 +93,9 @@ export function buildOrchestrationService({
},
evaluatePreRunSessionRotation: (agentId, connectorId) => {
try {
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (!agent.sessionId) return { rotated: false, handoff: null, sessionId: null };
const session = db.getSessionById(agent.sessionId);
const session = db.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.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId);
db.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.archiveSession(session.id, reason, handoff);
db.tasks.archiveSession(session.id, reason, handoff);
const newSessionId = db.createNewSession({
const newSessionId = db.tasks.createNewSession({
companyId: session.companyId,
agentId: session.agentId,
adapterType: session.adapterType,
});
db.setAgentSessionId(session.agentId, newSessionId);
db.tasks.setAgentSessionId(session.agentId, newSessionId);
logger.info(`[session] New session ${newSessionId} created for agent ${session.agentId}`);
return newSessionId;
+8 -6
View File
@@ -25,7 +25,9 @@ interface CapabilityMatchResult {
}
interface CapabilityMatchDb {
getAgent(id: string): { name: string; capabilities: string };
entityCrud: {
getAgent(id: string): { name: string; capabilities: string };
};
listAgentSkillAssignments(id: string): string[];
getCompanySkill(companyId: string, skillId: string): { name: string; description: string | null; slug: string } | null;
}
@@ -128,7 +130,7 @@ export function checkCapabilityMatch(
return { matched: true, warning: "Task description has no distinctive keywords for capability matching." };
}
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
const capWords = tokenize(agent.capabilities);
const capabilityOverlap = taskWords.filter((w) => capWords.includes(w)).length;
@@ -197,7 +199,7 @@ export function sweepStaleReviews(
const isCofounderMode = deps.getCompanyExecutionMode(companyId) === "ai_cofounder";
let escalatedCount = 0;
const reviewTasks = deps.db.listReviewTasksForSweep(companyId);
const reviewTasks = deps.db.tasks.listReviewTasksForSweep(companyId);
for (const task of reviewTasks) {
const reviewAge = now - new Date(task.updatedAt).getTime();
@@ -205,11 +207,11 @@ export function sweepStaleReviews(
if (!task.assigneeAgentId) continue;
// The reviewer is the assignee's direct manager
const reviewer = deps.db.getManagerForAgent(task.assigneeAgentId);
const reviewer = deps.db.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.getChainOfCommand(reviewer.id);
const chain = deps.db.agents.getChainOfCommand(reviewer.id);
const escalationTarget = chain.length > 0 ? chain[0] : null;
const pendingApproval = deps.db.findPendingApprovalForTask(companyId, task.id);
@@ -281,7 +283,7 @@ export function sweepStaleReviews(
// D-10: Advance task status from in_review to done
try {
deps.db.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes.");
deps.db.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));
+6 -6
View File
@@ -20,7 +20,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
*/
onTaskCreated(taskId: string, companyId: string): void {
try {
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
if (task.companyId !== companyId) return;
const thread = ensureTaskThread(db, taskId, companyId, task.title);
@@ -53,7 +53,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
*/
onTaskStatusChanged(taskId: string, companyId: string, previousStatus: string, newStatus: string): void {
try {
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
if (task.companyId !== companyId) return;
const thread = ensureTaskThread(db, taskId, companyId, task.title);
@@ -105,7 +105,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
onApprovalCreated(approvalId: string, companyId: string, taskId: string | null, approvalType: string, summary: string): void {
try {
const approval = db.getApprovalRecord(approvalId);
const task = taskId ? db.getTask(taskId) : null;
const task = taskId ? db.entityCrud.getTask(taskId) : null;
const thread = task
? ensureTaskThread(db, task.id, companyId, task.title)
: ensureApprovalThread(db, approvalId, companyId, summary || approval.payloadSummary);
@@ -181,7 +181,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
try {
if (!taskId) return;
db.getTask(taskId); // validates task exists and belongs to company
db.entityCrud.getTask(taskId); // validates task exists and belongs to company
const thread = findTaskThread(db, taskId, companyId);
if (!thread) return;
@@ -249,7 +249,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
if (status === "succeeded") {
const mode = db.getCompanyExecutionMode(companyId) as ExecutionMode;
if (mode === "ai_cofounder") {
const task = db.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
const completionReason = `@Board review needed: Agent completed work on "${task.title}" — awaiting your review.`;
db.setThreadWaitState({
threadId: thread.id,
@@ -304,7 +304,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
const thread = db.getThread(ws.threadId, companyId);
if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue;
try {
const task = db.getTask(thread.relatedEntityId);
const task = db.entityCrud.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));
@@ -104,7 +104,7 @@ function resolveApprovalAnnouncementChannel(
): { channel: "department" | "project" | "company"; channelTargetId: string | null } | null {
if (requestedByAgentId) {
try {
const requester = db.getAgent(requestedByAgentId);
const requester = db.entityCrud.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.getTask(taskId);
const task = db.entityCrud.getTask(taskId);
if (task.companyId === companyId && task.projectId) {
return { channel: "project", channelTargetId: task.projectId };
}
+1 -1
View File
@@ -197,7 +197,7 @@ export function createWorkerService({
db.releaseWorkspaceLock(run.workspaceId, run.id);
}
if (run.taskId) {
db.releaseTaskLock(run.taskId, run.id);
db.tasks.releaseTaskLock(run.taskId, run.id);
}
}
}
+4 -4
View File
@@ -48,12 +48,12 @@ export function handleWorkerExit(
}
// EXEC-02: Release execution lock on crash recovery
if (run.taskId) {
db.releaseTaskLock(run.taskId, run.id);
db.tasks.releaseTaskLock(run.taskId, run.id);
}
affectedAgentIds.add(run.agentId);
affectedCompanyIds.set(run.agentId, run.companyId);
const chain = db.getChainOfCommand(run.agentId);
const chain = db.agents.getChainOfCommand(run.agentId);
if (chain.length > 0) {
wakeAgentIfPossible(chain[0].id, run.companyId, "report_failed");
}
@@ -98,8 +98,8 @@ export function handleWorkerExit(
if (candidate.workspaceId) {
db.releaseWorkspaceLock(candidate.workspaceId, candidate.id);
}
const task = candidate.taskId ? db.getTask(candidate.taskId) : null;
const agent = db.getAgent(candidate.agentId);
const task = candidate.taskId ? db.entityCrud.getTask(candidate.taskId) : null;
const agent = db.entityCrud.getAgent(candidate.agentId);
const connector = db.getConnector(agent.connectorId);
const { workspace, lockMode } = resolveWorkspaceWithLockMode(db, task, agent, candidate.companyId);
const recoveryCompany = recoverySnapshot.companies.find((company) => company.id === candidate.companyId);
+2 -2
View File
@@ -85,8 +85,8 @@ export interface WorkerServiceDependencies {
dispatchRunToWorker: (params: {
runId: string;
companyId: string;
agent: ReturnType<AppDatabase["getAgent"]>;
task: ReturnType<AppDatabase["getTask"]> | null;
agent: ReturnType<AppDatabase["entityCrud"]["getAgent"]>;
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
connector: ReturnType<AppDatabase["getConnector"]>;
workspace: ReturnType<AppDatabase["getWorkspace"]>;
workspaceMode: "exclusive" | "shared" | "none";
@@ -7,7 +7,7 @@ import type { HandlerContext, HandlerDeps, WorkerMessageEvent } from "./handler-
type RunFinishedEvent = Extract<WorkerMessageEvent, { type: "run-finished" }>;
type RunRecord = ReturnType<HandlerDeps["db"]["getRun"]>;
type TaskRecord = ReturnType<HandlerDeps["db"]["getTask"]>;
type TaskRecord = ReturnType<HandlerDeps["db"]["entityCrud"]["getTask"]>;
export interface RunFinishedContext {
run: RunRecord;
@@ -56,7 +56,7 @@ export function finalizeRun(
let task: TaskRecord | null = null;
if (run.taskId) {
try {
task = db.getTask(run.taskId);
task = db.entityCrud.getTask(run.taskId);
} catch (error) {
logger.warn(
`[finalizeRun] getTask(${run.taskId}) failed; task may have been deleted: ${error instanceof Error ? error.message : String(error)}`,
@@ -97,9 +97,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"]["getAgent"]>;
let agentRecord: ReturnType<HandlerDeps["db"]["entityCrud"]["getAgent"]>;
try {
agentRecord = db.getAgent(run.agentId);
agentRecord = db.entityCrud.getAgent(run.agentId);
} catch (error) {
logger.warn(
`[session] accumulateAndRotateSession: getAgent(${run.agentId}) failed; skipping: ${error instanceof Error ? error.message : String(error)}`,
@@ -107,7 +107,7 @@ export function accumulateAndRotateSession(
return;
}
if (!agentRecord.sessionId) return;
const session = db.getSessionById(agentRecord.sessionId);
const session = db.tasks.getSessionById(agentRecord.sessionId);
if (!session) return;
sessionService.accumulateTokenUsage(
@@ -120,7 +120,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.getSessionById(agentRecord.sessionId);
const updatedSession = db.tasks.getSessionById(agentRecord.sessionId);
if (!updatedSession) return;
const rotationResult = sessionService.evaluateRotation(updatedSession);
if (rotationResult.shouldRotate) {
@@ -154,9 +154,9 @@ export function emitAndNotifyCompletion(
});
}
let updatedAgent: ReturnType<HandlerDeps["db"]["getAgent"]> | null = null;
let updatedAgent: ReturnType<HandlerDeps["db"]["entityCrud"]["getAgent"]> | null = null;
try {
updatedAgent = db.getAgent(run.agentId);
updatedAgent = db.entityCrud.getAgent(run.agentId);
} catch (error) {
logger.warn(
`[emitAndNotifyCompletion] getAgent(${run.agentId}) failed; skipping budget-paused notification: ${error instanceof Error ? error.message : String(error)}`,
@@ -294,7 +294,7 @@ export function triggerSelfWake(
if (hadDeferredWakes || isQuitting() || destroyed) return;
try {
const actionableTasks = db.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]);
const actionableTasks = db.tasks.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]);
const hasWorkToContinue = actionableTasks.length > 0 || hasUnfinishedTeamBuilding(db, run.agentId, run.companyId);
if (!hasWorkToContinue) return;
@@ -305,7 +305,7 @@ export function triggerSelfWake(
);
// Escalate to manager since agent is stuck
try {
const chain = db.getChainOfCommand(run.agentId);
const chain = db.agents.getChainOfCommand(run.agentId);
if (chain.length > 0) {
wakeAgentIfPossible(chain[0].id, run.companyId, "report_stuck");
}
@@ -342,9 +342,9 @@ function hasUnfinishedTeamBuilding(
companyId: string,
): boolean {
try {
const agent = db.getAgent(agentId);
const agent = db.entityCrud.getAgent(agentId);
if (agent.reportsTo !== null) return false;
const activeReports = db
const activeReports = db.agents
.getDirectReports(agentId, companyId)
.filter((r) => r.status !== "pending_approval" && r.status !== "terminated");
if (activeReports.length > 0) return false;
+1 -1
View File
@@ -38,7 +38,7 @@ export async function handleRunLog(
try {
const run = db.getRun(event.runId);
if (run.taskId) {
db.renewTaskLease(run.taskId, run.id);
db.tasks.renewTaskLease(run.taskId, run.id);
}
leaseRenewalMap.set(event.runId, now);
} catch (err) {
@@ -32,7 +32,7 @@ export function handleRunStatus(
try {
const run = db.getRun(event.runId);
if (run.taskId) {
db.renewTaskLease(run.taskId, run.id);
db.tasks.renewTaskLease(run.taskId, run.id);
}
onRunStarted?.(run.id, run.taskId, run.agentId, run.companyId);
} catch (err) {
@@ -11,12 +11,16 @@
export interface EscalationDeps {
db: {
getTaskRetryCount: (taskId: string) => number;
incrementTaskRetryCount: (taskId: string) => void;
resetTaskRetryCount: (taskId: string) => void;
getChainOfCommand: (agentId: string) => Array<{ id: string; name: string }>;
getCeoForCompany: (companyId: string) => { id: string; name: string } | null;
advanceTaskStatus: (taskId: string, status: string, note: string) => unknown;
tasks: {
getTaskRetryCount: (taskId: string) => number;
incrementTaskRetryCount: (taskId: string) => void;
resetTaskRetryCount: (taskId: string) => void;
advanceTaskStatus: (taskId: string, status: string, note: string) => unknown;
};
agents: {
getChainOfCommand: (agentId: string) => Array<{ id: string; name: string }>;
getCeoForCompany: (companyId: string) => { id: string; name: string } | null;
};
};
wakeAgentIfPossible: (agentId: string, companyId: string, trigger: string) => unknown;
notify: (title: string, body: string) => void;
@@ -42,11 +46,11 @@ export function handleFailureEscalation(
scheduleRetry: (fn: () => void, delayMs: number) => void,
): EscalationResult {
const { db, wakeAgentIfPossible, notify, logger } = deps;
const retryCount = db.getTaskRetryCount(run.taskId);
const retryCount = db.tasks.getTaskRetryCount(run.taskId);
if (retryCount < maxRetryAttempts) {
db.incrementTaskRetryCount(run.taskId);
db.advanceTaskStatus(
db.tasks.incrementTaskRetryCount(run.taskId);
db.tasks.advanceTaskStatus(
run.taskId,
"todo",
`Run ${eventStatus} (attempt ${retryCount + 1}/${maxRetryAttempts}). Auto-retrying after ${retryCooldownMs / 1000}s cooldown.`,
@@ -61,14 +65,14 @@ export function handleFailureEscalation(
}
// Retry limit reached — escalate up the chain
db.resetTaskRetryCount(run.taskId);
db.tasks.resetTaskRetryCount(run.taskId);
logger.warn(
`[escalation] Task ${run.taskId} failed ${maxRetryAttempts} times. Escalating to manager.`,
);
let escalated = false;
try {
const chain = db.getChainOfCommand(run.agentId);
const chain = db.agents.getChainOfCommand(run.agentId);
if (chain.length > 0) {
wakeAgentIfPossible(chain[0].id, run.companyId, "report_failed");
escalated = true;
@@ -84,7 +88,7 @@ export function handleFailureEscalation(
if (!escalated) {
try {
const ceo = db.getCeoForCompany(run.companyId);
const ceo = db.agents.getCeoForCompany(run.companyId);
if (ceo) {
wakeAgentIfPossible(ceo.id, run.companyId, "report_failed");
escalated = true;
@@ -120,7 +124,7 @@ export function handleSuccessReset(
logger?: EscalationDeps["logger"],
): void {
try {
db.resetTaskRetryCount(taskId);
db.tasks.resetTaskRetryCount(taskId);
} catch (error) {
logger?.warn(
`[escalation] resetTaskRetryCount failed for task ${taskId}: ${error instanceof Error ? error.message : String(error)}`,