refactor(db): group database domain modules
This commit is contained in:
1 parent
e43a89096d
commit
6da0a58769
160 files changed
+2124
-1999
No files matched your search
@@ -58,7 +58,7 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_
|
||||
const { db, logger, wakeAgentIfPossible } = deps;
|
||||
|
||||
if (ctx.taskId) {
|
||||
const assignedId = db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
const assignedId = db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
if (assignedId) {
|
||||
wakeAgentIfPossible(assignedId, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -67,14 +67,14 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_
|
||||
|
||||
if (!ctx.agentId) return;
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const unassigned = snapshot.tasks
|
||||
.filter((task) => task.companyId === ctx.companyId && !task.assigneeAgentId && ["backlog", "todo"].includes(task.status))
|
||||
.sort((a, b) => (TASK_PRIORITY_SCORE[b.priority] ?? 0) - (TASK_PRIORITY_SCORE[a.priority] ?? 0));
|
||||
if (unassigned.length === 0) return;
|
||||
|
||||
const task = unassigned[0]!;
|
||||
db.tasks.saveTask({
|
||||
db.core.tasks.saveTask({
|
||||
id: task.id,
|
||||
companyId: task.companyId,
|
||||
projectId: task.projectId,
|
||||
@@ -106,7 +106,7 @@ function handleCreateTask({ deps, callbacks, ctx, config }: TypedAutomationActio
|
||||
return;
|
||||
}
|
||||
|
||||
const taskId = db.tasks.saveTask({
|
||||
const taskId = db.core.tasks.saveTask({
|
||||
companyId: ctx.companyId,
|
||||
projectId: ctx.projectId ?? config.projectId ?? null,
|
||||
title,
|
||||
@@ -140,7 +140,7 @@ function handleAgentNotification<K extends "notify_agent" | "send_message">({
|
||||
|
||||
if (channel === "department" && !channelTargetId) {
|
||||
try {
|
||||
const sender = db.entityCrud.getAgent(fromAgentId);
|
||||
const sender = db.core.entity.getAgent(fromAgentId);
|
||||
channelTargetId = sender.department ?? null;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getAgent failed for sender ${fromAgentId}; dropping department target:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -184,7 +184,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi
|
||||
if (!targetAgentId && ctx.agentId) {
|
||||
const triggerType = rule.trigger ?? "";
|
||||
if (triggerType === "run_completed" || triggerType === "run_failed") {
|
||||
const manager = db.agents.getManagerForAgent(ctx.agentId);
|
||||
const manager = db.core.agents.getManagerForAgent(ctx.agentId);
|
||||
targetAgentId = manager?.id ?? ctx.agentId;
|
||||
} else {
|
||||
targetAgentId = ctx.agentId;
|
||||
@@ -197,7 +197,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi
|
||||
}
|
||||
|
||||
function handleCreateApproval({ deps, ctx, config }: TypedAutomationActionExecution<"create_approval">): void {
|
||||
deps.db.approvalsMod.requestApproval({
|
||||
deps.db.core.approvals.requestApproval({
|
||||
companyId: ctx.companyId,
|
||||
relatedTaskId: ctx.taskId ?? null,
|
||||
requestedByAgentId: ctx.agentId ?? null,
|
||||
@@ -219,8 +219,8 @@ function handleUpdateTaskStatus({
|
||||
const { db, publishDomainChanged } = deps;
|
||||
const { handleTaskStatusChange } = callbacks;
|
||||
const newStatus = config.status ?? "todo";
|
||||
const previousTask = db.entityCrud.getTask(ctx.taskId);
|
||||
db.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
|
||||
const previousTask = db.core.entity.getTask(ctx.taskId);
|
||||
db.core.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
|
||||
publishDomainChanged();
|
||||
|
||||
if (previousTask.status !== newStatus && rule.trigger !== "task_status_changed") {
|
||||
@@ -240,19 +240,19 @@ function handleEscalateToManager({
|
||||
|
||||
let agentName = "an agent";
|
||||
try {
|
||||
agentName = db.entityCrud.getAgent(targetAgentId).name;
|
||||
agentName = db.core.entity.getAgent(targetAgentId).name;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getAgent failed for escalation target ${targetAgentId}; using generic label:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
|
||||
let taskTitle = ctx.taskId ?? "unknown";
|
||||
try {
|
||||
if (ctx.taskId) taskTitle = db.entityCrud.getTask(ctx.taskId).title;
|
||||
if (ctx.taskId) taskTitle = db.core.entity.getTask(ctx.taskId).title;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getTask failed for escalation task ${ctx.taskId}; using id as title:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
|
||||
const manager = db.agents.getManagerForAgent(targetAgentId);
|
||||
const manager = db.core.agents.getManagerForAgent(targetAgentId);
|
||||
const escalationBody = config.body ?? `Task "${taskTitle}" needs your attention. ${agentName} has flagged this as requiring manager intervention.`;
|
||||
const wakeTrigger = deriveEscalationWakeTrigger(ctx.newStatus ?? null, rule.trigger ?? null);
|
||||
|
||||
@@ -291,7 +291,7 @@ function handleCrossDepartmentNotify({
|
||||
const targetDepartment = rule.targetDepartment ?? config.department;
|
||||
if (!targetDepartment) return;
|
||||
|
||||
const departmentAgents = db.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
|
||||
const departmentAgents = db.core.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
|
||||
const senderAgentId = ctx.agentId ?? departmentAgents[0]?.id;
|
||||
if (!senderAgentId) return;
|
||||
|
||||
@@ -322,7 +322,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu
|
||||
const offsetMinutes = config.scheduleOffsetMinutes ?? 60;
|
||||
const scheduledAt = config.scheduledAt ?? new Date(Date.now() + offsetMinutes * 60_000).toISOString();
|
||||
|
||||
deps.db.knowledgeMod.saveMeeting({
|
||||
deps.db.core.knowledge.saveMeeting({
|
||||
companyId: ctx.companyId,
|
||||
type: meetingType,
|
||||
title,
|
||||
@@ -334,7 +334,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu
|
||||
}
|
||||
|
||||
function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecution<"create_document">): void {
|
||||
deps.db.documentsMod.saveDocument({
|
||||
deps.db.core.documents.saveDocument({
|
||||
companyId: ctx.companyId,
|
||||
type: config.documentType ?? "status_report",
|
||||
title: config.title ?? "Auto-generated document",
|
||||
@@ -345,13 +345,13 @@ function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecut
|
||||
}
|
||||
|
||||
function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecution<"auto_approve">): void {
|
||||
const snapshot = deps.db.snap.listSnapshot();
|
||||
const snapshot = deps.db.projection.snapshot.listSnapshot();
|
||||
const pendingApprovals = snapshot.approvals.filter(
|
||||
(approval) => approval.companyId === ctx.companyId && approval.state === "pending",
|
||||
);
|
||||
|
||||
for (const approval of pendingApprovals.slice(0, 5)) {
|
||||
deps.db.approvalsMod.decideApproval({
|
||||
deps.db.core.approvals.decideApproval({
|
||||
companyId: ctx.companyId,
|
||||
approvalId: approval.id,
|
||||
state: "approved",
|
||||
@@ -364,7 +364,7 @@ function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecut
|
||||
function handleReassignTask({ deps, ctx }: TypedAutomationActionExecution<"reassign_task">): void {
|
||||
if (!ctx.taskId) return;
|
||||
|
||||
const newAssignee = deps.db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
const newAssignee = deps.db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
if (newAssignee) {
|
||||
deps.wakeAgentIfPossible(newAssignee, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -374,7 +374,7 @@ function handleRunTask({ deps, ctx, config }: TypedAutomationActionExecution<"ru
|
||||
const targetTaskId = config.taskId ?? ctx.taskId;
|
||||
if (!targetTaskId) return;
|
||||
|
||||
const task = deps.db.entityCrud.getTask(targetTaskId);
|
||||
const task = deps.db.core.entity.getTask(targetTaskId);
|
||||
if (!task.assigneeAgentId) return;
|
||||
deps.wakeAgentIfPossible(task.assigneeAgentId, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -30,7 +30,7 @@ function recordReviewFeedbackKnowledge(
|
||||
if (!approval.relatedTaskId) return;
|
||||
if (decision === "approved") return;
|
||||
|
||||
const task = db.entityCrud.getTask(approval.relatedTaskId);
|
||||
const task = db.core.entity.getTask(approval.relatedTaskId);
|
||||
const feedback = approval.decisionNote?.trim() || "Board requested revisions before approval.";
|
||||
const topic = `Review feedback: ${task.title}`;
|
||||
const content = [
|
||||
@@ -39,7 +39,7 @@ function recordReviewFeedbackKnowledge(
|
||||
"Use this review guidance to improve future deliverables for similar work.",
|
||||
].join("\n");
|
||||
|
||||
db.knowledgeMod.saveKnowledgeEntry({
|
||||
db.core.knowledge.saveKnowledgeEntry({
|
||||
companyId: approval.companyId,
|
||||
category: "lesson_learned",
|
||||
topic,
|
||||
@@ -84,7 +84,7 @@ export function handleApprovalResolved(
|
||||
const { db, logger, publishDomainChanged, notify, wakeAgentIfPossible, dispatchBrowserAction } = deps;
|
||||
const { dispatchAutomation, handleTaskStatusChange, runWorkflowPipeline, trackTimer } = callbacks;
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const approval = snapshot.approvals.find((entry) => entry.id === approvalId);
|
||||
if (!approval) return;
|
||||
|
||||
@@ -95,7 +95,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
|
||||
if (approval.type === "hire_agent" && decision === "approved" && approval.relatedAgentId) {
|
||||
const agent = db.entityCrud.getAgent(approval.relatedAgentId);
|
||||
const agent = db.core.entity.getAgent(approval.relatedAgentId);
|
||||
|
||||
if (agent.status === "idle") {
|
||||
// Deterministic default skill kit. The CEO's operating prompt
|
||||
@@ -111,7 +111,7 @@ export function handleApprovalResolved(
|
||||
department: agent.department,
|
||||
});
|
||||
if (assignedSkillIds.length > 0) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "agent.skills_defaulted",
|
||||
@@ -121,7 +121,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "agent.hired",
|
||||
@@ -136,13 +136,13 @@ export function handleApprovalResolved(
|
||||
});
|
||||
wakeAgentIfPossible(agent.id, companyId, "assignment");
|
||||
|
||||
const ceo = db.agents.getCeoForCompany(companyId);
|
||||
const ceo = db.core.agents.getCeoForCompany(companyId);
|
||||
if (ceo && ceo.id !== agent.id) {
|
||||
wakeAgentIfPossible(ceo.id, companyId, "assignment");
|
||||
}
|
||||
|
||||
publishDomainChanged();
|
||||
const hireLocale = db.snap.listSnapshot().locale;
|
||||
const hireLocale = db.projection.snapshot.listSnapshot().locale;
|
||||
notify({
|
||||
title: localizeNotification(hireLocale, "Agent Hired", "新员工入职"),
|
||||
body: localizeNotification(hireLocale, `${agent.name} joined as ${agent.role}`, `${agent.name} 已加入,职位:${agent.role}`),
|
||||
@@ -170,31 +170,31 @@ export function handleApprovalResolved(
|
||||
? "draft"
|
||||
: "in_review";
|
||||
for (const docId of documentIds) {
|
||||
db.documentsMod.updateDocumentStatus(docId, companyId, docStatus);
|
||||
db.core.documents.updateDocumentStatus(docId, companyId, docStatus);
|
||||
}
|
||||
|
||||
if (approval.relatedTaskId) {
|
||||
const task = db.entityCrud.getTask(approval.relatedTaskId);
|
||||
const task = db.core.entity.getTask(approval.relatedTaskId);
|
||||
if (task.assigneeAgentId) {
|
||||
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
|
||||
const agent = db.core.entity.getAgent(task.assigneeAgentId);
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
|
||||
}
|
||||
}
|
||||
if (decision === "approved") {
|
||||
db.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
|
||||
db.core.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "done");
|
||||
db.activityMod.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." });
|
||||
db.core.activity.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." });
|
||||
} else if (decision === "rejected") {
|
||||
recordReviewFeedbackKnowledge(db, approval, decision);
|
||||
db.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`);
|
||||
db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`);
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "in_progress");
|
||||
if (task.assigneeAgentId) {
|
||||
wakeAgentIfPossible(task.assigneeAgentId, companyId, "revision_needed");
|
||||
}
|
||||
} else if (decision === "revision_requested") {
|
||||
db.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`);
|
||||
db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`);
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "in_progress");
|
||||
recordReviewFeedbackKnowledge(db, approval, decision);
|
||||
if (task.assigneeAgentId) {
|
||||
@@ -202,9 +202,9 @@ export function handleApprovalResolved(
|
||||
}
|
||||
}
|
||||
} else if (approval.requestedByAgentId) {
|
||||
const agent = db.entityCrud.getAgent(approval.requestedByAgentId);
|
||||
const agent = db.core.entity.getAgent(approval.requestedByAgentId);
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
|
||||
}
|
||||
if (decision !== "approved") {
|
||||
@@ -219,7 +219,7 @@ export function handleApprovalResolved(
|
||||
const documentIds = extractApprovalDocumentIds(approval.payloadJson);
|
||||
const docStatus = decision === "approved" ? "approved" : "draft";
|
||||
for (const docId of documentIds) {
|
||||
db.documentsMod.updateDocumentStatus(docId, companyId, docStatus);
|
||||
db.core.documents.updateDocumentStatus(docId, companyId, docStatus);
|
||||
}
|
||||
publishDomainChanged();
|
||||
}
|
||||
@@ -235,8 +235,8 @@ export function handleApprovalResolved(
|
||||
};
|
||||
if (payload.policyId) {
|
||||
// Resolve the budget incident that triggered this approval
|
||||
db.budget.resolveBudgetIncident(payload.policyId, companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.budget.resolveBudgetIncident(payload.policyId, companyId);
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "board",
|
||||
action: "budget.override_approved",
|
||||
@@ -250,10 +250,10 @@ export function handleApprovalResolved(
|
||||
}
|
||||
}
|
||||
|
||||
const browserAction = db.social.getBrowserActionByApprovalId(approvalId, companyId);
|
||||
const browserAction = db.core.social.getBrowserActionByApprovalId(approvalId, companyId);
|
||||
if (browserAction) {
|
||||
if (decision === "approved") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "queued",
|
||||
resultSummary: "Approval granted. Dispatching browser action.",
|
||||
errorMessage: null,
|
||||
@@ -263,7 +263,7 @@ export function handleApprovalResolved(
|
||||
logger.error(`[browser] Failed to dispatch approved browser action ${browserAction.id}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
});
|
||||
} else if (decision === "rejected") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "cancelled",
|
||||
resultSummary: "Browser action was rejected during approval.",
|
||||
errorMessage: "Approval rejected by board.",
|
||||
@@ -271,7 +271,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
publishDomainChanged();
|
||||
} else if (decision === "revision_requested") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "approval_required",
|
||||
resultSummary: "Board requested revisions before execution.",
|
||||
errorMessage: "Approval requires revisions before the browser action can run.",
|
||||
@@ -291,7 +291,7 @@ export function handleApprovalResolved(
|
||||
};
|
||||
|
||||
const dispatchSocialAction = () => {
|
||||
const actionId = db.social.saveBrowserAction({
|
||||
const actionId = db.core.social.saveBrowserAction({
|
||||
companyId: approval.companyId,
|
||||
socialAccountId: draft.socialAccountId,
|
||||
agentId: approval.requestedByAgentId ?? "",
|
||||
@@ -333,14 +333,14 @@ export function handleApprovalResolved(
|
||||
const payload = workflowApprovalPayloadSchema.safeParse(JSON.parse(approval.payloadJson));
|
||||
if (payload.success && payload.data.workflowId && payload.data.stepId) {
|
||||
const { workflowId, stepId } = payload.data;
|
||||
const workflow = db.automationMod.getWorkflowRecord(workflowId, companyId);
|
||||
const workflow = db.automation.rules.getWorkflowRecord(workflowId, companyId);
|
||||
if (workflow) {
|
||||
const steps = normalizeWorkflowSteps(JSON.parse(workflow.stepsJson || "[]"));
|
||||
const stepIndex = steps.findIndex((step) => step.id === stepId);
|
||||
if (stepIndex >= 0) {
|
||||
if (decision === "approved") {
|
||||
steps[stepIndex]!.status = "pending";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflow.id,
|
||||
companyId,
|
||||
status: "paused",
|
||||
@@ -354,7 +354,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
} else {
|
||||
steps[stepIndex]!.status = decision === "revision_requested" ? "pending" : "failed";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflow.id,
|
||||
companyId,
|
||||
status: decision === "revision_requested" ? "paused" : "failed",
|
||||
|
||||
@@ -23,7 +23,7 @@ export function matchesEventConfig(
|
||||
if (conditions.previousStatus && ctx.previousStatus && conditions.previousStatus !== ctx.previousStatus) return false;
|
||||
if (conditions.priority) {
|
||||
try {
|
||||
const task = ctx.taskId ? db.entityCrud.getTask(ctx.taskId) : null;
|
||||
const task = ctx.taskId ? db.core.entity.getTask(ctx.taskId) : null;
|
||||
if (task && task.priority !== conditions.priority) return false;
|
||||
// If task was deleted, skip this condition instead of rejecting the rule.
|
||||
} catch (err) {
|
||||
@@ -32,7 +32,7 @@ export function matchesEventConfig(
|
||||
}
|
||||
if (typeof conditions.budgetThresholdPct === "number" && ctx.agentId) {
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(ctx.agentId);
|
||||
const agent = db.core.entity.getAgent(ctx.agentId);
|
||||
if (agent.budgetMonthlyUsd > 0) {
|
||||
const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
|
||||
if (utilization < (conditions.budgetThresholdPct as number) / 100) return false;
|
||||
|
||||
@@ -42,9 +42,9 @@ export function handleTaskStatusChange(
|
||||
const { db, wakeAgentIfPossible } = deps;
|
||||
const { dispatchAutomation } = callbacks;
|
||||
|
||||
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
|
||||
let task: ReturnType<typeof db.core.entity.getTask> | null = null;
|
||||
try {
|
||||
task = db.entityCrud.getTask(taskId);
|
||||
task = db.core.entity.getTask(taskId);
|
||||
} catch (error) {
|
||||
logAppError(deps.logger, error, {
|
||||
kind: "recoverable",
|
||||
@@ -56,7 +56,7 @@ export function handleTaskStatusChange(
|
||||
}
|
||||
if (!task) return;
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: task.assigneeAgentId ?? "board",
|
||||
action: "task.status_changed",
|
||||
@@ -74,7 +74,7 @@ export function handleTaskStatusChange(
|
||||
};
|
||||
try {
|
||||
if (task.assigneeAgentId) {
|
||||
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
|
||||
const agent = db.core.entity.getAgent(task.assigneeAgentId);
|
||||
context.department = agent.department ?? undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -91,7 +91,7 @@ export function handleTaskStatusChange(
|
||||
if (newStatus === "blocked") {
|
||||
dispatchAutomation("task_blocked", context);
|
||||
if (task.assigneeAgentId) {
|
||||
const manager = db.agents.getManagerForAgent(task.assigneeAgentId);
|
||||
const manager = db.core.agents.getManagerForAgent(task.assigneeAgentId);
|
||||
if (manager) {
|
||||
wakeAgentIfPossible(manager.id, companyId, "report_blocked");
|
||||
}
|
||||
@@ -100,14 +100,14 @@ export function handleTaskStatusChange(
|
||||
|
||||
if (newStatus === "done" && task.parentId) {
|
||||
try {
|
||||
const siblings = db.tasks.listChildTasks(task.parentId, companyId);
|
||||
const siblings = db.core.tasks.listChildTasks(task.parentId, companyId);
|
||||
const allDone = siblings.every((entry) => entry.id === taskId ? true : entry.status === "done");
|
||||
if (allDone) {
|
||||
const parent = db.entityCrud.getTask(task.parentId);
|
||||
const parent = db.core.entity.getTask(task.parentId);
|
||||
if (parent.assigneeAgentId) {
|
||||
wakeAgentIfPossible(parent.assigneeAgentId, companyId, "subtask_completed");
|
||||
} else {
|
||||
const assignedId = db.tasks.autoAssignTask(parent.id, companyId);
|
||||
const assignedId = db.core.tasks.autoAssignTask(parent.id, companyId);
|
||||
if (assignedId) {
|
||||
wakeAgentIfPossible(assignedId, companyId, "subtask_completed");
|
||||
}
|
||||
@@ -134,9 +134,9 @@ export function handleTaskCreated(
|
||||
const { db, wakeAgentIfPossible } = deps;
|
||||
const { dispatchAutomation } = callbacks;
|
||||
|
||||
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
|
||||
let task: ReturnType<typeof db.core.entity.getTask> | null = null;
|
||||
try {
|
||||
task = db.entityCrud.getTask(taskId);
|
||||
task = db.core.entity.getTask(taskId);
|
||||
} catch (error) {
|
||||
logAppError(deps.logger, error, {
|
||||
kind: "recoverable",
|
||||
@@ -155,7 +155,7 @@ export function handleTaskCreated(
|
||||
});
|
||||
|
||||
if (!task.assigneeAgentId) {
|
||||
const assignedId = db.tasks.autoAssignTask(taskId, companyId);
|
||||
const assignedId = db.core.tasks.autoAssignTask(taskId, companyId);
|
||||
if (assignedId) {
|
||||
wakeAgentIfPossible(assignedId, companyId, "assignment");
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ function checkRunProducedDeliverable(
|
||||
taskId: string,
|
||||
taskType: string,
|
||||
): boolean {
|
||||
const signals = db.knowledgeMod.collectRunDeliverableSignals(runId, companyId, taskId);
|
||||
const signals = db.core.knowledge.collectRunDeliverableSignals(runId, companyId, taskId);
|
||||
return hasValidDeliverableFor(taskType, signals);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ function createDeliverableReviewApproval(
|
||||
): string {
|
||||
const { db, logger, publishDomainChanged, notify } = deps;
|
||||
const docTitles = docs.map((d) => d.title).join(", ");
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
relatedTaskId: taskId,
|
||||
requestedByAgentId: agentId,
|
||||
@@ -190,10 +190,10 @@ function createDeliverableReviewApproval(
|
||||
impactSummary: reviewerNote ?? "Agent completed work with important deliverables. Awaiting your final review.",
|
||||
payloadJson: JSON.stringify({ documentIds: docs.map((d) => d.id), documents: docs }),
|
||||
});
|
||||
db.tasks.setTaskRequiresUserReview(taskId, false);
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
db.core.tasks.setTaskRequiresUserReview(taskId, false);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(agentId, false, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — awaiting user review.`);
|
||||
}
|
||||
notify({
|
||||
@@ -228,13 +228,13 @@ function createDeliverableReviewApproval(
|
||||
*/
|
||||
function decideTerminalStatus(
|
||||
db: RunCompletionDeps["db"],
|
||||
agent: ReturnType<RunCompletionDeps["db"]["entityCrud"]["getAgent"]>,
|
||||
agent: ReturnType<RunCompletionDeps["db"]["core"]["entity"]["getAgent"]>,
|
||||
companyId: string,
|
||||
): "in_review" | "done" {
|
||||
if (agent.reportsTo !== null) return "in_review";
|
||||
const mode = db.company.getCompanyExecutionMode(companyId);
|
||||
const mode = db.core.company.getCompanyExecutionMode(companyId);
|
||||
if (mode !== "ai_ceo") return "in_review";
|
||||
const settings = db.company.getCompanyApprovalSettings(companyId);
|
||||
const settings = db.core.company.getCompanyApprovalSettings(companyId);
|
||||
return settings?.reviewDeliverables ? "in_review" : "done";
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ function advanceToDone(
|
||||
statusMessage: string,
|
||||
): void {
|
||||
const { db, publishDomainChanged } = deps;
|
||||
db.tasks.advanceTaskStatus(taskId, "done", statusMessage);
|
||||
db.core.tasks.advanceTaskStatus(taskId, "done", statusMessage);
|
||||
callbacks.handleTaskStatusChange(taskId, run.companyId, "in_progress", "done");
|
||||
publishDomainChanged();
|
||||
}
|
||||
@@ -268,22 +268,22 @@ function advanceToInReview(
|
||||
// expected (`ai_cofounder` mode or `reviewDeliverables = true`), so
|
||||
// pre-flagging here is the right semantics — the human IS the
|
||||
// reviewer in those configurations.
|
||||
const agent = db.entityCrud.getAgent(run.agentId);
|
||||
const agent = db.core.entity.getAgent(run.agentId);
|
||||
if (agent.reportsTo === null) {
|
||||
db.tasks.setTaskRequiresUserReview(taskId, true);
|
||||
db.core.tasks.setTaskRequiresUserReview(taskId, true);
|
||||
}
|
||||
db.tasks.advanceTaskStatus(taskId, "in_review", statusMessage);
|
||||
db.core.tasks.advanceTaskStatus(taskId, "in_review", statusMessage);
|
||||
callbacks.handleTaskStatusChange(taskId, run.companyId, "in_progress", "in_review");
|
||||
const company = db.snap.listSnapshot().companies.find((c) => c.id === run.companyId);
|
||||
const company = db.projection.snapshot.listSnapshot().companies.find((c) => c.id === run.companyId);
|
||||
if (company?.reviewDeliverables) {
|
||||
const importantDocs = findImportantDocsForRun(db, run.id, run.companyId);
|
||||
if (importantDocs.length > 0) {
|
||||
db.tasks.setTaskRequiresUserReview(taskId, true);
|
||||
db.core.tasks.setTaskRequiresUserReview(taskId, true);
|
||||
if (agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(agent.id, false, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Paused heartbeat for ${agent.name} — deliverable flagged for review.`);
|
||||
}
|
||||
const chain = db.agents.getChainOfCommand(agent.id);
|
||||
const chain = db.core.agents.getChainOfCommand(agent.id);
|
||||
if (chain.length === 0) {
|
||||
createDeliverableReviewApproval(deps, run.companyId, taskId, run.agentId, importantDocs);
|
||||
}
|
||||
@@ -305,7 +305,7 @@ function advanceSuccessfulRun(
|
||||
taskId: string,
|
||||
statusMessage: string,
|
||||
): "in_review" | "done" {
|
||||
const agent = deps.db.entityCrud.getAgent(run.agentId);
|
||||
const agent = deps.db.core.entity.getAgent(run.agentId);
|
||||
const target = decideTerminalStatus(deps.db, agent, run.companyId);
|
||||
if (target === "done") {
|
||||
advanceToDone(deps, callbacks, run, taskId, statusMessage);
|
||||
@@ -327,9 +327,9 @@ export function processRunCompletion(
|
||||
const { dispatchAutomation, handleTaskStatusChange } = callbacks;
|
||||
|
||||
if (!run.taskId) return;
|
||||
let task: ReturnType<typeof db.entityCrud.getTask> | null = null;
|
||||
let task: ReturnType<typeof db.core.entity.getTask> | null = null;
|
||||
try {
|
||||
task = db.entityCrud.getTask(run.taskId);
|
||||
task = db.core.entity.getTask(run.taskId);
|
||||
} catch (error) {
|
||||
deps.logger.warn(`[automation] processRunCompletion: task ${run.taskId} not found for run ${run.id}:`, error);
|
||||
return;
|
||||
@@ -363,7 +363,7 @@ export function processRunCompletion(
|
||||
// the task in the manager's blocker queue with the diagnostic
|
||||
// comment attached; the manager patches it back to `todo` (or
|
||||
// `cancelled`) after deciding how to proceed.
|
||||
db.tasks.advanceTaskStatus(
|
||||
db.core.tasks.advanceTaskStatus(
|
||||
task.id,
|
||||
"blocked",
|
||||
`${taskType} task run completed but no deliverable artifact was detected. Flagging for manager review.`,
|
||||
@@ -371,8 +371,8 @@ export function processRunCompletion(
|
||||
handleTaskStatusChange(task.id, run.companyId, "in_progress", "blocked");
|
||||
// Wake manager for review
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(run.agentId);
|
||||
const chain = db.agents.getChainOfCommand(agent.id);
|
||||
const agent = db.core.entity.getAgent(run.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(agent.id);
|
||||
if (chain.length > 0) {
|
||||
wakeAgentIfPossible(chain[0].id, run.companyId, "review_needed");
|
||||
}
|
||||
@@ -414,7 +414,7 @@ export function processRunCompletion(
|
||||
runId: run.id,
|
||||
};
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(run.agentId);
|
||||
const agent = db.core.entity.getAgent(run.agentId);
|
||||
automationContext.department = agent.department ?? undefined;
|
||||
} catch (err) {
|
||||
deps.logger.warn(`[automation] getAgent failed for ${run.agentId} during run completion; dropping department from context:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -424,14 +424,14 @@ export function processRunCompletion(
|
||||
dispatchAutomation("run_completed", automationContext);
|
||||
if (task.parentId) {
|
||||
try {
|
||||
const siblings = db.tasks.listChildTasks(task.parentId, run.companyId);
|
||||
const siblings = db.core.tasks.listChildTasks(task.parentId, run.companyId);
|
||||
const allSiblingsDoneOrReview = siblings.every((entry) => entry.status === "done" || entry.status === "in_review");
|
||||
if (allSiblingsDoneOrReview) {
|
||||
const parent = db.entityCrud.getTask(task.parentId);
|
||||
const parent = db.core.entity.getTask(task.parentId);
|
||||
if (parent.assigneeAgentId) {
|
||||
wakeAgentIfPossible(parent.assigneeAgentId, run.companyId, "subtask_completed");
|
||||
} else {
|
||||
const assignedId = db.tasks.autoAssignTask(parent.id, run.companyId);
|
||||
const assignedId = db.core.tasks.autoAssignTask(parent.id, run.companyId);
|
||||
if (assignedId) {
|
||||
wakeAgentIfPossible(assignedId, run.companyId, "subtask_completed");
|
||||
}
|
||||
@@ -452,7 +452,7 @@ export function processRunCompletion(
|
||||
// just read the post-run spend and fire if it crosses 80%.
|
||||
if (isSuccess) {
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(run.agentId);
|
||||
const agent = db.core.entity.getAgent(run.agentId);
|
||||
if (agent.budgetMonthlyUsd > 0) {
|
||||
const utilization = agent.spentMonthlyUsd / agent.budgetMonthlyUsd;
|
||||
if (utilization >= 0.8) {
|
||||
@@ -462,7 +462,7 @@ export function processRunCompletion(
|
||||
});
|
||||
}
|
||||
}
|
||||
const budgetLocale = db.snap.listSnapshot().locale;
|
||||
const budgetLocale = db.projection.snapshot.listSnapshot().locale;
|
||||
checkBudgetThreshold(
|
||||
agent,
|
||||
notify,
|
||||
|
||||
@@ -60,7 +60,7 @@ export function resolveWorkflowStepActor(
|
||||
companyId: string,
|
||||
): AgentRecord | null {
|
||||
if (step.assigneeAgentId) {
|
||||
const agent = db.entityCrud.getAgent(step.assigneeAgentId);
|
||||
const agent = db.core.entity.getAgent(step.assigneeAgentId);
|
||||
if (agent.companyId !== companyId) {
|
||||
throw new Error(`Workflow step assignee ${step.assigneeAgentId} does not belong to company ${companyId}.`);
|
||||
}
|
||||
@@ -68,13 +68,13 @@ export function resolveWorkflowStepActor(
|
||||
}
|
||||
|
||||
if (step.assigneeDepartment) {
|
||||
const idleAgents = db.agents.getIdleAgentsForDepartment(companyId, step.assigneeDepartment);
|
||||
const idleAgents = db.core.agents.getIdleAgentsForDepartment(companyId, step.assigneeDepartment);
|
||||
if (idleAgents.length > 0) {
|
||||
return db.entityCrud.getAgent(idleAgents[0]!.id);
|
||||
return db.core.entity.getAgent(idleAgents[0]!.id);
|
||||
}
|
||||
}
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
return snapshot.agents.find((agent) => agent.companyId === companyId && !agent.reportsTo && agent.status !== "terminated") ?? null;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function runWorkflowPipeline(
|
||||
): Promise<void> {
|
||||
const { db, publishDomainChanged } = deps;
|
||||
|
||||
const workflow = db.automationMod.getWorkflowRecord(workflowId, companyId);
|
||||
const workflow = db.automation.rules.getWorkflowRecord(workflowId, companyId);
|
||||
if (!workflow) {
|
||||
throw new Error("Workflow not found.");
|
||||
}
|
||||
@@ -133,7 +133,7 @@ export async function runWorkflowPipeline(
|
||||
return step;
|
||||
});
|
||||
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflowId,
|
||||
companyId,
|
||||
status: "active",
|
||||
@@ -147,7 +147,7 @@ export async function runWorkflowPipeline(
|
||||
for (let index = resumeFrom; index < steps.length; index += 1) {
|
||||
const step = steps[index]!;
|
||||
step.status = "running";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflowId,
|
||||
companyId,
|
||||
status: "active",
|
||||
@@ -161,7 +161,7 @@ export async function runWorkflowPipeline(
|
||||
const { config, ctx } = buildWorkflowActionContext(companyId, step, actor);
|
||||
|
||||
if (step.requiresApproval) {
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
requestedByAgentId: actor?.id ?? null,
|
||||
relatedTaskId: ctx.taskId ?? null,
|
||||
@@ -171,7 +171,7 @@ export async function runWorkflowPipeline(
|
||||
payloadJson: JSON.stringify({ workflowId, stepId: step.id, stepName: step.name }),
|
||||
});
|
||||
step.status = "pending";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflowId,
|
||||
companyId,
|
||||
status: "paused",
|
||||
@@ -179,7 +179,7 @@ export async function runWorkflowPipeline(
|
||||
stepsJson: JSON.stringify(steps),
|
||||
lastRunAt: new Date().toISOString(),
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: actor?.id ?? "system",
|
||||
action: "workflow.paused_for_approval",
|
||||
@@ -204,7 +204,7 @@ export async function runWorkflowPipeline(
|
||||
ctx,
|
||||
);
|
||||
step.status = "completed";
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: actor?.id ?? "system",
|
||||
action: "workflow.step_completed",
|
||||
@@ -214,7 +214,7 @@ export async function runWorkflowPipeline(
|
||||
});
|
||||
const isLastStep = index === steps.length - 1;
|
||||
const completionStatus = isLastStep ? "completed" : "active";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflowId,
|
||||
companyId,
|
||||
status: completionStatus,
|
||||
@@ -225,7 +225,7 @@ export async function runWorkflowPipeline(
|
||||
publishDomainChanged();
|
||||
} catch (error) {
|
||||
step.status = "failed";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflowId,
|
||||
companyId,
|
||||
status: "failed",
|
||||
@@ -233,7 +233,7 @@ export async function runWorkflowPipeline(
|
||||
stepsJson: JSON.stringify(steps),
|
||||
lastRunAt: new Date().toISOString(),
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: actor?.id ?? "system",
|
||||
action: "workflow.step_failed",
|
||||
|
||||
@@ -107,7 +107,7 @@ export function createAutomationService({
|
||||
|
||||
function evaluateAutomationRules(trigger: string, ctx: AutomationEventContext) {
|
||||
try {
|
||||
const rules = db.automationMod.listActiveAutomationRules(ctx.companyId, trigger);
|
||||
const rules = db.automation.rules.listActiveAutomationRules(ctx.companyId, trigger);
|
||||
for (const rule of rules) {
|
||||
if (!matchesConditions(db, rule, ctx, logger)) continue;
|
||||
const executionKey = JSON.stringify({
|
||||
@@ -127,7 +127,7 @@ export function createAutomationService({
|
||||
activeAutomationExecutions.add(executionKey);
|
||||
try {
|
||||
executeAutomationAction(actionDeps, actionCallbacks, rule, ctx);
|
||||
db.automationMod.recordAutomationExecution(
|
||||
db.automation.rules.recordAutomationExecution(
|
||||
rule.id,
|
||||
ctx.companyId,
|
||||
rule.name,
|
||||
@@ -145,7 +145,7 @@ export function createAutomationService({
|
||||
logger.info(`[automation] Fired rule "${rule.name}" (${trigger} → ${rule.action})`);
|
||||
} catch (error) {
|
||||
logger.error(`[automation] Failed to execute action for rule "${rule.name}": ${error instanceof Error ? error.message : String(error)}`);
|
||||
db.automationMod.recordAutomationExecution(
|
||||
db.automation.rules.recordAutomationExecution(
|
||||
rule.id,
|
||||
ctx.companyId,
|
||||
rule.name,
|
||||
@@ -164,7 +164,7 @@ export function createAutomationService({
|
||||
|
||||
function evaluateWorkflowTriggers(trigger: string, ctx: AutomationEventContext) {
|
||||
try {
|
||||
const workflows = db.automationMod.listTriggerableWorkflows(ctx.companyId, trigger);
|
||||
const workflows = db.automation.rules.listTriggerableWorkflows(ctx.companyId, trigger);
|
||||
for (const workflow of workflows) {
|
||||
if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx, logger)) continue;
|
||||
const executionKey = JSON.stringify({
|
||||
|
||||
Reference in new issue
Block a user