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
@@ -6,7 +6,7 @@ function safeGetAgent(db: AppDatabase, agentId: string | null | undefined) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return db.entityCrud.getAgent(agentId);
|
||||
return db.core.entity.getAgent(agentId);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("Agent not found:")) {
|
||||
return null;
|
||||
@@ -46,14 +46,14 @@ export function hasManagementAuthority(agent: { role: string; title: string; dep
|
||||
}
|
||||
|
||||
function hasOrganizationPermission(db: AppDatabase, agentId: string) {
|
||||
return db.agents.hasAgentPermission(agentId, "organization_manage");
|
||||
return db.core.agents.hasAgentPermission(agentId, "organization_manage");
|
||||
}
|
||||
|
||||
function managesAgent(db: AppDatabase, managerId: string, targetAgentId: string): boolean {
|
||||
if (managerId === targetAgentId) {
|
||||
return true;
|
||||
}
|
||||
return db.agents.getChainOfCommand(targetAgentId).some((agent) => agent.id === managerId);
|
||||
return db.core.agents.getChainOfCommand(targetAgentId).some((agent) => agent.id === managerId);
|
||||
}
|
||||
|
||||
export function canControlAgent(db: AppDatabase, companyId: string, actorAgentId: string, targetAgentId: string): boolean {
|
||||
@@ -96,7 +96,7 @@ export function canRequestHire(db: AppDatabase, companyId: string, agentId: stri
|
||||
if (canManageOrganization(db, companyId, agentId)) {
|
||||
return true;
|
||||
}
|
||||
if (db.agents.getDirectReports(agentId, companyId).length > 0) {
|
||||
if (db.core.agents.getDirectReports(agentId, companyId).length > 0) {
|
||||
return true;
|
||||
}
|
||||
return hasManagementAuthority(agent);
|
||||
@@ -154,9 +154,9 @@ function getDepartmentLeadCandidateId(
|
||||
.filter((candidate) => candidate.id !== excludeAgentId)
|
||||
.map((candidate) => ({
|
||||
id: candidate.id,
|
||||
hasReports: db.agents.getDirectReports(candidate.id, companyId).length > 0,
|
||||
hasReports: db.core.agents.getDirectReports(candidate.id, companyId).length > 0,
|
||||
leadershipScore: hasManagementAuthority(candidate) ? 1 : 0,
|
||||
chainDepth: db.agents.getChainOfCommand(candidate.id).length,
|
||||
chainDepth: db.core.agents.getChainOfCommand(candidate.id).length,
|
||||
createdAt: candidate.createdAt,
|
||||
}))
|
||||
.filter((candidate) => candidate.hasReports || candidate.leadershipScore > 0)
|
||||
@@ -187,7 +187,7 @@ export function resolveHireReportsTo(
|
||||
}
|
||||
|
||||
if (!department) {
|
||||
return requester?.id ?? db.agents.getCeoForCompany(companyId)?.id ?? null;
|
||||
return requester?.id ?? db.core.agents.getCeoForCompany(companyId)?.id ?? null;
|
||||
}
|
||||
|
||||
if (requester && (requester.department === department || !requester.reportsTo)) {
|
||||
@@ -203,7 +203,7 @@ export function resolveHireReportsTo(
|
||||
return requester.id;
|
||||
}
|
||||
|
||||
return db.agents.getCeoForCompany(companyId)?.id ?? null;
|
||||
return db.core.agents.getCeoForCompany(companyId)?.id ?? null;
|
||||
}
|
||||
|
||||
function normalizeComparableText(value: string): string {
|
||||
@@ -281,7 +281,7 @@ export function canAssignTask(db: AppDatabase, companyId: string, actorAgentId:
|
||||
if (!targetAgentId || targetAgentId === actorAgentId) {
|
||||
return true;
|
||||
}
|
||||
if (!db.settings.belongsToCompany("agents", targetAgentId, companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("agents", targetAgentId, companyId)) {
|
||||
return false;
|
||||
}
|
||||
if (canManageOrganization(db, companyId, actorAgentId)) {
|
||||
|
||||
@@ -51,7 +51,7 @@ export function retireDeletedArtifacts(
|
||||
deleted: string[],
|
||||
): ProjectionOutcome[] {
|
||||
if (deleted.length === 0) return [];
|
||||
const archived = db.documentsMod.archiveWorkProductsByExternalPaths({
|
||||
const archived = db.core.documents.archiveWorkProductsByExternalPaths({
|
||||
companyId: ctx.companyId,
|
||||
workspaceId: ctx.workspaceId,
|
||||
provider: "filesystem",
|
||||
@@ -97,24 +97,24 @@ function upsertFilesystemArtifact(
|
||||
const title = lastSlash >= 0 ? relativePath.slice(lastSlash + 1) : relativePath;
|
||||
const provider = "filesystem";
|
||||
const resolvedWorkspaceId =
|
||||
ctx.workspaceId ?? db.workspacesMod.findWorkspaceIdByLocalPath(ctx.companyId, workspacePath);
|
||||
const existing = db.documentsMod.findWorkProductByExternalId(
|
||||
ctx.workspaceId ?? db.core.workspaces.findWorkspaceIdByLocalPath(ctx.companyId, workspacePath);
|
||||
const existing = db.core.documents.findWorkProductByExternalId(
|
||||
ctx.companyId,
|
||||
provider,
|
||||
relativePath,
|
||||
resolvedWorkspaceId,
|
||||
);
|
||||
if (existing) {
|
||||
const patch: Parameters<typeof db.documentsMod.updateWorkProduct>[2] = {
|
||||
const patch: Parameters<typeof db.core.documents.updateWorkProduct>[2] = {
|
||||
title,
|
||||
status: "active",
|
||||
lastTouchedByRunId: ctx.runId,
|
||||
};
|
||||
if (resolvedWorkspaceId) patch.workspaceId = resolvedWorkspaceId;
|
||||
db.documentsMod.updateWorkProduct(existing.id, ctx.companyId, patch);
|
||||
db.core.documents.updateWorkProduct(existing.id, ctx.companyId, patch);
|
||||
return { kind: "updated", workProductId: existing.id, relativePath };
|
||||
}
|
||||
const workProductId = db.documentsMod.createWorkProduct({
|
||||
const workProductId = db.core.documents.createWorkProduct({
|
||||
companyId: ctx.companyId,
|
||||
taskId: ctx.taskId,
|
||||
workspaceId: resolvedWorkspaceId,
|
||||
|
||||
@@ -45,7 +45,7 @@ export class CostWriter {
|
||||
);
|
||||
}
|
||||
|
||||
this.deps.db.runs.recordCostEntry({
|
||||
this.deps.db.core.runs.recordCostEntry({
|
||||
runId: input.runId,
|
||||
companyId: input.companyId,
|
||||
agentId: input.agentId,
|
||||
|
||||
@@ -113,9 +113,9 @@ function checkOperationalApprovalBlock(
|
||||
toolName: string,
|
||||
): BeforeToolCallRpcResult {
|
||||
try {
|
||||
const blocking = deps.db.approvalsMod.getBlockingApproval(ctx.companyId, ctx.agentId, ctx.taskId);
|
||||
const blocking = deps.db.core.approvals.getBlockingApproval(ctx.companyId, ctx.agentId, ctx.taskId);
|
||||
if (!blocking) return { block: false, reason: null };
|
||||
const companyMode = deps.db.company.getCompanyExecutionMode(ctx.companyId);
|
||||
const companyMode = deps.db.core.company.getCompanyExecutionMode(ctx.companyId);
|
||||
const cofounderCheckpointBlocks =
|
||||
companyMode === "ai_cofounder" && isCofounderMandatoryApproval(blocking.type);
|
||||
if (!isOperationalApproval(blocking.type) && !cofounderCheckpointBlocks) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* `api-server/routes-approvals.ts`: validate → enforce the
|
||||
* `approve_ceo_strategy` ordering gate → request approval → auto-
|
||||
* approve or notify. Both the tool path and the HTTP path funnel
|
||||
* through the same `db.approvalsMod.requestApproval` + `shouldAutoApprove`
|
||||
* through the same `db.core.approvals.requestApproval` + `shouldAutoApprove`
|
||||
* plumbing, so the board sees identical rows regardless of entry point.
|
||||
*
|
||||
* No fallbacks: malformed input throws UserInputError, DB failures
|
||||
@@ -71,7 +71,7 @@ export function handleFileApprovalToolCall(
|
||||
// otherwise re-open the exact loop the HTTP gate was written to
|
||||
// close.
|
||||
if (input.type === "approve_ceo_strategy") {
|
||||
const companyHireApprovals = db.approvalsMod.listCompanyApprovalsByType(companyId, "hire_agent");
|
||||
const companyHireApprovals = db.core.approvals.listCompanyApprovalsByType(companyId, "hire_agent");
|
||||
if (companyHireApprovals.length === 0) {
|
||||
throw new UserInputError({
|
||||
code: "file_approval.strategy.before_hires",
|
||||
@@ -79,7 +79,7 @@ export function handleFileApprovalToolCall(
|
||||
"approve_ceo_strategy requires at least one hire_agent approval to exist first. File the intended hires, then the strategy.",
|
||||
});
|
||||
}
|
||||
const existingStrategy = db.approvalsMod
|
||||
const existingStrategy = db.core.approvals
|
||||
.listCompanyApprovalsByType(companyId, "approve_ceo_strategy")
|
||||
.find((a) => a.state === "pending" || a.state === "approved");
|
||||
if (existingStrategy) {
|
||||
@@ -111,7 +111,7 @@ export function handleFileApprovalToolCall(
|
||||
|
||||
let approvalId: string;
|
||||
try {
|
||||
approvalId = db.approvalsMod.requestApproval({
|
||||
approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
type: input.type,
|
||||
relatedTaskId: input.relatedTaskId ?? null,
|
||||
@@ -131,7 +131,7 @@ export function handleFileApprovalToolCall(
|
||||
}
|
||||
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: requesterId,
|
||||
action: "approval.created",
|
||||
|
||||
@@ -137,7 +137,7 @@ export function handleHireAgentToolCall(
|
||||
resolvedReportsTo &&
|
||||
resolvedReportsTo !== requesterId &&
|
||||
!canManageOrganization(db, companyId, requesterId) &&
|
||||
!db.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === requesterId)
|
||||
!db.core.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === requesterId)
|
||||
) {
|
||||
throw new UserInputError({
|
||||
code: "hire_agent.routing.not_in_chain",
|
||||
@@ -149,7 +149,7 @@ export function handleHireAgentToolCall(
|
||||
|
||||
const connectorRecord = (() => {
|
||||
try {
|
||||
return db.connectorsCrud.getConnector(input.connectorId);
|
||||
return db.core.connectors.getConnector(input.connectorId);
|
||||
} catch (error) {
|
||||
throw new UserInputError({
|
||||
code: "hire_agent.connector.missing",
|
||||
@@ -167,7 +167,7 @@ export function handleHireAgentToolCall(
|
||||
});
|
||||
}
|
||||
|
||||
const existingAgents = db.agents.listExistingAgentsForHireCheck(companyId);
|
||||
const existingAgents = db.core.agents.listExistingAgentsForHireCheck(companyId);
|
||||
const duplicateByName = existingAgents.find(
|
||||
(a) => a.name.toLowerCase() === input.name.toLowerCase(),
|
||||
);
|
||||
@@ -178,7 +178,7 @@ export function handleHireAgentToolCall(
|
||||
});
|
||||
}
|
||||
|
||||
const pendingHires = db.approvalsMod.listPendingHireApprovalPayloads(companyId);
|
||||
const pendingHires = db.core.approvals.listPendingHireApprovalPayloads(companyId);
|
||||
for (const pending of pendingHires) {
|
||||
try {
|
||||
const payload = pending.payloadJson ? JSON.parse(pending.payloadJson) as Record<string, unknown> : {};
|
||||
@@ -199,7 +199,7 @@ export function handleHireAgentToolCall(
|
||||
const managerLabel = resolvedReportsTo
|
||||
? (() => {
|
||||
try {
|
||||
return db.entityCrud.getAgent(resolvedReportsTo).name;
|
||||
return db.core.entity.getAgent(resolvedReportsTo).name;
|
||||
} catch {
|
||||
return resolvedReportsTo;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ export function handleHireAgentToolCall(
|
||||
}
|
||||
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: requesterId,
|
||||
action: "agent.hire_requested",
|
||||
|
||||
@@ -235,7 +235,7 @@ export class RuntimeBridge {
|
||||
*/
|
||||
private resolveAgentName(agentId: string): string {
|
||||
try {
|
||||
const name = this.deps.db.agents.findAgentName(agentId);
|
||||
const name = this.deps.db.core.agents.findAgentName(agentId);
|
||||
if (name && name.length > 0) return name;
|
||||
} catch (error) {
|
||||
this.deps.logger.warn(
|
||||
@@ -434,11 +434,11 @@ export class RuntimeBridge {
|
||||
// `?unreadOnly=true`, so agents posted status comments instead of
|
||||
// replies. The explicit read endpoint is the sole signal.
|
||||
const store = this.getOrCreateStore(payload.taskSessionId);
|
||||
const inboxRecords = this.deps.db.messages.listAgentInboxUnread(payload.companyId, payload.agentId);
|
||||
const inboxRecords = this.deps.db.core.messages.listAgentInboxUnread(payload.companyId, payload.agentId);
|
||||
if (inboxRecords.length > 0) {
|
||||
this.deps.db.withTransaction(() => {
|
||||
for (const record of inboxRecords) {
|
||||
const claimed = this.deps.db.inboxProjections.claimProjection(record.id, payload.taskSessionId);
|
||||
const claimed = this.deps.db.projection.inbox.claimProjection(record.id, payload.taskSessionId);
|
||||
if (!claimed) continue;
|
||||
// `projectInboundMessage` allocates a monotonic pi-timestamp
|
||||
// and indexes the correlation under `record.id`. Two rows
|
||||
|
||||
@@ -52,7 +52,7 @@ export class RuntimeThreadProjector {
|
||||
try {
|
||||
const body = extractTextBody(message.content);
|
||||
const actor = this.resolveThreadEventActor(envelope, message);
|
||||
this.deps.db.threads.addThreadEvent({
|
||||
this.deps.db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: actor.actorId,
|
||||
actorType: actor.actorType,
|
||||
@@ -91,7 +91,7 @@ export class RuntimeThreadProjector {
|
||||
: record.senderKind === "system"
|
||||
? "system"
|
||||
: "agent";
|
||||
this.deps.db.threads.addThreadEvent({
|
||||
this.deps.db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: record.fromAgentId,
|
||||
actorType,
|
||||
@@ -116,7 +116,7 @@ export class RuntimeThreadProjector {
|
||||
private resolveThreadForRun(envelope: RuntimeThreadEnvelope) {
|
||||
if (envelope.taskId) {
|
||||
try {
|
||||
const task = this.deps.db.entityCrud.getTask(envelope.taskId);
|
||||
const task = this.deps.db.core.entity.getTask(envelope.taskId);
|
||||
return ensureTaskThread(this.deps.db, envelope.taskId, envelope.companyId, task.title);
|
||||
} catch (error) {
|
||||
this.deps.logger.warn(
|
||||
|
||||
@@ -18,7 +18,7 @@ export class SqliteSessionStore {
|
||||
) {}
|
||||
|
||||
loadMessages(): AgentMessage[] {
|
||||
const rows = this.db.sessionEntries.listActive(this.taskSessionId);
|
||||
const rows = this.db.projection.sessionEntries.listActive(this.taskSessionId);
|
||||
const result: AgentMessage[] = [];
|
||||
for (const row of rows) {
|
||||
if (row.kind === "message") {
|
||||
@@ -29,7 +29,7 @@ export class SqliteSessionStore {
|
||||
}
|
||||
|
||||
appendMessage(message: AgentMessage): void {
|
||||
this.db.sessionEntries.append({
|
||||
this.db.projection.sessionEntries.append({
|
||||
taskSessionId: this.taskSessionId,
|
||||
kind: "message",
|
||||
role: (message as { role?: string }).role ?? null,
|
||||
|
||||
@@ -12,7 +12,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
const targetAgentId = requireParam(params, "agentId");
|
||||
const targetAgent = (() => {
|
||||
try {
|
||||
return db.entityCrud.getAgent(targetAgentId);
|
||||
return db.core.entity.getAgent(targetAgentId);
|
||||
} catch (err) {
|
||||
log.warn(`[api/agent-control] getAgent failed for ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
|
||||
return null;
|
||||
@@ -31,7 +31,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
});
|
||||
const parsed = wakeSchema.safeParse(body);
|
||||
const reason = parsed.success ? parsed.data.reason : "peer_request";
|
||||
const alreadyActive = db.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId });
|
||||
const alreadyActive = db.core.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId });
|
||||
const runId = onWakeAgent(targetAgentId, token.companyId, reason);
|
||||
if (runId) {
|
||||
server.json(res, 200, { ok: true, runId, message: `Wake started for agent ${targetAgentId}` });
|
||||
@@ -52,7 +52,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
const targetAgentId = requireParam(params, "agentId");
|
||||
let targetAgent;
|
||||
try {
|
||||
targetAgent = db.entityCrud.getAgent(targetAgentId);
|
||||
targetAgent = db.core.entity.getAgent(targetAgentId);
|
||||
} catch (err) {
|
||||
log.warn(`[api/agent-control] getAgent failed for heartbeat ${targetAgentId}:`, err instanceof Error ? err.message : String(err));
|
||||
targetAgent = null;
|
||||
@@ -65,7 +65,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Current agent is not allowed to trigger heartbeat for this agent" });
|
||||
return;
|
||||
}
|
||||
const alreadyActive = db.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId });
|
||||
const alreadyActive = db.core.runs.hasActiveRun({ companyId: token.companyId, agentId: targetAgentId });
|
||||
const runId = onWakeAgent(targetAgentId, token.companyId, "peer_request");
|
||||
if (runId) {
|
||||
server.json(res, 200, { ok: true, runId, message: `Heartbeat triggered for agent ${targetAgentId}` });
|
||||
@@ -86,7 +86,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
const agentId = requireParam(params, "agentId");
|
||||
let agent;
|
||||
try {
|
||||
agent = db.entityCrud.getAgent(agentId);
|
||||
agent = db.core.entity.getAgent(agentId);
|
||||
} catch (error) {
|
||||
log.warn(`[api/agent-skills:GET] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
agent = null;
|
||||
@@ -95,9 +95,9 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
server.json(res, 404, { error: "Agent not found in your company" });
|
||||
return;
|
||||
}
|
||||
const skillIds = db.skills.listAgentSkillAssignments(agentId);
|
||||
const skillIds = db.core.skills.listAgentSkillAssignments(agentId);
|
||||
const skills = skillIds
|
||||
.map((sid) => db.skills.getCompanySkill(token.companyId, sid))
|
||||
.map((sid) => db.core.skills.getCompanySkill(token.companyId, sid))
|
||||
.filter((s): s is NonNullable<typeof s> => s != null);
|
||||
server.json(res, 200, skills.map((s) => ({
|
||||
id: s.id,
|
||||
@@ -118,7 +118,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
}
|
||||
let agent;
|
||||
try {
|
||||
agent = db.entityCrud.getAgent(agentId);
|
||||
agent = db.core.entity.getAgent(agentId);
|
||||
} catch (error) {
|
||||
log.warn(`[api/agent-skills:POST] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
agent = null;
|
||||
@@ -127,12 +127,12 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
server.json(res, 404, { error: "Agent not found in your company" });
|
||||
return;
|
||||
}
|
||||
const skill = db.skills.getCompanySkill(token.companyId, parsed.data.skillId);
|
||||
const skill = db.core.skills.getCompanySkill(token.companyId, parsed.data.skillId);
|
||||
if (!skill) {
|
||||
server.json(res, 404, { error: "Skill not found in your company" });
|
||||
return;
|
||||
}
|
||||
db.skills.assignSkillToAgent(agentId, parsed.data.skillId);
|
||||
db.core.skills.assignSkillToAgent(agentId, parsed.data.skillId);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { ok: true, message: `Skill "${skill.name}" assigned to agent ${agent.name}` });
|
||||
});
|
||||
@@ -142,7 +142,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
const skillId = requireParam(params, "skillId");
|
||||
let agent;
|
||||
try {
|
||||
agent = db.entityCrud.getAgent(agentId);
|
||||
agent = db.core.entity.getAgent(agentId);
|
||||
} catch (error) {
|
||||
log.warn(`[api/agent-skills:DELETE] getAgent(${agentId}) failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
agent = null;
|
||||
@@ -151,7 +151,7 @@ export function registerAgentControlRoutes(server: AgentApiServer) {
|
||||
server.json(res, 404, { error: "Agent not found in your company" });
|
||||
return;
|
||||
}
|
||||
db.skills.removeSkillFromAgent(agentId, skillId);
|
||||
db.core.skills.removeSkillFromAgent(agentId, skillId);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { ok: true, message: `Skill removed from agent ${agent.name}` });
|
||||
});
|
||||
|
||||
@@ -3,23 +3,23 @@ import type { AgentApiServer } from "./server-core";
|
||||
export function registerAgentStandupRoute(server: AgentApiServer): void {
|
||||
const db = server.db;
|
||||
server.addRoute("GET", "/api/agents/me/standup", (_req, res, _params, token) => {
|
||||
const agent = db.entityCrud.getAgent(token.agentId);
|
||||
const agent = db.core.entity.getAgent(token.agentId);
|
||||
if (!agent || agent.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const directReports = db.agents.listDirectReportsForStandup(token.companyId, token.agentId);
|
||||
const directReports = db.core.agents.listDirectReportsForStandup(token.companyId, token.agentId);
|
||||
|
||||
const myTasks = db.tasks.listAssigneeTasksForStandup(token.companyId, token.agentId);
|
||||
const myTasks = db.core.tasks.listAssigneeTasksForStandup(token.companyId, token.agentId);
|
||||
|
||||
const reportTasks = db.tasks.listTasksForAssignees(
|
||||
const reportTasks = db.core.tasks.listTasksForAssignees(
|
||||
token.companyId,
|
||||
directReports.map((r) => r.id),
|
||||
);
|
||||
|
||||
const reportStatusRows = directReports.length > 0
|
||||
? db.agents.listReportStatusesForStandup(token.companyId, token.agentId)
|
||||
? db.core.agents.listReportStatusesForStandup(token.companyId, token.agentId)
|
||||
: [];
|
||||
|
||||
const tasksAwaitingMyReview = reportTasks.filter((t) => t.status === "in_review");
|
||||
@@ -33,20 +33,20 @@ export function registerAgentStandupRoute(server: AgentApiServer): void {
|
||||
...reportTasks.map((t) => t.id),
|
||||
]);
|
||||
const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
|
||||
const recentComments = db.activityMod.listRecentTaskComments([...allRelevantTaskIds], twoHoursAgo, 20);
|
||||
const recentComments = db.core.activity.listRecentTaskComments([...allRelevantTaskIds], twoHoursAgo, 20);
|
||||
|
||||
const myPendingApprovals = db.approvalsMod.listAgentPendingApprovals(token.companyId, token.agentId);
|
||||
const myResolvedApprovals = db.approvalsMod.listAgentResolvedApprovals(token.companyId, token.agentId, 5);
|
||||
const myPendingApprovals = db.core.approvals.listAgentPendingApprovals(token.companyId, token.agentId);
|
||||
const myResolvedApprovals = db.core.approvals.listAgentResolvedApprovals(token.companyId, token.agentId, 5);
|
||||
|
||||
// Unread inbox lives in the standup briefing as the agent's
|
||||
// canonical view of pending communication. Pre-fix the agent had
|
||||
// to make a second API call to /messages?unreadOnly=true that the
|
||||
// model frequently skipped, so messages from peers and managers
|
||||
// sat dark while the agent posted heartbeat status comments.
|
||||
const unreadInbox = db.messages.listAgentInboxUnread(token.companyId, token.agentId);
|
||||
const unreadInbox = db.core.messages.listAgentInboxUnread(token.companyId, token.agentId);
|
||||
const senderIds = new Set(unreadInbox.map((m) => m.fromAgentId));
|
||||
const senderNames = senderIds.size > 0
|
||||
? new Map(db.agents.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name]))
|
||||
? new Map(db.core.agents.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name]))
|
||||
: new Map<string, string>();
|
||||
|
||||
// Recent outputs the AGENT itself produced — closes the
|
||||
@@ -59,10 +59,10 @@ export function registerAgentStandupRoute(server: AgentApiServer): void {
|
||||
// surface keeps the briefing readable.
|
||||
const RECENT_OUTPUT_LIMIT = 20;
|
||||
const myTaskIds = new Set(myTasks.map((t) => t.id));
|
||||
const recentDocuments = db.documentsMod.listDocuments(token.companyId)
|
||||
const recentDocuments = db.core.documents.listDocuments(token.companyId)
|
||||
.filter((doc) => doc.authorAgentId === token.agentId)
|
||||
.slice(0, RECENT_OUTPUT_LIMIT);
|
||||
const recentWorkProducts = db.documentsMod.listWorkProductsForCompany(token.companyId)
|
||||
const recentWorkProducts = db.core.documents.listWorkProductsForCompany(token.companyId)
|
||||
.filter((wp) => wp.taskId !== null && myTaskIds.has(wp.taskId))
|
||||
.slice(0, RECENT_OUTPUT_LIMIT);
|
||||
|
||||
@@ -71,7 +71,7 @@ export function registerAgentStandupRoute(server: AgentApiServer): void {
|
||||
...myTasks.map<[string, string]>((t) => [t.id, t.title]),
|
||||
...reportTasks.map<[string, string]>((t) => [t.id, t.title]),
|
||||
]);
|
||||
const socialAccounts = db.social.listSocialAccounts(token.companyId);
|
||||
const socialAccounts = db.core.social.listSocialAccounts(token.companyId);
|
||||
|
||||
server.json(res, 200, {
|
||||
agent: { id: agent.id, name: agent.name, role: agent.role, status: agent.status },
|
||||
|
||||
@@ -24,9 +24,9 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
const callbacks = server.callbacks;
|
||||
const onDomainChanged = server.onDomainChanged;
|
||||
server.addRoute("GET", "/api/agents/me", (_req, res, _params, token) => {
|
||||
const agent = db.entityCrud.getAgent(token.agentId);
|
||||
const chainOfCommand = db.agents.getChainOfCommand(token.agentId);
|
||||
const directReports = db.agents.getDirectReports(token.agentId, token.companyId);
|
||||
const agent = db.core.entity.getAgent(token.agentId);
|
||||
const chainOfCommand = db.core.agents.getChainOfCommand(token.agentId);
|
||||
const directReports = db.core.agents.getDirectReports(token.agentId, token.companyId);
|
||||
server.json(res, 200, { agent, chainOfCommand, directReports });
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
// and the standup module is registered separately in api-server/index.ts.
|
||||
|
||||
server.addRoute("GET", "/api/agents/me/metrics", (_req, res, _params, token) => {
|
||||
const companyMetrics = db.company.getCompanyMetrics(token.companyId, 30);
|
||||
const companyMetrics = db.core.company.getCompanyMetrics(token.companyId, 30);
|
||||
const agentMetric = companyMetrics.agentMetrics.find(m => m.agentId === token.agentId);
|
||||
if (!agentMetric) {
|
||||
server.json(res, 404, { error: "Metrics not found" });
|
||||
@@ -47,12 +47,12 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("GET", "/api/agents/:agentId/metrics", (_req, res, params, token) => {
|
||||
const agentId = requireParam(params, "agentId");
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (!agent || agent.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
const companyMetrics = db.company.getCompanyMetrics(token.companyId, 30);
|
||||
const companyMetrics = db.core.company.getCompanyMetrics(token.companyId, 30);
|
||||
const agentMetric = companyMetrics.agentMetrics.find((metric) => metric.agentId === agentId);
|
||||
if (!agentMetric) {
|
||||
server.json(res, 404, { error: "Metrics not found" });
|
||||
@@ -64,7 +64,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
server.addRoute("GET", "/api/agents/:agentId", (_req, res, params, token) => {
|
||||
let agent;
|
||||
try {
|
||||
agent = db.entityCrud.getAgent(requireParam(params, "agentId"));
|
||||
agent = db.core.entity.getAgent(requireParam(params, "agentId"));
|
||||
} catch (err) {
|
||||
log.warn(`[api/agents] getAgent failed:`, err instanceof Error ? err.message : String(err));
|
||||
server.json(res, 404, { error: "Agent not found" });
|
||||
@@ -74,12 +74,12 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
server.json(res, 404, { error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
const directReports = db.agents.getDirectReports(agent.id, token.companyId);
|
||||
const directReports = db.core.agents.getDirectReports(agent.id, token.companyId);
|
||||
const reportsTo = agent.reportsTo;
|
||||
const manager = reportsTo
|
||||
? (() => {
|
||||
try {
|
||||
return db.entityCrud.getAgent(reportsTo);
|
||||
return db.core.entity.getAgent(reportsTo);
|
||||
} catch (error) {
|
||||
log.warn(`[api/agents] manager lookup failed for ${reportsTo}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return null;
|
||||
@@ -98,7 +98,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const agents = db.agents.listAgentsForApi(params.companyId);
|
||||
const agents = db.core.agents.listAgentsForApi(params.companyId);
|
||||
server.json(res, 200, agents);
|
||||
});
|
||||
|
||||
@@ -183,14 +183,14 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
resolvedReportsTo &&
|
||||
resolvedReportsTo !== token.agentId &&
|
||||
!canManageOrganization(db, companyId, token.agentId) &&
|
||||
!db.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId)
|
||||
!db.core.agents.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId)
|
||||
) {
|
||||
server.json(res, 403, { error: "Requested hire must report to you or a manager in your reporting chain." });
|
||||
return;
|
||||
}
|
||||
const connectorRecord = (() => {
|
||||
try {
|
||||
return db.connectorsCrud.getConnector(input.connectorId);
|
||||
return db.core.connectors.getConnector(input.connectorId);
|
||||
} catch (err) {
|
||||
log.warn(`[api/agents] getConnector failed for ${input.connectorId}:`, err instanceof Error ? err.message : String(err));
|
||||
return null;
|
||||
@@ -207,7 +207,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
}
|
||||
|
||||
// Prevent duplicate agents: reject if a non-terminated agent with the same name already exists
|
||||
const existingAgents = db.agents.listExistingAgentsForHireCheck(companyId);
|
||||
const existingAgents = db.core.agents.listExistingAgentsForHireCheck(companyId);
|
||||
const duplicateByName = existingAgents.find(
|
||||
(a) => a.name.toLowerCase() === input.name.toLowerCase(),
|
||||
);
|
||||
@@ -216,7 +216,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
// Also check pending hire approvals with the same name to prevent concurrent duplicate requests
|
||||
const pendingHires = db.approvalsMod.listPendingHireApprovalPayloads(companyId);
|
||||
const pendingHires = db.core.approvals.listPendingHireApprovalPayloads(companyId);
|
||||
for (const pending of pendingHires) {
|
||||
try {
|
||||
const payload = pending.payloadJson ? JSON.parse(pending.payloadJson) as Record<string, unknown> : {};
|
||||
@@ -234,7 +234,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
const managerLabel = resolvedReportsTo
|
||||
? (() => {
|
||||
try {
|
||||
return db.entityCrud.getAgent(resolvedReportsTo).name;
|
||||
return db.core.entity.getAgent(resolvedReportsTo).name;
|
||||
} catch (err) {
|
||||
log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
|
||||
return resolvedReportsTo;
|
||||
@@ -283,7 +283,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "agent.hire_requested",
|
||||
|
||||
@@ -74,7 +74,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
// Reject the request at the API boundary so the model cannot
|
||||
// circumvent the ordering by resubmitting.
|
||||
if (input.type === "approve_ceo_strategy") {
|
||||
const companyHireApprovals = db.approvalsMod.listCompanyApprovalsByType(companyId, "hire_agent");
|
||||
const companyHireApprovals = db.core.approvals.listCompanyApprovalsByType(companyId, "hire_agent");
|
||||
if (companyHireApprovals.length === 0) {
|
||||
server.json(res, 409, {
|
||||
error: "approve_ceo_strategy requires at least one hire_agent approval to exist first. Submit the intended hires, then file the strategy.",
|
||||
@@ -82,7 +82,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const existingStrategy = db.approvalsMod.listCompanyApprovalsByType(companyId, "approve_ceo_strategy")
|
||||
const existingStrategy = db.core.approvals.listCompanyApprovalsByType(companyId, "approve_ceo_strategy")
|
||||
.find((a) => a.state === "pending" || a.state === "approved");
|
||||
if (existingStrategy) {
|
||||
server.json(res, 409, {
|
||||
@@ -117,7 +117,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
|
||||
let id: string;
|
||||
try {
|
||||
id = db.approvalsMod.requestApproval({
|
||||
id = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
type: input.type,
|
||||
relatedTaskId: input.relatedTaskId ?? null,
|
||||
@@ -132,7 +132,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "approval.created",
|
||||
@@ -186,7 +186,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
const statuses = parsedQuery.data.status?.split(",").filter(Boolean) ?? null;
|
||||
const approvals = db.approvalsMod.listApprovalsForApi(companyId, {
|
||||
const approvals = db.core.approvals.listApprovalsForApi(companyId, {
|
||||
requestedByAgentId: parsedQuery.data.requestedByAgentId,
|
||||
relatedTaskId: parsedQuery.data.relatedTaskId,
|
||||
statuses,
|
||||
@@ -195,7 +195,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
});
|
||||
|
||||
server.addRoute("GET", "/api/approvals/:approvalId", (_req, res, params, token) => {
|
||||
const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId"));
|
||||
const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId"));
|
||||
if (!approval || approval.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Approval not found" });
|
||||
return;
|
||||
@@ -209,28 +209,28 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("GET", "/api/approvals/:approvalId/comments", (_req, res, params, token) => {
|
||||
const approvalId = requireParam(params, "approvalId");
|
||||
const approval = db.approvalsMod.findApprovalById(approvalId);
|
||||
const approval = db.core.approvals.findApprovalById(approvalId);
|
||||
if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; }
|
||||
if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
const comments = db.approvalsMod.listApprovalComments(approvalId, approval.companyId);
|
||||
const comments = db.core.approvals.listApprovalComments(approvalId, approval.companyId);
|
||||
server.json(res, 200, comments);
|
||||
});
|
||||
|
||||
server.addRoute("POST", "/api/approvals/:approvalId/comments", (_req, res, params, token, body) => {
|
||||
const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId"));
|
||||
const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId"));
|
||||
if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; }
|
||||
if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
const data = body as Record<string, unknown>;
|
||||
const commentBody = typeof data.body === "string" ? data.body.trim() : "";
|
||||
if (!commentBody) { server.json(res, 400, { error: "Comment body is required" }); return; }
|
||||
const commentId = db.approvalsMod.addApprovalComment({
|
||||
const commentId = db.core.approvals.addApprovalComment({
|
||||
companyId: approval.companyId,
|
||||
approvalId: approval.id,
|
||||
authorAgentId: token.agentId ?? null,
|
||||
body: commentBody,
|
||||
});
|
||||
// Wake mentioned agents in comment
|
||||
const mentionedIds = db.agents.findMentionedAgentIds(approval.companyId, commentBody);
|
||||
const mentionedIds = db.core.agents.findMentionedAgentIds(approval.companyId, commentBody);
|
||||
for (const mentionedId of mentionedIds) {
|
||||
if (mentionedId !== token.agentId) {
|
||||
onWakeAgent(mentionedId, approval.companyId, "approval_comment_mentioned");
|
||||
@@ -241,7 +241,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
});
|
||||
|
||||
server.addRoute("POST", "/api/approvals/:approvalId/resubmit", (_req, res, params, token) => {
|
||||
const approval = db.approvalsMod.findApprovalById(requireParam(params, "approvalId"));
|
||||
const approval = db.core.approvals.findApprovalById(requireParam(params, "approvalId"));
|
||||
if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; }
|
||||
if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
if (approval.state !== "revision_requested") {
|
||||
@@ -252,34 +252,34 @@ export function registerApprovalRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Only the requesting agent can resubmit this approval" });
|
||||
return;
|
||||
}
|
||||
db.approvalsMod.resubmitApproval(approval.id, approval.companyId);
|
||||
db.core.approvals.resubmitApproval(approval.id, approval.companyId);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { resubmitted: true });
|
||||
});
|
||||
|
||||
server.addRoute("GET", "/api/approvals/:approvalId/tasks", (_req, res, params, token) => {
|
||||
const approvalId = requireParam(params, "approvalId");
|
||||
const approval = db.approvalsMod.findApprovalById(approvalId);
|
||||
const approval = db.core.approvals.findApprovalById(approvalId);
|
||||
if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; }
|
||||
if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
const taskIds = db.approvalsMod.listTasksForApproval(approvalId);
|
||||
const taskIds = db.core.approvals.listTasksForApproval(approvalId);
|
||||
server.json(res, 200, taskIds);
|
||||
});
|
||||
|
||||
server.addRoute("POST", "/api/approvals/:approvalId/tasks", (_req, res, params, token, body) => {
|
||||
const approvalId = requireParam(params, "approvalId");
|
||||
const approval = db.approvalsMod.findApprovalById(approvalId);
|
||||
const approval = db.core.approvals.findApprovalById(approvalId);
|
||||
if (!approval) { server.json(res, 404, { error: "Approval not found" }); return; }
|
||||
if (approval.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
const data = body as Record<string, unknown>;
|
||||
const taskIds = Array.isArray(data.taskIds) ? data.taskIds.filter((id): id is string => typeof id === "string") : [];
|
||||
if (taskIds.length === 0) { server.json(res, 400, { error: "taskIds array is required" }); return; }
|
||||
for (const taskId of taskIds) {
|
||||
if (!db.settings.belongsToCompany("tasks", taskId, token.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("tasks", taskId, token.companyId)) {
|
||||
server.json(res, 400, { error: `Task ${taskId} does not belong to this company` }); return;
|
||||
}
|
||||
}
|
||||
db.approvalsMod.linkManyTasksToApproval(approvalId, taskIds, token.companyId, token.agentId ?? null);
|
||||
db.core.approvals.linkManyTasksToApproval(approvalId, taskIds, token.companyId, token.agentId ?? null);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { linked: taskIds.length });
|
||||
});
|
||||
|
||||
@@ -71,15 +71,15 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
const reviewerAgentId = data.reviewerAgentId ?? null;
|
||||
const projectId = data.projectId ?? null;
|
||||
const goalId = data.goalId ?? null;
|
||||
if (reviewerAgentId && !db.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) {
|
||||
if (reviewerAgentId && !db.core.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Reviewer agent does not belong to the current company" });
|
||||
return;
|
||||
}
|
||||
if (projectId && !db.settings.belongsToCompany("projects", projectId, token.companyId)) {
|
||||
if (projectId && !db.core.settings.belongsToCompany("projects", projectId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Project does not belong to the current company" });
|
||||
return;
|
||||
}
|
||||
if (goalId && !db.settings.belongsToCompany("goals", goalId, token.companyId)) {
|
||||
if (goalId && !db.core.settings.belongsToCompany("goals", goalId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Goal does not belong to the current company" });
|
||||
return;
|
||||
}
|
||||
@@ -114,7 +114,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const docs = db.documentsMod.listDocuments(token.companyId);
|
||||
const docs = db.core.documents.listDocuments(token.companyId);
|
||||
server.json(res, 200, docs);
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
server.json(res, 400, { error: "Missing documentId" });
|
||||
return;
|
||||
}
|
||||
const doc = db.documentsMod.getDocumentForUpdate(documentId, token.companyId);
|
||||
const doc = db.core.documents.getDocumentForUpdate(documentId, token.companyId);
|
||||
if (!doc) {
|
||||
server.json(res, 404, { error: "Document not found in this company" });
|
||||
return;
|
||||
@@ -146,7 +146,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
server.json(res, 400, { error: "Missing documentId" });
|
||||
return;
|
||||
}
|
||||
const doc = db.documentsMod.getDocumentForUpdate(documentId, token.companyId);
|
||||
const doc = db.core.documents.getDocumentForUpdate(documentId, token.companyId);
|
||||
if (!doc) {
|
||||
server.json(res, 404, { error: "Document not found in this company" });
|
||||
return;
|
||||
@@ -158,14 +158,14 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
const reviewerAgentId = data.reviewerAgentId !== undefined
|
||||
? (data.reviewerAgentId ?? null)
|
||||
: doc.reviewerAgentId;
|
||||
if (data.reviewerAgentId !== undefined && reviewerAgentId && !db.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) {
|
||||
if (data.reviewerAgentId !== undefined && reviewerAgentId && !db.core.settings.belongsToCompany("agents", reviewerAgentId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Reviewer agent does not belong to the current company" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Conflict detection: if baseRevisionId provided, verify it matches current
|
||||
if (data.baseRevisionId !== undefined) {
|
||||
const currentRevId = db.documentsMod.getDocumentLatestRevisionId(documentId);
|
||||
const currentRevId = db.core.documents.getDocumentLatestRevisionId(documentId);
|
||||
if (currentRevId && data.baseRevisionId !== currentRevId) {
|
||||
server.json(res, 409, { error: "Document was updated by someone else", currentRevisionId: currentRevId });
|
||||
return;
|
||||
@@ -182,7 +182,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
// Content update → create revision for audit trail
|
||||
let updatedVersion = doc.version;
|
||||
if (data.content !== undefined) {
|
||||
const revision = db.documentsMod.createDocumentRevision({
|
||||
const revision = db.core.documents.createDocumentRevision({
|
||||
documentId,
|
||||
companyId: token.companyId,
|
||||
body: data.content,
|
||||
@@ -194,9 +194,9 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
// Field updates (title, reviewer, tags, etc.)
|
||||
if (data.status !== undefined) {
|
||||
db.documentsMod.updateDocumentStatus(documentId, token.companyId, data.status);
|
||||
db.core.documents.updateDocumentStatus(documentId, token.companyId, data.status);
|
||||
}
|
||||
db.documentsMod.saveDocument({
|
||||
db.core.documents.saveDocument({
|
||||
id: documentId,
|
||||
companyId: token.companyId,
|
||||
type: data.type ?? doc.type,
|
||||
@@ -213,14 +213,14 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
});
|
||||
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { updated: true, latestRevisionId: db.documentsMod.getDocumentLatestRevisionId(documentId) });
|
||||
server.json(res, 200, { updated: true, latestRevisionId: db.core.documents.getDocumentLatestRevisionId(documentId) });
|
||||
});
|
||||
|
||||
server.addRoute("GET", "/api/companies/:companyId/documents/:documentId/revisions", (_req, res, params, token) => {
|
||||
if (params.companyId !== token.companyId) { server.json(res, 403, { error: "Company mismatch" }); return; }
|
||||
const documentId = params.documentId;
|
||||
if (!documentId) { server.json(res, 400, { error: "Missing documentId" }); return; }
|
||||
const revisions = db.documentsMod.listDocumentRevisions(documentId, token.companyId);
|
||||
const revisions = db.core.documents.listDocumentRevisions(documentId, token.companyId);
|
||||
server.json(res, 200, revisions);
|
||||
});
|
||||
|
||||
@@ -228,9 +228,9 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("GET", "/api/issues/:issueId/work-products", (_req, res, params, token) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
|
||||
const products = db.documentsMod.listWorkProductsForTask(task.id, task.companyId);
|
||||
const products = db.core.documents.listWorkProductsForTask(task.id, task.companyId);
|
||||
server.json(res, 200, products);
|
||||
} catch (error) {
|
||||
log.warn(`[api/work-products] list failed for issue ${params.issueId}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -240,12 +240,12 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("POST", "/api/issues/:issueId/work-products", (_req, res, params, token, body) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
|
||||
const parsed = createWorkProductSchema.safeParse(body);
|
||||
if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; }
|
||||
const data = parsed.data;
|
||||
const id = db.documentsMod.createWorkProduct({
|
||||
const id = db.core.documents.createWorkProduct({
|
||||
companyId: task.companyId,
|
||||
taskId: task.id,
|
||||
projectId: data.projectId ?? task.projectId,
|
||||
@@ -275,7 +275,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
if (!wpId) { server.json(res, 400, { error: "Missing workProductId" }); return; }
|
||||
const parsed = updateWorkProductSchema.safeParse(body);
|
||||
if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; }
|
||||
const changes = db.documentsMod.updateWorkProduct(wpId, token.companyId, parsed.data);
|
||||
const changes = db.core.documents.updateWorkProduct(wpId, token.companyId, parsed.data);
|
||||
if (changes === 0) { server.json(res, 404, { error: "Work product not found" }); return; }
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { updated: true });
|
||||
@@ -284,7 +284,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
server.addRoute("DELETE", "/api/work-products/:workProductId", (_req, res, params, token) => {
|
||||
const wpId = params.workProductId;
|
||||
if (!wpId) { server.json(res, 400, { error: "Missing workProductId" }); return; }
|
||||
const changes = db.documentsMod.deleteWorkProduct(wpId, token.companyId);
|
||||
const changes = db.core.documents.deleteWorkProduct(wpId, token.companyId);
|
||||
if (changes === 0) { server.json(res, 404, { error: "Work product not found" }); return; }
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { deleted: true });
|
||||
@@ -294,9 +294,9 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("GET", "/api/issues/:issueId/documents", (_req, res, params, token) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
|
||||
const links = db.documentsMod.listDocumentsForTask(task.id, task.companyId);
|
||||
const links = db.core.documents.listDocumentsForTask(task.id, task.companyId);
|
||||
server.json(res, 200, links);
|
||||
} catch (error) {
|
||||
log.warn(`[api/task-documents] list failed for issue ${params.issueId}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -306,7 +306,7 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("PUT", "/api/issues/:issueId/documents/:key", (_req, res, params, token, body) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
|
||||
const key = String(params.key ?? "").trim().toLowerCase();
|
||||
if (!key || !/^[a-z0-9][a-z0-9_-]*$/.test(key) || key.length > 64) {
|
||||
@@ -315,10 +315,10 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
const parsed = linkDocumentSchema.safeParse(body);
|
||||
if (!parsed.success) { server.json(res, 400, { error: "Invalid input", details: parsed.error.flatten() }); return; }
|
||||
const documentId = parsed.data.documentId;
|
||||
if (!db.settings.belongsToCompany("documents", documentId, token.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("documents", documentId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Document does not belong to this company" }); return;
|
||||
}
|
||||
const linkId = db.documentsMod.linkDocumentToTask(task.id, documentId, task.companyId, key);
|
||||
const linkId = db.core.documents.linkDocumentToTask(task.id, documentId, task.companyId, key);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { id: linkId, key });
|
||||
} catch (error) {
|
||||
@@ -329,10 +329,10 @@ export function registerFileRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("DELETE", "/api/issues/:issueId/documents/:key", (_req, res, params, token) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) { server.json(res, 404, { error: "Task not found" }); return; }
|
||||
const key = String(params.key ?? "").trim().toLowerCase();
|
||||
db.documentsMod.unlinkDocumentFromTask(task.id, key, task.companyId);
|
||||
db.core.documents.unlinkDocumentFromTask(task.id, key, task.companyId);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { unlinked: true });
|
||||
} catch (error) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export function registerMeetingRoutes(server: AgentApiServer) {
|
||||
try {
|
||||
const parsedParticipants = JSON.parse(input.participantAgentIds) as string[];
|
||||
for (const agentId of parsedParticipants) {
|
||||
if (typeof agentId !== "string" || !db.settings.belongsToCompany("agents", agentId, token.companyId)) {
|
||||
if (typeof agentId !== "string" || !db.core.settings.belongsToCompany("agents", agentId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Meeting participants must belong to the current company" });
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export function registerMeetingRoutes(server: AgentApiServer) {
|
||||
server.json(res, 400, { error: "participantAgentIds must be a JSON array of agent ids" });
|
||||
return;
|
||||
}
|
||||
const id = db.knowledgeMod.saveMeeting({
|
||||
const id = db.core.knowledge.saveMeeting({
|
||||
companyId: token.companyId,
|
||||
type: input.type,
|
||||
title: input.title,
|
||||
@@ -63,13 +63,13 @@ export function registerMeetingRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const meetings = db.knowledgeMod.listMeetings(token.companyId);
|
||||
const meetings = db.core.knowledge.listMeetings(token.companyId);
|
||||
server.json(res, 200, meetings);
|
||||
});
|
||||
|
||||
server.addRoute("PATCH", "/api/meetings/:meetingId", (_req, res, params, token, body) => {
|
||||
const meetingId = requireParam(params, "meetingId");
|
||||
const existing = db.knowledgeMod.getMeeting(meetingId, token.companyId);
|
||||
const existing = db.core.knowledge.getMeeting(meetingId, token.companyId);
|
||||
if (!existing) {
|
||||
server.json(res, 404, { error: "Meeting not found" });
|
||||
return;
|
||||
@@ -102,13 +102,13 @@ export function registerMeetingRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
for (const item of actionItems) {
|
||||
if (item.assigneeAgentId && !db.settings.belongsToCompany("agents", item.assigneeAgentId, token.companyId)) {
|
||||
if (item.assigneeAgentId && !db.core.settings.belongsToCompany("agents", item.assigneeAgentId, token.companyId)) {
|
||||
server.json(res, 400, { error: "Meeting action item assignees must belong to the current company" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
db.knowledgeMod.saveMeeting({
|
||||
db.core.knowledge.saveMeeting({
|
||||
id: meetingId,
|
||||
companyId: token.companyId,
|
||||
type: data.type ?? "department_sync",
|
||||
@@ -133,7 +133,7 @@ export function registerMeetingRoutes(server: AgentApiServer) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
db.tasks.saveTask({
|
||||
db.core.tasks.saveTask({
|
||||
companyId: token.companyId,
|
||||
title: item.title,
|
||||
description: item.description ?? `Action item from meeting: ${data.title ?? existing.title}`,
|
||||
|
||||
@@ -20,13 +20,13 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
return;
|
||||
}
|
||||
const agent = db.entityCrud.getAgent(token.agentId);
|
||||
const comment = db.activityMod.addComment({
|
||||
const agent = db.core.entity.getAgent(token.agentId);
|
||||
const comment = db.core.activity.addComment({
|
||||
companyId: task.companyId,
|
||||
taskId: task.id,
|
||||
authorAgentId: token.agentId,
|
||||
@@ -44,14 +44,14 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
wokenForComment.add(task.assigneeAgentId);
|
||||
}
|
||||
|
||||
const chain = db.agents.getChainOfCommand(token.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(token.agentId);
|
||||
if (chain.length > 0 && chain[0].id !== token.agentId && !wokenForComment.has(chain[0].id)) {
|
||||
onWakeAgent(chain[0].id, task.companyId, "comment");
|
||||
wokenForComment.add(chain[0].id);
|
||||
}
|
||||
|
||||
// @mention detection: wake any mentioned agents not already woken
|
||||
const mentionedIds = db.agents.findMentionedAgentIds(task.companyId, input.body);
|
||||
const mentionedIds = db.core.agents.findMentionedAgentIds(task.companyId, input.body);
|
||||
for (const mentionedId of mentionedIds) {
|
||||
if (mentionedId !== token.agentId && !wokenForComment.has(mentionedId)) {
|
||||
onWakeAgent(mentionedId, task.companyId, "comment_mentioned");
|
||||
@@ -68,7 +68,7 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
|
||||
server.addRoute("GET", "/api/issues/:issueId/comments", (_req, res, params, token) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
return;
|
||||
@@ -78,7 +78,7 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
return;
|
||||
}
|
||||
const comments = db.activityMod.listComments(requireParam(params, "issueId"));
|
||||
const comments = db.core.activity.listComments(requireParam(params, "issueId"));
|
||||
server.json(res, 200, comments);
|
||||
});
|
||||
|
||||
@@ -142,7 +142,7 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = db.messages.listAgentMessages(companyId, {
|
||||
const messages = db.core.messages.listAgentMessages(companyId, {
|
||||
agentId: token.agentId,
|
||||
channel: channelParam ?? undefined,
|
||||
channelTargetId: channelTargetId && channelTargetId.length > 0 ? channelTargetId : undefined,
|
||||
@@ -158,12 +158,12 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const message = db.messages.getAgentMessage(requireParam(params, "messageId"), companyId);
|
||||
const message = db.core.messages.getAgentMessage(requireParam(params, "messageId"), companyId);
|
||||
if (!message) {
|
||||
server.json(res, 404, { error: "Message not found" });
|
||||
return;
|
||||
}
|
||||
const visibleToAgent = db.messages.listAgentMessages(companyId, {
|
||||
const visibleToAgent = db.core.messages.listAgentMessages(companyId, {
|
||||
agentId: token.agentId,
|
||||
messageId: message.id,
|
||||
limit: 1,
|
||||
@@ -172,7 +172,7 @@ export function registerMessageRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "This message is not visible to the current agent" });
|
||||
return;
|
||||
}
|
||||
db.messages.markMessageReadForReader(requireParam(params, "messageId"), companyId, token.agentId);
|
||||
db.core.messages.markMessageReadForReader(requireParam(params, "messageId"), companyId, token.agentId);
|
||||
onDomainChanged();
|
||||
server.json(res, 200, { ok: true });
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
|
||||
const input = parsed.data;
|
||||
|
||||
const account = db.social.getSocialAccount(input.socialAccountId, companyId);
|
||||
const account = db.core.social.getSocialAccount(input.socialAccountId, companyId);
|
||||
if (!account) {
|
||||
server.json(res, 404, { error: "Social account not found" });
|
||||
return;
|
||||
@@ -42,7 +42,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
|
||||
const postActions = ["post", "reply", "dm"];
|
||||
if (account.requireApproval && postActions.includes(input.actionType)) {
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
type: "dangerous_command",
|
||||
relatedTaskId: null,
|
||||
@@ -51,7 +51,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
impactSummary: `Will ${input.actionType} on ${account.displayName || account.accountName}'s ${account.platform} account. Content will be publicly visible.`,
|
||||
});
|
||||
|
||||
const actionId = db.social.saveBrowserAction({
|
||||
const actionId = db.core.social.saveBrowserAction({
|
||||
companyId,
|
||||
socialAccountId: input.socialAccountId,
|
||||
agentId: token.agentId,
|
||||
@@ -61,7 +61,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
approvalId,
|
||||
});
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "browser.action_pending_approval",
|
||||
@@ -75,7 +75,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionId = db.social.saveBrowserAction({
|
||||
const actionId = db.core.social.saveBrowserAction({
|
||||
companyId,
|
||||
socialAccountId: input.socialAccountId,
|
||||
agentId: token.agentId,
|
||||
@@ -84,7 +84,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
status: "queued",
|
||||
});
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "browser.action_queued",
|
||||
@@ -108,7 +108,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const accounts = db.social.listSocialAccounts(requireParam(params, "companyId"));
|
||||
const accounts = db.core.social.listSocialAccounts(requireParam(params, "companyId"));
|
||||
const sanitized = accounts.map(a => ({
|
||||
id: a.id,
|
||||
platform: a.platform,
|
||||
@@ -134,7 +134,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = db.social.getSocialAccount(parsed.data.socialAccountId, companyId);
|
||||
const account = db.core.social.getSocialAccount(parsed.data.socialAccountId, companyId);
|
||||
if (!account) {
|
||||
server.json(res, 404, { error: "Social account not found" });
|
||||
return;
|
||||
@@ -142,7 +142,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
|
||||
const contentPreview = parsed.data.content.slice(0, 120) + (parsed.data.content.length > 120 ? "..." : "");
|
||||
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
requestedByAgentId: token.agentId,
|
||||
type: "social_post",
|
||||
@@ -171,7 +171,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
const status = url.searchParams.get("status") ?? undefined;
|
||||
const limit = Number(url.searchParams.get("limit") || 50);
|
||||
|
||||
const actions = db.social.listBrowserActions(requireParam(params, "companyId"), { socialAccountId, status, limit });
|
||||
const actions = db.core.social.listBrowserActions(requireParam(params, "companyId"), { socialAccountId, status, limit });
|
||||
server.json(res, 200, actions);
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
}
|
||||
|
||||
const input = parsed.data;
|
||||
const id = db.knowledgeMod.saveKnowledgeEntry({
|
||||
const id = db.core.knowledge.saveKnowledgeEntry({
|
||||
companyId: token.companyId,
|
||||
category: input.category,
|
||||
topic: input.topic,
|
||||
@@ -219,7 +219,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const entries = db.knowledgeMod.listKnowledgeEntries(token.companyId);
|
||||
const entries = db.core.knowledge.listKnowledgeEntries(token.companyId);
|
||||
server.json(res, 200, entries);
|
||||
});
|
||||
|
||||
@@ -231,7 +231,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const skills = db.skills.listCompanySkills(companyId);
|
||||
const skills = db.core.skills.listCompanySkills(companyId);
|
||||
server.json(res, 200, skills.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
@@ -244,7 +244,7 @@ export function registerMiscRoutes(server: AgentApiServer) {
|
||||
});
|
||||
|
||||
server.addRoute("GET", "/api/skills/:skillId", (_req, res, params, token) => {
|
||||
const skill = db.skills.getCompanySkill(token.companyId, requireParam(params, "skillId"));
|
||||
const skill = db.core.skills.getCompanySkill(token.companyId, requireParam(params, "skillId"));
|
||||
if (!skill) {
|
||||
server.json(res, 404, { error: "Skill not found" });
|
||||
return;
|
||||
|
||||
@@ -16,7 +16,7 @@ function parsePositiveInt(value: string | null, fallback: number) {
|
||||
}
|
||||
|
||||
function resolveAuthorizedRun(server: AgentApiServer, token: TokenPayload, runId: string) {
|
||||
const runState = server.db.runs.getRunAuthState(runId);
|
||||
const runState = server.db.core.runs.getRunAuthState(runId);
|
||||
if (!runState || runState.companyId !== token.companyId || runState.agentId !== token.agentId) {
|
||||
return null;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export function registerRunRoutes(server: AgentApiServer) {
|
||||
const url = new URL(req.url ?? "/", `http://127.0.0.1:${server.port}`);
|
||||
const offset = parsePositiveInt(url.searchParams.get("offset"), 0);
|
||||
const limit = Math.min(parsePositiveInt(url.searchParams.get("limit"), DEFAULT_EVENT_LIMIT), MAX_EVENT_LIMIT);
|
||||
const events = server.db.runs.listRunEvents(runId, token.companyId, { offset, limit });
|
||||
const events = server.db.core.runs.listRunEvents(runId, token.companyId, { offset, limit });
|
||||
server.json(res, 200, { events, nextOffset: offset + events.length });
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ export function registerRunRoutes(server: AgentApiServer) {
|
||||
|
||||
const flushPendingEvents = () => {
|
||||
while (true) {
|
||||
const events = server.db.runs.listRunEventsAfter(runId, token.companyId, cursor, SSE_BATCH_LIMIT);
|
||||
const events = server.db.core.runs.listRunEventsAfter(runId, token.companyId, cursor, SSE_BATCH_LIMIT);
|
||||
if (events.length === 0) return;
|
||||
for (const event of events) {
|
||||
writeSse(res, "run-event", event);
|
||||
|
||||
@@ -39,17 +39,17 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
const statuses = statusFilter ? statusFilter.split(",") : ["todo", "in_progress", "blocked"];
|
||||
|
||||
if (assigneeAgentId) {
|
||||
const tasks = db.tasks.listAgentTasks(assigneeAgentId, companyId, statuses);
|
||||
const tasks = db.core.tasks.listAgentTasks(assigneeAgentId, companyId, statuses);
|
||||
server.json(res, 200, tasks);
|
||||
} else {
|
||||
const tasks = db.tasks.listTasksByStatuses(companyId, statuses);
|
||||
const tasks = db.core.tasks.listTasksByStatuses(companyId, statuses);
|
||||
server.json(res, 200, tasks);
|
||||
}
|
||||
});
|
||||
|
||||
server.addRoute("GET", "/api/issues/:issueId", (_req, res, params, token) => {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
return;
|
||||
@@ -74,7 +74,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
}
|
||||
let task;
|
||||
try {
|
||||
task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
} catch (err) {
|
||||
log.warn(`[api/tasks] getTask failed for checkout:`, err instanceof Error ? err.message : String(err));
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
@@ -89,7 +89,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
const previousStatus = task.status;
|
||||
const success = db.tasks.checkoutTask(task.id, token.runId);
|
||||
const success = db.core.tasks.checkoutTask(task.id, token.runId);
|
||||
if (!success) {
|
||||
server.json(res, 409, { error: "Task cannot be checked out — already assigned or not in valid status" });
|
||||
return;
|
||||
@@ -109,7 +109,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
const update = parsedUpdate.data;
|
||||
|
||||
try {
|
||||
const task = db.entityCrud.getTask(requireParam(params, "issueId"));
|
||||
const task = db.core.entity.getTask(requireParam(params, "issueId"));
|
||||
if (task.companyId !== token.companyId) {
|
||||
server.json(res, 404, { error: "Task not found" });
|
||||
return;
|
||||
@@ -133,10 +133,10 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actorAgent = db.entityCrud.getAgent(token.agentId);
|
||||
const actorAgent = db.core.entity.getAgent(token.agentId);
|
||||
let reviewRequest: { reviewerAgentName: string; documents: Array<{ id: string; title: string } & Record<string, unknown>> } | null = null;
|
||||
if (effectiveStatus === "done" && task.requiresUserReview) {
|
||||
const docs = db.documentsMod.listImportantDocsForReview({
|
||||
const docs = db.core.documents.listImportantDocsForReview({
|
||||
companyId: task.companyId,
|
||||
types: IMPORTANT_DOC_TYPES,
|
||||
authorAgentId: task.assigneeAgentId ?? null,
|
||||
@@ -146,7 +146,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
reviewRequest = { reviewerAgentName: actorAgent.name, documents: docs };
|
||||
}
|
||||
|
||||
const updateResult = db.tasks.updateTaskWithReviewSideEffects({
|
||||
const updateResult = db.core.tasks.updateTaskWithReviewSideEffects({
|
||||
taskId: task.id,
|
||||
companyId: task.companyId,
|
||||
actorAgentId: token.agentId,
|
||||
@@ -183,7 +183,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
if (update.status && ["done", "in_review"].includes(newStatus)) {
|
||||
if (updateResult.parentId) {
|
||||
try {
|
||||
const parentTask = db.entityCrud.getTask(updateResult.parentId);
|
||||
const parentTask = db.core.entity.getTask(updateResult.parentId);
|
||||
if (parentTask.assigneeAgentId && parentTask.assigneeAgentId !== token.agentId) {
|
||||
onWakeAgent(parentTask.assigneeAgentId, task.companyId, "subtask_completed");
|
||||
}
|
||||
@@ -197,7 +197,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
// This is not a subtask — use a distinct wake reason so the
|
||||
// reviewer prompt does not misframe the work as "a subtask
|
||||
// you delegated".
|
||||
const chain = db.agents.getChainOfCommand(token.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(token.agentId);
|
||||
if (chain.length > 0) {
|
||||
onWakeAgent(chain[0].id, task.companyId, "task_review_requested");
|
||||
}
|
||||
@@ -205,7 +205,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
}
|
||||
|
||||
if (update.status && newStatus === "blocked") {
|
||||
const chain = db.agents.getChainOfCommand(token.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(token.agentId);
|
||||
if (chain.length > 0) {
|
||||
onWakeAgent(chain[0].id, task.companyId, "report_blocked");
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
onWakeAgent(task.assigneeAgentId, task.companyId, "comment");
|
||||
wokenForComment.add(task.assigneeAgentId);
|
||||
}
|
||||
const chain = db.agents.getChainOfCommand(token.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(token.agentId);
|
||||
if (chain.length > 0 && chain[0].id !== token.agentId && !wokenForComment.has(chain[0].id)) {
|
||||
onWakeAgent(chain[0].id, task.companyId, "comment");
|
||||
}
|
||||
@@ -297,7 +297,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
|
||||
let id: string;
|
||||
try {
|
||||
id = db.tasks.saveTask({
|
||||
id = db.core.tasks.saveTask({
|
||||
companyId,
|
||||
title: input.title,
|
||||
description: input.description ?? "",
|
||||
@@ -317,12 +317,12 @@ export function registerTaskRoutes(server: AgentApiServer) {
|
||||
|
||||
// Auto-infer task_type from assignee's department
|
||||
if (input.assigneeAgentId) {
|
||||
const assignee = db.entityCrud.getAgent(input.assigneeAgentId);
|
||||
const assignee = db.core.entity.getAgent(input.assigneeAgentId);
|
||||
const inferredType = inferTaskType(assignee.department);
|
||||
db.tasks.setTaskType(id, inferredType);
|
||||
db.core.tasks.setTaskType(id, inferredType);
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "task.created",
|
||||
|
||||
@@ -35,7 +35,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = db.entityCrud.saveProject({
|
||||
const id = db.core.entity.saveProject({
|
||||
companyId,
|
||||
name: input.name,
|
||||
description: input.description ?? "",
|
||||
@@ -45,7 +45,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
targetDate: input.targetDate ?? null,
|
||||
});
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "project.created",
|
||||
@@ -79,7 +79,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = db.entityCrud.getProjectForUpdate(projectId, token.companyId);
|
||||
const existing = db.core.entity.getProjectForUpdate(projectId, token.companyId);
|
||||
if (!existing) {
|
||||
server.json(res, 404, { error: "Project not found" });
|
||||
return;
|
||||
@@ -91,7 +91,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
}
|
||||
|
||||
try {
|
||||
db.entityCrud.saveProject({
|
||||
db.core.entity.saveProject({
|
||||
id: projectId,
|
||||
companyId: token.companyId,
|
||||
name: parsed.data.name ?? existing.name,
|
||||
@@ -135,8 +135,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = db.entityCrud.saveGoal(input);
|
||||
db.activityMod.addActivity({
|
||||
const id = db.core.entity.saveGoal(input);
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: token.agentId,
|
||||
action: "goal.created",
|
||||
@@ -167,7 +167,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = db.entityCrud.getGoalForUpdate(goalId, token.companyId);
|
||||
const existing = db.core.entity.getGoalForUpdate(goalId, token.companyId);
|
||||
if (!existing) {
|
||||
server.json(res, 404, { error: "Goal not found" });
|
||||
return;
|
||||
@@ -179,7 +179,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
}
|
||||
|
||||
try {
|
||||
db.entityCrud.saveGoal({
|
||||
db.core.entity.saveGoal({
|
||||
id: goalId,
|
||||
companyId: token.companyId,
|
||||
title: parsed.data.title ?? existing.title,
|
||||
@@ -200,7 +200,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const goals = db.entityCrud.listGoals(params.companyId);
|
||||
const goals = db.core.entity.listGoals(params.companyId);
|
||||
server.json(res, 200, goals);
|
||||
});
|
||||
|
||||
@@ -209,7 +209,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const projects = db.entityCrud.listProjects(params.companyId);
|
||||
const projects = db.core.entity.listProjects(params.companyId);
|
||||
server.json(res, 200, projects);
|
||||
});
|
||||
|
||||
@@ -219,7 +219,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const sprints = db.knowledgeMod.listSprints(companyId);
|
||||
const sprints = db.core.knowledge.listSprints(companyId);
|
||||
server.json(res, 200, sprints);
|
||||
});
|
||||
|
||||
@@ -231,7 +231,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
}
|
||||
const data = body as Record<string, unknown>;
|
||||
try {
|
||||
const id = db.knowledgeMod.saveSprint({
|
||||
const id = db.core.knowledge.saveSprint({
|
||||
companyId,
|
||||
name: String(data.name ?? "Sprint"),
|
||||
goal: String(data.goal ?? ""),
|
||||
@@ -256,7 +256,7 @@ export function registerWorkGraphRoutes(server: AgentApiServer) {
|
||||
server.json(res, 403, { error: "Company mismatch" });
|
||||
return;
|
||||
}
|
||||
const { agents, tasks, recentActivity } = db.company.getDashboardSnapshot(companyId);
|
||||
const { agents, tasks, recentActivity } = db.core.company.getDashboardSnapshot(companyId);
|
||||
|
||||
const tasksByStatus: Record<string, number> = {};
|
||||
for (const t of tasks) {
|
||||
|
||||
@@ -230,7 +230,7 @@ export class AgentApiServer {
|
||||
if (typeof claimedRunId !== "string" || claimedRunId.trim() !== token.runId) {
|
||||
return { ok: false, status: 401, error: "Missing or mismatched X-Agent-Company-Run-Id header" };
|
||||
}
|
||||
const runState = this._db.runs.getRunAuthState(token.runId);
|
||||
const runState = this._db.core.runs.getRunAuthState(token.runId);
|
||||
if (!runState || runState.companyId !== token.companyId || runState.agentId !== token.agentId) {
|
||||
return { ok: false, status: 403, error: "Token references an unknown or mismatched run" };
|
||||
}
|
||||
@@ -362,7 +362,7 @@ export class AgentApiServer {
|
||||
companyId: string,
|
||||
refs: Array<{ id: string | null | undefined; table: "agents" | "goals" | "projects" | "tasks" | "workspaces" | "approvals"; label: string }>,
|
||||
) {
|
||||
const invalid = refs.find((ref) => ref.id && !this._db.settings.belongsToCompany(ref.table, ref.id, companyId));
|
||||
const invalid = refs.find((ref) => ref.id && !this._db.core.settings.belongsToCompany(ref.table, ref.id, companyId));
|
||||
if (!invalid) return false;
|
||||
this.json(res, 422, { error: `${invalid.label} does not belong to the current company` });
|
||||
return true;
|
||||
|
||||
@@ -20,13 +20,13 @@ export function resolveApprovalDecision({
|
||||
actor?: string;
|
||||
}) {
|
||||
const note = decisionNote ?? "";
|
||||
db.approvalsMod.decideApproval({
|
||||
db.core.approvals.decideApproval({
|
||||
companyId,
|
||||
approvalId,
|
||||
state,
|
||||
decisionNote: note,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor,
|
||||
action: `approval.${state}`,
|
||||
|
||||
@@ -58,7 +58,7 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_
|
||||
const { db, logger, wakeAgentIfPossible } = deps;
|
||||
|
||||
if (ctx.taskId) {
|
||||
const assignedId = db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
const assignedId = db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
if (assignedId) {
|
||||
wakeAgentIfPossible(assignedId, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -67,14 +67,14 @@ function handleAssignTask({ deps, ctx }: TypedAutomationActionExecution<"assign_
|
||||
|
||||
if (!ctx.agentId) return;
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const unassigned = snapshot.tasks
|
||||
.filter((task) => task.companyId === ctx.companyId && !task.assigneeAgentId && ["backlog", "todo"].includes(task.status))
|
||||
.sort((a, b) => (TASK_PRIORITY_SCORE[b.priority] ?? 0) - (TASK_PRIORITY_SCORE[a.priority] ?? 0));
|
||||
if (unassigned.length === 0) return;
|
||||
|
||||
const task = unassigned[0]!;
|
||||
db.tasks.saveTask({
|
||||
db.core.tasks.saveTask({
|
||||
id: task.id,
|
||||
companyId: task.companyId,
|
||||
projectId: task.projectId,
|
||||
@@ -106,7 +106,7 @@ function handleCreateTask({ deps, callbacks, ctx, config }: TypedAutomationActio
|
||||
return;
|
||||
}
|
||||
|
||||
const taskId = db.tasks.saveTask({
|
||||
const taskId = db.core.tasks.saveTask({
|
||||
companyId: ctx.companyId,
|
||||
projectId: ctx.projectId ?? config.projectId ?? null,
|
||||
title,
|
||||
@@ -140,7 +140,7 @@ function handleAgentNotification<K extends "notify_agent" | "send_message">({
|
||||
|
||||
if (channel === "department" && !channelTargetId) {
|
||||
try {
|
||||
const sender = db.entityCrud.getAgent(fromAgentId);
|
||||
const sender = db.core.entity.getAgent(fromAgentId);
|
||||
channelTargetId = sender.department ?? null;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getAgent failed for sender ${fromAgentId}; dropping department target:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -184,7 +184,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi
|
||||
if (!targetAgentId && ctx.agentId) {
|
||||
const triggerType = rule.trigger ?? "";
|
||||
if (triggerType === "run_completed" || triggerType === "run_failed") {
|
||||
const manager = db.agents.getManagerForAgent(ctx.agentId);
|
||||
const manager = db.core.agents.getManagerForAgent(ctx.agentId);
|
||||
targetAgentId = manager?.id ?? ctx.agentId;
|
||||
} else {
|
||||
targetAgentId = ctx.agentId;
|
||||
@@ -197,7 +197,7 @@ function handleTriggerHeartbeat({ deps, rule, ctx, config }: TypedAutomationActi
|
||||
}
|
||||
|
||||
function handleCreateApproval({ deps, ctx, config }: TypedAutomationActionExecution<"create_approval">): void {
|
||||
deps.db.approvalsMod.requestApproval({
|
||||
deps.db.core.approvals.requestApproval({
|
||||
companyId: ctx.companyId,
|
||||
relatedTaskId: ctx.taskId ?? null,
|
||||
requestedByAgentId: ctx.agentId ?? null,
|
||||
@@ -219,8 +219,8 @@ function handleUpdateTaskStatus({
|
||||
const { db, publishDomainChanged } = deps;
|
||||
const { handleTaskStatusChange } = callbacks;
|
||||
const newStatus = config.status ?? "todo";
|
||||
const previousTask = db.entityCrud.getTask(ctx.taskId);
|
||||
db.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
|
||||
const previousTask = db.core.entity.getTask(ctx.taskId);
|
||||
db.core.tasks.advanceTaskStatus(ctx.taskId, newStatus, "Status changed by automation rule");
|
||||
publishDomainChanged();
|
||||
|
||||
if (previousTask.status !== newStatus && rule.trigger !== "task_status_changed") {
|
||||
@@ -240,19 +240,19 @@ function handleEscalateToManager({
|
||||
|
||||
let agentName = "an agent";
|
||||
try {
|
||||
agentName = db.entityCrud.getAgent(targetAgentId).name;
|
||||
agentName = db.core.entity.getAgent(targetAgentId).name;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getAgent failed for escalation target ${targetAgentId}; using generic label:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
|
||||
let taskTitle = ctx.taskId ?? "unknown";
|
||||
try {
|
||||
if (ctx.taskId) taskTitle = db.entityCrud.getTask(ctx.taskId).title;
|
||||
if (ctx.taskId) taskTitle = db.core.entity.getTask(ctx.taskId).title;
|
||||
} catch (err) {
|
||||
logger.warn(`[automation] getTask failed for escalation task ${ctx.taskId}; using id as title:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
|
||||
const manager = db.agents.getManagerForAgent(targetAgentId);
|
||||
const manager = db.core.agents.getManagerForAgent(targetAgentId);
|
||||
const escalationBody = config.body ?? `Task "${taskTitle}" needs your attention. ${agentName} has flagged this as requiring manager intervention.`;
|
||||
const wakeTrigger = deriveEscalationWakeTrigger(ctx.newStatus ?? null, rule.trigger ?? null);
|
||||
|
||||
@@ -291,7 +291,7 @@ function handleCrossDepartmentNotify({
|
||||
const targetDepartment = rule.targetDepartment ?? config.department;
|
||||
if (!targetDepartment) return;
|
||||
|
||||
const departmentAgents = db.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
|
||||
const departmentAgents = db.core.agents.getIdleAgentsForDepartment(ctx.companyId, targetDepartment);
|
||||
const senderAgentId = ctx.agentId ?? departmentAgents[0]?.id;
|
||||
if (!senderAgentId) return;
|
||||
|
||||
@@ -322,7 +322,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu
|
||||
const offsetMinutes = config.scheduleOffsetMinutes ?? 60;
|
||||
const scheduledAt = config.scheduledAt ?? new Date(Date.now() + offsetMinutes * 60_000).toISOString();
|
||||
|
||||
deps.db.knowledgeMod.saveMeeting({
|
||||
deps.db.core.knowledge.saveMeeting({
|
||||
companyId: ctx.companyId,
|
||||
type: meetingType,
|
||||
title,
|
||||
@@ -334,7 +334,7 @@ function handleScheduleMeeting({ deps, ctx, config }: TypedAutomationActionExecu
|
||||
}
|
||||
|
||||
function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecution<"create_document">): void {
|
||||
deps.db.documentsMod.saveDocument({
|
||||
deps.db.core.documents.saveDocument({
|
||||
companyId: ctx.companyId,
|
||||
type: config.documentType ?? "status_report",
|
||||
title: config.title ?? "Auto-generated document",
|
||||
@@ -345,13 +345,13 @@ function handleCreateDocument({ deps, ctx, config }: TypedAutomationActionExecut
|
||||
}
|
||||
|
||||
function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecution<"auto_approve">): void {
|
||||
const snapshot = deps.db.snap.listSnapshot();
|
||||
const snapshot = deps.db.projection.snapshot.listSnapshot();
|
||||
const pendingApprovals = snapshot.approvals.filter(
|
||||
(approval) => approval.companyId === ctx.companyId && approval.state === "pending",
|
||||
);
|
||||
|
||||
for (const approval of pendingApprovals.slice(0, 5)) {
|
||||
deps.db.approvalsMod.decideApproval({
|
||||
deps.db.core.approvals.decideApproval({
|
||||
companyId: ctx.companyId,
|
||||
approvalId: approval.id,
|
||||
state: "approved",
|
||||
@@ -364,7 +364,7 @@ function handleAutoApprove({ deps, callbacks, ctx }: TypedAutomationActionExecut
|
||||
function handleReassignTask({ deps, ctx }: TypedAutomationActionExecution<"reassign_task">): void {
|
||||
if (!ctx.taskId) return;
|
||||
|
||||
const newAssignee = deps.db.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
const newAssignee = deps.db.core.tasks.autoAssignTask(ctx.taskId, ctx.companyId);
|
||||
if (newAssignee) {
|
||||
deps.wakeAgentIfPossible(newAssignee, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -374,7 +374,7 @@ function handleRunTask({ deps, ctx, config }: TypedAutomationActionExecution<"ru
|
||||
const targetTaskId = config.taskId ?? ctx.taskId;
|
||||
if (!targetTaskId) return;
|
||||
|
||||
const task = deps.db.entityCrud.getTask(targetTaskId);
|
||||
const task = deps.db.core.entity.getTask(targetTaskId);
|
||||
if (!task.assigneeAgentId) return;
|
||||
deps.wakeAgentIfPossible(task.assigneeAgentId, ctx.companyId, "assignment");
|
||||
}
|
||||
@@ -30,7 +30,7 @@ function recordReviewFeedbackKnowledge(
|
||||
if (!approval.relatedTaskId) return;
|
||||
if (decision === "approved") return;
|
||||
|
||||
const task = db.entityCrud.getTask(approval.relatedTaskId);
|
||||
const task = db.core.entity.getTask(approval.relatedTaskId);
|
||||
const feedback = approval.decisionNote?.trim() || "Board requested revisions before approval.";
|
||||
const topic = `Review feedback: ${task.title}`;
|
||||
const content = [
|
||||
@@ -39,7 +39,7 @@ function recordReviewFeedbackKnowledge(
|
||||
"Use this review guidance to improve future deliverables for similar work.",
|
||||
].join("\n");
|
||||
|
||||
db.knowledgeMod.saveKnowledgeEntry({
|
||||
db.core.knowledge.saveKnowledgeEntry({
|
||||
companyId: approval.companyId,
|
||||
category: "lesson_learned",
|
||||
topic,
|
||||
@@ -84,7 +84,7 @@ export function handleApprovalResolved(
|
||||
const { db, logger, publishDomainChanged, notify, wakeAgentIfPossible, dispatchBrowserAction } = deps;
|
||||
const { dispatchAutomation, handleTaskStatusChange, runWorkflowPipeline, trackTimer } = callbacks;
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const approval = snapshot.approvals.find((entry) => entry.id === approvalId);
|
||||
if (!approval) return;
|
||||
|
||||
@@ -95,7 +95,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
|
||||
if (approval.type === "hire_agent" && decision === "approved" && approval.relatedAgentId) {
|
||||
const agent = db.entityCrud.getAgent(approval.relatedAgentId);
|
||||
const agent = db.core.entity.getAgent(approval.relatedAgentId);
|
||||
|
||||
if (agent.status === "idle") {
|
||||
// Deterministic default skill kit. The CEO's operating prompt
|
||||
@@ -111,7 +111,7 @@ export function handleApprovalResolved(
|
||||
department: agent.department,
|
||||
});
|
||||
if (assignedSkillIds.length > 0) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "agent.skills_defaulted",
|
||||
@@ -121,7 +121,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "agent.hired",
|
||||
@@ -136,13 +136,13 @@ export function handleApprovalResolved(
|
||||
});
|
||||
wakeAgentIfPossible(agent.id, companyId, "assignment");
|
||||
|
||||
const ceo = db.agents.getCeoForCompany(companyId);
|
||||
const ceo = db.core.agents.getCeoForCompany(companyId);
|
||||
if (ceo && ceo.id !== agent.id) {
|
||||
wakeAgentIfPossible(ceo.id, companyId, "assignment");
|
||||
}
|
||||
|
||||
publishDomainChanged();
|
||||
const hireLocale = db.snap.listSnapshot().locale;
|
||||
const hireLocale = db.projection.snapshot.listSnapshot().locale;
|
||||
notify({
|
||||
title: localizeNotification(hireLocale, "Agent Hired", "新员工入职"),
|
||||
body: localizeNotification(hireLocale, `${agent.name} joined as ${agent.role}`, `${agent.name} 已加入,职位:${agent.role}`),
|
||||
@@ -170,31 +170,31 @@ export function handleApprovalResolved(
|
||||
? "draft"
|
||||
: "in_review";
|
||||
for (const docId of documentIds) {
|
||||
db.documentsMod.updateDocumentStatus(docId, companyId, docStatus);
|
||||
db.core.documents.updateDocumentStatus(docId, companyId, docStatus);
|
||||
}
|
||||
|
||||
if (approval.relatedTaskId) {
|
||||
const task = db.entityCrud.getTask(approval.relatedTaskId);
|
||||
const task = db.core.entity.getTask(approval.relatedTaskId);
|
||||
if (task.assigneeAgentId) {
|
||||
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
|
||||
const agent = db.core.entity.getAgent(task.assigneeAgentId);
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(task.assigneeAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
|
||||
}
|
||||
}
|
||||
if (decision === "approved") {
|
||||
db.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
|
||||
db.core.tasks.advanceTaskStatus(task.id, "done", "Board approved deliverable.");
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "done");
|
||||
db.activityMod.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." });
|
||||
db.core.activity.addActivity({ companyId, actor: "board", action: "deliverable.approved", entityType: "task", entityId: task.id, detail: approval.decisionNote || "Deliverable approved by board." });
|
||||
} else if (decision === "rejected") {
|
||||
recordReviewFeedbackKnowledge(db, approval, decision);
|
||||
db.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`);
|
||||
db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board rejected deliverable: ${approval.decisionNote || "No reason given."}`);
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "in_progress");
|
||||
if (task.assigneeAgentId) {
|
||||
wakeAgentIfPossible(task.assigneeAgentId, companyId, "revision_needed");
|
||||
}
|
||||
} else if (decision === "revision_requested") {
|
||||
db.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`);
|
||||
db.core.tasks.advanceTaskStatus(task.id, "in_progress", `Board requested revisions: ${approval.decisionNote || "No reason given."}`);
|
||||
handleTaskStatusChange(task.id, companyId, task.status, "in_progress");
|
||||
recordReviewFeedbackKnowledge(db, approval, decision);
|
||||
if (task.assigneeAgentId) {
|
||||
@@ -202,9 +202,9 @@ export function handleApprovalResolved(
|
||||
}
|
||||
}
|
||||
} else if (approval.requestedByAgentId) {
|
||||
const agent = db.entityCrud.getAgent(approval.requestedByAgentId);
|
||||
const agent = db.core.entity.getAgent(approval.requestedByAgentId);
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(approval.requestedByAgentId, true, agent.heartbeatIntervalSec || 120);
|
||||
logger.info(`[deliverable-review] Resumed heartbeat for ${agent.name} after user ${decision}.`);
|
||||
}
|
||||
if (decision !== "approved") {
|
||||
@@ -219,7 +219,7 @@ export function handleApprovalResolved(
|
||||
const documentIds = extractApprovalDocumentIds(approval.payloadJson);
|
||||
const docStatus = decision === "approved" ? "approved" : "draft";
|
||||
for (const docId of documentIds) {
|
||||
db.documentsMod.updateDocumentStatus(docId, companyId, docStatus);
|
||||
db.core.documents.updateDocumentStatus(docId, companyId, docStatus);
|
||||
}
|
||||
publishDomainChanged();
|
||||
}
|
||||
@@ -235,8 +235,8 @@ export function handleApprovalResolved(
|
||||
};
|
||||
if (payload.policyId) {
|
||||
// Resolve the budget incident that triggered this approval
|
||||
db.budget.resolveBudgetIncident(payload.policyId, companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.budget.resolveBudgetIncident(payload.policyId, companyId);
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "board",
|
||||
action: "budget.override_approved",
|
||||
@@ -250,10 +250,10 @@ export function handleApprovalResolved(
|
||||
}
|
||||
}
|
||||
|
||||
const browserAction = db.social.getBrowserActionByApprovalId(approvalId, companyId);
|
||||
const browserAction = db.core.social.getBrowserActionByApprovalId(approvalId, companyId);
|
||||
if (browserAction) {
|
||||
if (decision === "approved") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "queued",
|
||||
resultSummary: "Approval granted. Dispatching browser action.",
|
||||
errorMessage: null,
|
||||
@@ -263,7 +263,7 @@ export function handleApprovalResolved(
|
||||
logger.error(`[browser] Failed to dispatch approved browser action ${browserAction.id}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
});
|
||||
} else if (decision === "rejected") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "cancelled",
|
||||
resultSummary: "Browser action was rejected during approval.",
|
||||
errorMessage: "Approval rejected by board.",
|
||||
@@ -271,7 +271,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
publishDomainChanged();
|
||||
} else if (decision === "revision_requested") {
|
||||
db.social.updateBrowserAction(browserAction.id, {
|
||||
db.core.social.updateBrowserAction(browserAction.id, {
|
||||
status: "approval_required",
|
||||
resultSummary: "Board requested revisions before execution.",
|
||||
errorMessage: "Approval requires revisions before the browser action can run.",
|
||||
@@ -291,7 +291,7 @@ export function handleApprovalResolved(
|
||||
};
|
||||
|
||||
const dispatchSocialAction = () => {
|
||||
const actionId = db.social.saveBrowserAction({
|
||||
const actionId = db.core.social.saveBrowserAction({
|
||||
companyId: approval.companyId,
|
||||
socialAccountId: draft.socialAccountId,
|
||||
agentId: approval.requestedByAgentId ?? "",
|
||||
@@ -333,14 +333,14 @@ export function handleApprovalResolved(
|
||||
const payload = workflowApprovalPayloadSchema.safeParse(JSON.parse(approval.payloadJson));
|
||||
if (payload.success && payload.data.workflowId && payload.data.stepId) {
|
||||
const { workflowId, stepId } = payload.data;
|
||||
const workflow = db.automationMod.getWorkflowRecord(workflowId, companyId);
|
||||
const workflow = db.automation.rules.getWorkflowRecord(workflowId, companyId);
|
||||
if (workflow) {
|
||||
const steps = normalizeWorkflowSteps(JSON.parse(workflow.stepsJson || "[]"));
|
||||
const stepIndex = steps.findIndex((step) => step.id === stepId);
|
||||
if (stepIndex >= 0) {
|
||||
if (decision === "approved") {
|
||||
steps[stepIndex]!.status = "pending";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflow.id,
|
||||
companyId,
|
||||
status: "paused",
|
||||
@@ -354,7 +354,7 @@ export function handleApprovalResolved(
|
||||
});
|
||||
} else {
|
||||
steps[stepIndex]!.status = decision === "revision_requested" ? "pending" : "failed";
|
||||
db.automationMod.updateWorkflowExecution({
|
||||
db.automation.rules.updateWorkflowExecution({
|
||||
id: workflow.id,
|
||||
companyId,
|
||||
status: decision === "revision_requested" ? "paused" : "failed",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -97,7 +97,7 @@ export function backfillWorkspaceArtifacts(
|
||||
filesAlreadyTracked: 0,
|
||||
filesIgnored: 0,
|
||||
};
|
||||
const flag = deps.db.settings.getSetting(SETTINGS_FLAG_KEY);
|
||||
const flag = deps.db.core.settings.getSetting(SETTINGS_FLAG_KEY);
|
||||
if (flag === "true") return empty;
|
||||
|
||||
let filesScanned = 0;
|
||||
@@ -105,9 +105,9 @@ export function backfillWorkspaceArtifacts(
|
||||
let filesAlreadyTracked = 0;
|
||||
let filesIgnored = 0;
|
||||
|
||||
const snapshot = deps.db.snap.listSnapshot();
|
||||
const snapshot = deps.db.projection.snapshot.listSnapshot();
|
||||
for (const company of snapshot.companies) {
|
||||
const observation = deps.db.workspacesMod.getCompanyObservationWorkspace(company.id);
|
||||
const observation = deps.db.core.workspaces.getCompanyObservationWorkspace(company.id);
|
||||
if (!observation) continue;
|
||||
const workspaceRoot = observation.localPath;
|
||||
if (!workspaceRoot || !existsSync(workspaceRoot)) continue;
|
||||
@@ -186,7 +186,7 @@ export function backfillWorkspaceArtifacts(
|
||||
}
|
||||
}
|
||||
|
||||
const existing = deps.db.documentsMod.findWorkProductByExternalId(
|
||||
const existing = deps.db.core.documents.findWorkProductByExternalId(
|
||||
company.id,
|
||||
"filesystem",
|
||||
rel,
|
||||
@@ -197,7 +197,7 @@ export function backfillWorkspaceArtifacts(
|
||||
continue;
|
||||
}
|
||||
const title = lastSlash >= 0 ? rel.slice(lastSlash + 1) : rel;
|
||||
deps.db.documentsMod.createWorkProduct({
|
||||
deps.db.core.documents.createWorkProduct({
|
||||
companyId: company.id,
|
||||
taskId: null,
|
||||
workspaceId: observation.id,
|
||||
@@ -212,7 +212,7 @@ export function backfillWorkspaceArtifacts(
|
||||
}
|
||||
}
|
||||
|
||||
deps.db.settings.setSetting(SETTINGS_FLAG_KEY, "true");
|
||||
deps.db.core.settings.setSetting(SETTINGS_FLAG_KEY, "true");
|
||||
deps.logger.info(
|
||||
`[backfill-workspace-artifacts] scanned=${filesScanned} inserted=${filesInserted} already-tracked=${filesAlreadyTracked} ignored=${filesIgnored}`,
|
||||
);
|
||||
|
||||
@@ -121,9 +121,9 @@ export function prepareServices(deps: PrepareServicesDeps): ServiceGraphHandles
|
||||
logger: deps.logger,
|
||||
getMainWindow: () => serviceGraph?.getDesktopShellService()?.getMainWindow() ?? null,
|
||||
getDb: () => ({
|
||||
hasCompany: (companyId: string) => deps.db.settings.hasCompany(companyId),
|
||||
hasCompany: (companyId: string) => deps.db.core.settings.hasCompany(companyId),
|
||||
belongsToCompany: (table: string, id: string, companyId: string) =>
|
||||
deps.db.settings.belongsToCompany(table as Parameters<typeof deps.db.settings.belongsToCompany>[0], id, companyId),
|
||||
deps.db.core.settings.belongsToCompany(table as Parameters<typeof deps.db.core.settings.belongsToCompany>[0], id, companyId),
|
||||
}),
|
||||
notificationService,
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export function emitCrashRecoveryNotification(deps: RecoverInterruptedTasksDeps)
|
||||
const interruptedCount = deps.db.lastInterruptedCount;
|
||||
if (interruptedCount <= 0) return;
|
||||
deps.logger.warn(`[bootstrap] Detected ${interruptedCount} interrupted runs from previous session. Sending recovery notification.`);
|
||||
const interruptedRuns = deps.db.runs.getInterruptedRunsForRecovery();
|
||||
const interruptedRuns = deps.db.core.runs.getInterruptedRunsForRecovery();
|
||||
if (interruptedRuns.length === 0) return;
|
||||
deps.emitEvent({ type: "recovery-needed", interruptedRuns });
|
||||
deps.notify({
|
||||
@@ -39,7 +39,7 @@ export function emitCrashRecoveryNotification(deps: RecoverInterruptedTasksDeps)
|
||||
*/
|
||||
export function recoverShutdownInterruptedTasks(deps: RecoverInterruptedTasksDeps): void {
|
||||
try {
|
||||
const snapshot = deps.db.snap.listSnapshot();
|
||||
const snapshot = deps.db.projection.snapshot.listSnapshot();
|
||||
const shutdownRuns = snapshot.runs.filter(
|
||||
(r) => r.status === "interrupted" &&
|
||||
(r.summary === "Application shut down." || r.summary === "Worker process crashed."),
|
||||
@@ -49,10 +49,10 @@ export function recoverShutdownInterruptedTasks(deps: RecoverInterruptedTasksDep
|
||||
for (const run of shutdownRuns) {
|
||||
try {
|
||||
if (run.taskId) {
|
||||
const task = deps.db.entityCrud.getTask(run.taskId);
|
||||
const task = deps.db.core.entity.getTask(run.taskId);
|
||||
if (task.status === "in_progress" || task.status === "blocked") {
|
||||
deps.db.tasks.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart.");
|
||||
deps.db.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
deps.db.core.tasks.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart.");
|
||||
deps.db.core.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
}
|
||||
}
|
||||
if (!resumedAgents.has(run.agentId)) {
|
||||
|
||||
@@ -15,9 +15,9 @@ export interface RecoverScheduledPostsDeps {
|
||||
export function recoverScheduledPosts(deps: RecoverScheduledPostsDeps): Set<ReturnType<typeof setTimeout>> {
|
||||
const timers = new Set<ReturnType<typeof setTimeout>>();
|
||||
try {
|
||||
const snapshot = deps.db.snap.listSnapshot();
|
||||
const snapshot = deps.db.projection.snapshot.listSnapshot();
|
||||
for (const company of snapshot.companies) {
|
||||
const queuedActions = deps.db.social.listBrowserActions(company.id, { status: "queued" });
|
||||
const queuedActions = deps.db.core.social.listBrowserActions(company.id, { status: "queued" });
|
||||
for (const action of queuedActions) {
|
||||
if (!action.payloadJson) continue;
|
||||
try {
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function runStartupHealthChecks(
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
if (!connectorHealth) return;
|
||||
const connectors = db.snap.listSnapshot().connectors;
|
||||
const connectors = db.projection.snapshot.listSnapshot().connectors;
|
||||
const results = await Promise.allSettled(
|
||||
connectors.map((connector) => connectorHealth.checkConnector(connector.id)),
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ export function wireApiServer(deps: WireApiServerDeps): AgentApiServer {
|
||||
deps.threadIntegration.onTaskStatusChanged(taskId, companyId, prev, next);
|
||||
if (prev === "todo" && next === "in_progress") {
|
||||
try {
|
||||
const task = deps.db.entityCrud.getTask(taskId);
|
||||
const task = deps.db.core.entity.getTask(taskId);
|
||||
deps.checkpointEngine.onTaskStarted(taskId, companyId, task.assigneeAgentId);
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
@@ -120,7 +120,7 @@ export function wireApiServer(deps: WireApiServerDeps): AgentApiServer {
|
||||
},
|
||||
input,
|
||||
),
|
||||
onAutoAssignTask: (taskId, companyId) => deps.db.tasks.autoAssignTask(taskId, companyId),
|
||||
onAutoAssignTask: (taskId, companyId) => deps.db.core.tasks.autoAssignTask(taskId, companyId),
|
||||
onHumanMessage: (companyId, toAgentId, threadId, waitStateId) => {
|
||||
deps.threadIntegration.onHumanMessage(companyId, toAgentId, threadId, waitStateId);
|
||||
deps.publishDomainChanged();
|
||||
|
||||
@@ -27,9 +27,9 @@ export function createBrowserActionService({
|
||||
* missing. Isolates the "approval row disappeared" case so the caller does
|
||||
* not need to nest a try/catch around the state machine.
|
||||
*/
|
||||
function getApprovalOrNull(approvalId: string): ReturnType<typeof db.approvalsMod.getApprovalRecord> | null {
|
||||
function getApprovalOrNull(approvalId: string): ReturnType<typeof db.core.approvals.getApprovalRecord> | null {
|
||||
try {
|
||||
return db.approvalsMod.getApprovalRecord(approvalId);
|
||||
return db.core.approvals.getApprovalRecord(approvalId);
|
||||
} catch (err) {
|
||||
const classified = classifyUnknownError(err, {
|
||||
kind: "recoverable",
|
||||
@@ -43,7 +43,7 @@ export function createBrowserActionService({
|
||||
}
|
||||
|
||||
async function dispatchBrowserAction(actionId: string, companyId: string) {
|
||||
const action = db.social.getBrowserAction(actionId, companyId);
|
||||
const action = db.core.social.getBrowserAction(actionId, companyId);
|
||||
if (!action) {
|
||||
throw new Error(`Browser action not found: ${actionId}`);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export function createBrowserActionService({
|
||||
throw new Error(`Browser action approval ${action.approvalId} does not belong to company ${companyId}.`);
|
||||
}
|
||||
if (approval.state !== "approved") {
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: "approval_required",
|
||||
resultSummary: "Browser action is still waiting for approval.",
|
||||
errorMessage: "Approval must be granted before execution.",
|
||||
@@ -66,9 +66,9 @@ export function createBrowserActionService({
|
||||
}
|
||||
}
|
||||
|
||||
const account = db.social.getSocialAccount(action.socialAccountId, companyId);
|
||||
const account = db.core.social.getSocialAccount(action.socialAccountId, companyId);
|
||||
if (!account) {
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: "failed",
|
||||
errorMessage: "Social account no longer exists.",
|
||||
finishedAt: new Date().toISOString(),
|
||||
@@ -88,7 +88,7 @@ export function createBrowserActionService({
|
||||
context: { actionId },
|
||||
});
|
||||
log.warn(`[${classified.code}]`, classified.message, classified.context);
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: "failed",
|
||||
errorMessage: "Browser action payload is not valid JSON.",
|
||||
finishedAt: new Date().toISOString(),
|
||||
@@ -97,7 +97,7 @@ export function createBrowserActionService({
|
||||
return;
|
||||
}
|
||||
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: "running",
|
||||
startedAt: new Date().toISOString(),
|
||||
errorMessage: null,
|
||||
@@ -113,7 +113,7 @@ export function createBrowserActionService({
|
||||
payload,
|
||||
});
|
||||
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: result.cancelled ? "cancelled" : result.success ? "succeeded" : "failed",
|
||||
resultSummary: result.summary,
|
||||
screenshotPath: result.screenshotPath,
|
||||
@@ -121,7 +121,7 @@ export function createBrowserActionService({
|
||||
finishedAt: new Date().toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
db.social.updateBrowserAction(actionId, {
|
||||
db.core.social.updateBrowserAction(actionId, {
|
||||
status: "failed",
|
||||
errorMessage: error instanceof Error ? error.message : String(error),
|
||||
finishedAt: new Date().toISOString(),
|
||||
|
||||
@@ -31,7 +31,7 @@ function getScopeLabel(
|
||||
): string {
|
||||
if (policy.scopeType === "agent") {
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(policy.scopeId);
|
||||
const agent = db.core.entity.getAgent(policy.scopeId);
|
||||
return `Agent "${agent.name}"`;
|
||||
} catch (err) {
|
||||
logger.warn(`[budget] getAgent failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -68,7 +68,7 @@ export function createBudgetEnforcementService({
|
||||
agentId: string,
|
||||
projectId: string | null,
|
||||
): BudgetGateResult {
|
||||
const policies = db.budget.getActivePoliciesForRun(companyId, agentId, projectId);
|
||||
const policies = db.core.budget.getActivePoliciesForRun(companyId, agentId, projectId);
|
||||
if (policies.length === 0) {
|
||||
return { allowed: true, blockedBy: [] };
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export function createBudgetEnforcementService({
|
||||
const blockedBy: BudgetGateResult["blockedBy"] = [];
|
||||
for (const policy of policies) {
|
||||
if (!policy.hardStopEnabled) continue;
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
const spent = db.core.budget.getSpentInWindow(policy);
|
||||
if (spent >= policy.amount) {
|
||||
blockedBy.push({
|
||||
policyId: policy.id,
|
||||
@@ -103,19 +103,19 @@ export function createBudgetEnforcementService({
|
||||
agentId: string,
|
||||
projectId: string | null,
|
||||
): void {
|
||||
const policies = db.budget.getActivePoliciesForRun(companyId, agentId, projectId);
|
||||
const policies = db.core.budget.getActivePoliciesForRun(companyId, agentId, projectId);
|
||||
|
||||
for (const policy of policies) {
|
||||
// amount <= 0 disables the policy (and would otherwise divide by zero below).
|
||||
if (policy.amount <= 0) continue;
|
||||
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
const spent = db.core.budget.getSpentInWindow(policy);
|
||||
const utilization = spent / policy.amount;
|
||||
const warnThreshold = policy.warnPercent / 100;
|
||||
const scopeLabel = getScopeLabel(db, policy, logger);
|
||||
|
||||
if (utilization >= warnThreshold) {
|
||||
db.budget.upsertBudgetIncident(policy, "warn_threshold", spent);
|
||||
db.core.budget.upsertBudgetIncident(policy, "warn_threshold", spent);
|
||||
if (policy.notifyEnabled) {
|
||||
notify({
|
||||
title: "Budget Warning",
|
||||
@@ -128,7 +128,7 @@ export function createBudgetEnforcementService({
|
||||
}
|
||||
|
||||
if (utilization >= 1.0 && policy.hardStopEnabled) {
|
||||
db.budget.upsertBudgetIncident(policy, "hard_stop", spent);
|
||||
db.core.budget.upsertBudgetIncident(policy, "hard_stop", spent);
|
||||
if (policy.notifyEnabled) {
|
||||
notify({
|
||||
title: "Budget Exhausted",
|
||||
@@ -151,22 +151,22 @@ export function createBudgetEnforcementService({
|
||||
* Per D-06: fires auto_resolved_limit_increase status.
|
||||
*/
|
||||
function checkAutoResolve(companyId: string, policyId: string): void {
|
||||
const policy = db.budget.getBudgetPolicy(policyId);
|
||||
const policy = db.core.budget.getBudgetPolicy(policyId);
|
||||
if (!policy) return;
|
||||
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
const spent = db.core.budget.getSpentInWindow(policy);
|
||||
let resolved = false;
|
||||
|
||||
// If spend is below hard-stop threshold, resolve all open incidents
|
||||
if (spent < policy.amount) {
|
||||
const count = db.budget.resolveIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
|
||||
const count = db.core.budget.resolveIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
|
||||
if (count > 0) resolved = true;
|
||||
}
|
||||
|
||||
// If spend is below warn threshold, resolve warn incidents specifically
|
||||
const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
|
||||
if (spent < warnAmount) {
|
||||
const count = db.budget.resolveWarnIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
|
||||
const count = db.core.budget.resolveWarnIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
|
||||
if (count > 0) resolved = true;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export function createBudgetEnforcementService({
|
||||
* Per D-06: window rollover cleanup.
|
||||
*/
|
||||
function autoCloseExpiredIncidents(companyId: string): number {
|
||||
const count = db.budget.autoCloseExpiredIncidents(companyId);
|
||||
const count = db.core.budget.autoCloseExpiredIncidents(companyId);
|
||||
if (count > 0) {
|
||||
logger.info(`Budget auto-close: ${count} expired incident(s) for company ${companyId}`);
|
||||
publishDomainChanged();
|
||||
@@ -193,12 +193,12 @@ export function createBudgetEnforcementService({
|
||||
* Returns a summary of all budget policies and their current spend status.
|
||||
*/
|
||||
function getBudgetStatus(companyId: string): BudgetStatusSummary {
|
||||
const policies = db.budget.listBudgetPolicies(companyId);
|
||||
const openIncidents = db.budget.listBudgetIncidents(companyId, "open");
|
||||
const policies = db.core.budget.listBudgetPolicies(companyId);
|
||||
const openIncidents = db.core.budget.listBudgetIncidents(companyId, "open");
|
||||
|
||||
return {
|
||||
policies: policies.map((policy) => {
|
||||
const spent = db.budget.getSpentInWindow(policy);
|
||||
const spent = db.core.budget.getSpentInWindow(policy);
|
||||
const hasOpenIncident = openIncidents.some((i) => i.policyId === policy.id);
|
||||
return {
|
||||
policyId: policy.id,
|
||||
|
||||
@@ -27,7 +27,7 @@ interface CheckpointDeps {
|
||||
}
|
||||
|
||||
export function getCompanyExecutionMode(db: AppDatabase, companyId: string): ExecutionMode {
|
||||
return db.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
return db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
}
|
||||
|
||||
export function createCheckpointEngine(deps: CheckpointDeps) {
|
||||
@@ -48,8 +48,8 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
|
||||
const mode = getCompanyExecutionMode(db, companyId);
|
||||
if (mode !== "ai_cofounder") return null;
|
||||
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const taskType = db.tasks.getTaskType(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
const taskType = db.core.tasks.getTaskType(taskId);
|
||||
|
||||
// Only create plan checkpoints for non-code task types
|
||||
if (!taskType || !CHECKPOINT_TASK_TYPES.has(taskType)) return null;
|
||||
@@ -60,7 +60,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
|
||||
// the moment delegation starts.
|
||||
if (task.parentId) return null;
|
||||
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
relatedTaskId: taskId,
|
||||
requestedByAgentId: agentId,
|
||||
@@ -107,7 +107,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
|
||||
? "strategy_review"
|
||||
: "draft_review";
|
||||
|
||||
const approvalId = db.approvalsMod.requestApproval({
|
||||
const approvalId = db.core.approvals.requestApproval({
|
||||
companyId,
|
||||
relatedTaskId: taskId,
|
||||
requestedByAgentId: authorAgentId,
|
||||
@@ -154,7 +154,7 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
|
||||
}
|
||||
|
||||
// Check for pending approvals related to this agent's tasks
|
||||
return db.approvalsMod.hasPendingReviewApprovalForAgent(companyId, agentId);
|
||||
return db.core.approvals.hasPendingReviewApprovalForAgent(companyId, agentId);
|
||||
} catch (err) {
|
||||
logger.error(`[checkpoint] shouldPauseHeartbeat failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
return false;
|
||||
|
||||
@@ -27,7 +27,7 @@ export function createConnectorHealthService({
|
||||
credentialStore,
|
||||
}: ConnectorHealthDependencies) {
|
||||
function randomFallbackCompanyId() {
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
return snapshot.companies[0]?.id ?? "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function createConnectorHealthService({
|
||||
// Re-parse envBindingText on every probe: it is user-editable and must
|
||||
// not be cached into credentialStore. An explicit binding for the
|
||||
// connector's declared envKey (non-empty value) counts as ready.
|
||||
const connector = db.connectorsCrud.findConnector(id);
|
||||
const connector = db.core.connectors.findConnector(id);
|
||||
const boundValue = connector
|
||||
? parseEnvBindingText(connector.envBindingText)[definition.auth.envKey]
|
||||
: undefined;
|
||||
@@ -113,12 +113,12 @@ export function createConnectorHealthService({
|
||||
}
|
||||
|
||||
async function checkConnector(id: ConnectorId) {
|
||||
const companyId = db.settings.getCurrentCompanyId() ?? randomFallbackCompanyId();
|
||||
const companyId = db.core.settings.getCurrentCompanyId() ?? randomFallbackCompanyId();
|
||||
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
db.core.connectors.updateConnectorHealth({
|
||||
id,
|
||||
status: "detected",
|
||||
authState: db.connectorsCrud.getConnector(id).authState,
|
||||
authState: db.core.connectors.getConnector(id).authState,
|
||||
version: null,
|
||||
lastError: null,
|
||||
});
|
||||
@@ -126,14 +126,14 @@ export function createConnectorHealthService({
|
||||
|
||||
try {
|
||||
const result = evaluateCredential(id);
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
db.core.connectors.updateConnectorHealth({
|
||||
id,
|
||||
status: result.status,
|
||||
authState: result.authState,
|
||||
version: null,
|
||||
lastError: result.lastError,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "connector.tested",
|
||||
@@ -145,7 +145,7 @@ export function createConnectorHealthService({
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`[connector] Health check failed for ${id}: ${errorMessage}`);
|
||||
db.connectorsCrud.updateConnectorHealth({
|
||||
db.core.connectors.updateConnectorHealth({
|
||||
id,
|
||||
status: "error",
|
||||
authState: "unknown",
|
||||
|
||||
@@ -14,12 +14,14 @@ interface ConnectorModelPolicyDeps {
|
||||
* domain modules so we do not repeat the closure literal at every site.
|
||||
*/
|
||||
export function connectorPolicyDepsFromDb(db: {
|
||||
connectorsCrud: { getConnector: (id: ConnectorId) => { id: ConnectorId; label?: string | null; model: string | null } };
|
||||
settings: { getDisabledModels: (companyId: string, connectorId: string) => Array<{ id: string; label: string }> };
|
||||
core: {
|
||||
connectors: { getConnector: (id: ConnectorId) => { id: ConnectorId; label?: string | null; model: string | null } };
|
||||
settings: { getDisabledModels: (companyId: string, connectorId: string) => Array<{ id: string; label: string }> };
|
||||
};
|
||||
}): ConnectorModelPolicyDeps {
|
||||
return {
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export class CredentialStore {
|
||||
constructor(private readonly deps: CredentialStoreDeps) {}
|
||||
|
||||
list(): CredentialSummary[] {
|
||||
return this.deps.db.credentialsTable.list().map((row) => ({
|
||||
return this.deps.db.core.credentials.list().map((row) => ({
|
||||
connectorId: row.connectorId,
|
||||
kind: row.kind as CredentialKind,
|
||||
displayHint: row.displayHint,
|
||||
@@ -66,7 +66,7 @@ export class CredentialStore {
|
||||
save(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null = null): CredentialSummary {
|
||||
const json = JSON.stringify(payload.value);
|
||||
const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(json);
|
||||
const stored = this.deps.db.credentialsTable.upsert({
|
||||
const stored = this.deps.db.core.credentials.upsert({
|
||||
connectorId,
|
||||
kind: payload.kind,
|
||||
backend: backend as CredentialBackend,
|
||||
@@ -91,7 +91,7 @@ export class CredentialStore {
|
||||
|
||||
/** Decrypts and returns the typed credential payload. Returns null when missing. */
|
||||
read(connectorId: string): StoredCredentialPayload | null {
|
||||
const row = this.deps.db.credentialsTable.get(connectorId);
|
||||
const row = this.deps.db.core.credentials.get(connectorId);
|
||||
if (!row) return null;
|
||||
const json = this.deps.db.vault.decrypt(
|
||||
row.backend,
|
||||
@@ -102,17 +102,17 @@ export class CredentialStore {
|
||||
}
|
||||
|
||||
remove(connectorId: string): void {
|
||||
this.deps.db.credentialsTable.remove(connectorId);
|
||||
this.deps.db.core.credentials.remove(connectorId);
|
||||
}
|
||||
|
||||
setStatus(connectorId: string, status: CredentialStatus, lastError: string | null = null): void {
|
||||
this.deps.db.credentialsTable.setStatus(connectorId, status, lastError);
|
||||
this.deps.db.core.credentials.setStatus(connectorId, status, lastError);
|
||||
}
|
||||
|
||||
/** Replaces the OAuth ciphertext after a successful refresh. */
|
||||
saveRefreshedOAuth(connectorId: string, payload: StoredCredentialPayload, expiresAt: string | null): void {
|
||||
const { backend, payload: ciphertext } = this.deps.db.vault.encrypt(JSON.stringify(payload.value));
|
||||
this.deps.db.credentialsTable.markRefreshed(
|
||||
this.deps.db.core.credentials.markRefreshed(
|
||||
connectorId,
|
||||
backend as CredentialBackend,
|
||||
ciphertext,
|
||||
|
||||
+125
-92
@@ -31,12 +31,47 @@ import { buildModelCapabilityMethods } from "./database/model-capabilities-crud"
|
||||
import { MODEL_CAPABILITY_SEED } from "./model-capability-seed";
|
||||
import { initializeDatabase } from "./database/schema";
|
||||
import { SecretVault } from "./vault";
|
||||
import type { AgentStatus, ConnectorId, ProfileSnapshot, RunStatus } from "@shared/types";
|
||||
import { STALE_LOCK_TIMEOUT_MS } from "./constants";
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
export interface AppDatabaseCoreModules {
|
||||
threads: ReturnType<typeof buildThreadMethods>;
|
||||
company: ReturnType<typeof buildCompanyMethods>;
|
||||
agents: ReturnType<typeof buildAgentMethods>;
|
||||
tasks: ReturnType<typeof buildTaskMethods>;
|
||||
messages: ReturnType<typeof buildMessageMethods>;
|
||||
runs: ReturnType<typeof buildRunMethods>;
|
||||
settings: ReturnType<typeof buildSettingsMethods>;
|
||||
connectors: ReturnType<typeof buildConnectorMethods>;
|
||||
workspaces: ReturnType<typeof buildWorkspaceMethods>;
|
||||
approvals: ReturnType<typeof buildApprovalMethods>;
|
||||
social: ReturnType<typeof buildSocialMethods>;
|
||||
activity: ReturnType<typeof buildActivityMethods>;
|
||||
skills: ReturnType<typeof buildSkillMethods>;
|
||||
budget: ReturnType<typeof buildBudgetMethods>;
|
||||
entity: ReturnType<typeof buildEntityCrudMethods>;
|
||||
documents: ReturnType<typeof buildDocumentMethods>;
|
||||
knowledge: ReturnType<typeof buildKnowledgeMethods>;
|
||||
backup: ReturnType<typeof buildBackupMethods>;
|
||||
credentials: ReturnType<typeof buildCredentialMethods>;
|
||||
modelCapabilities: ReturnType<typeof buildModelCapabilityMethods>;
|
||||
}
|
||||
|
||||
export interface AppDatabaseAutomationModules {
|
||||
rules: ReturnType<typeof buildAutomationMethods>;
|
||||
wakeup: ReturnType<typeof buildWakeupMethods>;
|
||||
}
|
||||
|
||||
export interface AppDatabaseProjectionModules {
|
||||
snapshot: ReturnType<typeof buildSnapshotMethods>;
|
||||
sessionEntries: ReturnType<typeof buildSessionEntryMethods>;
|
||||
inbox: ReturnType<typeof buildInboxProjectionMethods>;
|
||||
documentLifecycleEvents: ReturnType<typeof buildDocumentLifecycleEventMethods>;
|
||||
}
|
||||
|
||||
export class AppDatabaseBase {
|
||||
readonly profileDir: string;
|
||||
readonly dbPath: string;
|
||||
@@ -47,36 +82,9 @@ export class AppDatabaseBase {
|
||||
readonly vault: SecretVault;
|
||||
protected db: DatabaseSync;
|
||||
|
||||
// Domain modules. The canonical surface — call these directly from
|
||||
// every consumer (`db.tasks.saveTask(...)`, not `db.saveTask(...)`).
|
||||
// The ESLint rule in eslint.config.mjs blocks new top-level methods
|
||||
// on AppDatabase so the old pass-through layer cannot grow back.
|
||||
public readonly threads: ReturnType<typeof buildThreadMethods>;
|
||||
public readonly company: ReturnType<typeof buildCompanyMethods>;
|
||||
public readonly agents: ReturnType<typeof buildAgentMethods>;
|
||||
public readonly tasks: ReturnType<typeof buildTaskMethods>;
|
||||
public readonly messages: ReturnType<typeof buildMessageMethods>;
|
||||
public readonly runs: ReturnType<typeof buildRunMethods>;
|
||||
public readonly snap: ReturnType<typeof buildSnapshotMethods>;
|
||||
public readonly settings: ReturnType<typeof buildSettingsMethods>;
|
||||
public readonly connectorsCrud: ReturnType<typeof buildConnectorMethods>;
|
||||
public readonly workspacesMod: ReturnType<typeof buildWorkspaceMethods>;
|
||||
public readonly approvalsMod: ReturnType<typeof buildApprovalMethods>;
|
||||
public readonly social: ReturnType<typeof buildSocialMethods>;
|
||||
public readonly activityMod: ReturnType<typeof buildActivityMethods>;
|
||||
public readonly skills: ReturnType<typeof buildSkillMethods>;
|
||||
public readonly budget: ReturnType<typeof buildBudgetMethods>;
|
||||
public readonly entityCrud: ReturnType<typeof buildEntityCrudMethods>;
|
||||
public readonly documentsMod: ReturnType<typeof buildDocumentMethods>;
|
||||
public readonly automationMod: ReturnType<typeof buildAutomationMethods>;
|
||||
public readonly knowledgeMod: ReturnType<typeof buildKnowledgeMethods>;
|
||||
public readonly wakeup: ReturnType<typeof buildWakeupMethods>;
|
||||
public readonly backupMod: ReturnType<typeof buildBackupMethods>;
|
||||
public readonly sessionEntries: ReturnType<typeof buildSessionEntryMethods>;
|
||||
public readonly inboxProjections: ReturnType<typeof buildInboxProjectionMethods>;
|
||||
public readonly documentLifecycleEvents: ReturnType<typeof buildDocumentLifecycleEventMethods>;
|
||||
public readonly credentialsTable: ReturnType<typeof buildCredentialMethods>;
|
||||
public readonly modelCapabilities: ReturnType<typeof buildModelCapabilityMethods>;
|
||||
public readonly core: AppDatabaseCoreModules;
|
||||
public readonly automation: AppDatabaseAutomationModules;
|
||||
public readonly projection: AppDatabaseProjectionModules;
|
||||
|
||||
constructor(rootDir: string, options: { isPackaged?: boolean; appVersion?: string } = {}) {
|
||||
this.profileDir = rootDir;
|
||||
@@ -99,70 +107,70 @@ export class AppDatabaseBase {
|
||||
const ra = <T>(sql: string, ...params: SQLInputValue[]) => this.readAll<T>(sql, ...params);
|
||||
const tx = <T>(op: () => T) => this.transaction(op);
|
||||
|
||||
this.activityMod = buildActivityMethods(getDb, ra);
|
||||
this.settings = buildSettingsMethods(getDb, ra, {
|
||||
const activity = buildActivityMethods(getDb, ra);
|
||||
const settings = buildSettingsMethods(getDb, ra, {
|
||||
profileDir: this.profileDir, logsDir: this.logsDir,
|
||||
artifactsDir: this.artifactsDir, backupsDir: this.backupsDir,
|
||||
});
|
||||
this.connectorsCrud = buildConnectorMethods(getDb, ra);
|
||||
this.snap = buildSnapshotMethods(getDb, ra, {
|
||||
getCurrentCompanyId: () => this.settings.getCurrentCompanyId(),
|
||||
const connectors = buildConnectorMethods(getDb, ra);
|
||||
const snapshot = buildSnapshotMethods(getDb, ra, {
|
||||
getCurrentCompanyId: () => settings.getCurrentCompanyId(),
|
||||
getSetting: (key) => this.getSetting(key),
|
||||
readConnectors: () => this.connectorsCrud.readConnectors(),
|
||||
readConnectors: () => connectors.readConnectors(),
|
||||
profileDir: this.profileDir,
|
||||
vaultBackendLabel: () => this.vault.getBackendLabel(),
|
||||
});
|
||||
this.threads = buildThreadMethods(getDb, ra);
|
||||
this.company = buildCompanyMethods(getDb, ra, tx, {
|
||||
getSetting: (key) => this.getSetting(key),
|
||||
setSetting: (key, value) => this.setSetting(key, value),
|
||||
listSnapshot: (options) => this.snap.listSnapshot(options),
|
||||
addComment: (input) => this.activityMod.addComment(input),
|
||||
});
|
||||
this.agents = buildAgentMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => this.entityCrud.getAgent(id),
|
||||
wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo),
|
||||
recordConfigRevision: (input) => this.documentsMod.recordConfigRevision(input),
|
||||
cancelRunForDeletion: (runId, taskId, workspaceId, summary) => this.runs.cancelRunForDeletion(runId, taskId, workspaceId, summary),
|
||||
addActivity: (input) => this.activityMod.addActivity(input),
|
||||
});
|
||||
this.tasks = buildTaskMethods(getDb, ra, tx, {
|
||||
addComment: (input) => this.activityMod.addComment(input),
|
||||
addActivity: (input) => this.activityMod.addActivity(input),
|
||||
requestApproval: (input) => this.approvalsMod.requestApproval(input),
|
||||
setHeartbeat: (agentId, enabled, intervalSec) => this.agents.setHeartbeat(agentId, enabled, intervalSec),
|
||||
});
|
||||
this.messages = buildMessageMethods(getDb, ra, {
|
||||
getAgent: (id) => this.entityCrud.getAgent(id),
|
||||
addActivity: (input) => this.activityMod.addActivity(input),
|
||||
transaction: tx,
|
||||
});
|
||||
this.runs = buildRunMethods(getDb, ra, tx, { logsDir: this.logsDir });
|
||||
this.workspacesMod = buildWorkspaceMethods(getDb, ra, tx);
|
||||
this.approvalsMod = buildApprovalMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => this.entityCrud.getAgent(id),
|
||||
});
|
||||
this.social = buildSocialMethods(getDb, ra, tx);
|
||||
this.skills = buildSkillMethods(getDb, ra);
|
||||
this.budget = buildBudgetMethods(getDb, ra, tx);
|
||||
this.entityCrud = buildEntityCrudMethods(getDb, ra, tx, {
|
||||
addActivity: (input) => this.activityMod.addActivity(input),
|
||||
const threads = buildThreadMethods(getDb, ra);
|
||||
const entity = buildEntityCrudMethods(getDb, ra, tx, {
|
||||
addActivity: (input) => activity.addActivity(input),
|
||||
vault: this.vault,
|
||||
listInvalidEnvBindingKeys,
|
||||
parseEnvBindingText,
|
||||
});
|
||||
this.documentsMod = buildDocumentMethods(getDb, ra, tx);
|
||||
this.automationMod = buildAutomationMethods(getDb, ra, {
|
||||
addActivity: (input) => this.activityMod.addActivity(input),
|
||||
const documents = buildDocumentMethods(getDb, ra, tx);
|
||||
const runs = buildRunMethods(getDb, ra, tx, { logsDir: this.logsDir });
|
||||
const company = buildCompanyMethods(getDb, ra, tx, {
|
||||
getSetting: (key) => this.getSetting(key),
|
||||
setSetting: (key, value) => this.setSetting(key, value),
|
||||
listSnapshot: (options) => snapshot.listSnapshot(options),
|
||||
addComment: (input) => activity.addComment(input),
|
||||
});
|
||||
this.knowledgeMod = buildKnowledgeMethods(getDb, ra);
|
||||
this.wakeup = buildWakeupMethods(getDb, ra, tx);
|
||||
this.sessionEntries = buildSessionEntryMethods(getDb, ra, tx);
|
||||
this.inboxProjections = buildInboxProjectionMethods(getDb);
|
||||
this.documentLifecycleEvents = buildDocumentLifecycleEventMethods(getDb, ra);
|
||||
this.credentialsTable = buildCredentialMethods(getDb, ra, tx);
|
||||
this.modelCapabilities = buildModelCapabilityMethods(getDb, ra, tx);
|
||||
this.backupMod = buildBackupMethods({
|
||||
const agents = buildAgentMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => entity.getAgent(id),
|
||||
wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo),
|
||||
recordConfigRevision: (input) => documents.recordConfigRevision(input),
|
||||
cancelRunForDeletion: (runId, taskId, workspaceId, summary) => runs.cancelRunForDeletion(runId, taskId, workspaceId, summary),
|
||||
addActivity: (input) => activity.addActivity(input),
|
||||
});
|
||||
const approvals = buildApprovalMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => entity.getAgent(id),
|
||||
});
|
||||
const tasks = buildTaskMethods(getDb, ra, tx, {
|
||||
addComment: (input) => activity.addComment(input),
|
||||
addActivity: (input) => activity.addActivity(input),
|
||||
requestApproval: (input) => approvals.requestApproval(input),
|
||||
setHeartbeat: (agentId, enabled, intervalSec) => agents.setHeartbeat(agentId, enabled, intervalSec),
|
||||
});
|
||||
const messages = buildMessageMethods(getDb, ra, {
|
||||
getAgent: (id) => entity.getAgent(id),
|
||||
addActivity: (input) => activity.addActivity(input),
|
||||
transaction: tx,
|
||||
});
|
||||
const workspaces = buildWorkspaceMethods(getDb, ra, tx);
|
||||
const social = buildSocialMethods(getDb, ra, tx);
|
||||
const skills = buildSkillMethods(getDb, ra);
|
||||
const budget = buildBudgetMethods(getDb, ra, tx);
|
||||
const automationRules = buildAutomationMethods(getDb, ra, {
|
||||
addActivity: (input) => activity.addActivity(input),
|
||||
});
|
||||
const knowledge = buildKnowledgeMethods(getDb, ra);
|
||||
const wakeup = buildWakeupMethods(getDb, ra, tx);
|
||||
const sessionEntries = buildSessionEntryMethods(getDb, ra, tx);
|
||||
const inbox = buildInboxProjectionMethods(getDb);
|
||||
const documentLifecycleEvents = buildDocumentLifecycleEventMethods(getDb, ra);
|
||||
const credentials = buildCredentialMethods(getDb, ra, tx);
|
||||
const modelCapabilities = buildModelCapabilityMethods(getDb, ra, tx);
|
||||
const backup = buildBackupMethods({
|
||||
getDb, setDb: (db) => { this.db = db; },
|
||||
openConnection: () => this.openConnection(),
|
||||
init: () => this.init(),
|
||||
@@ -172,6 +180,31 @@ export class AppDatabaseBase {
|
||||
backupsDir: this.backupsDir,
|
||||
appVersion: this.appVersion,
|
||||
});
|
||||
|
||||
this.core = {
|
||||
threads,
|
||||
company,
|
||||
agents,
|
||||
tasks,
|
||||
messages,
|
||||
runs,
|
||||
settings,
|
||||
connectors,
|
||||
workspaces,
|
||||
approvals,
|
||||
social,
|
||||
activity,
|
||||
skills,
|
||||
budget,
|
||||
entity,
|
||||
documents,
|
||||
knowledge,
|
||||
backup,
|
||||
credentials,
|
||||
modelCapabilities,
|
||||
};
|
||||
this.automation = { rules: automationRules, wakeup };
|
||||
this.projection = { snapshot, sessionEntries, inbox, documentLifecycleEvents };
|
||||
}
|
||||
|
||||
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
@@ -179,9 +212,9 @@ export class AppDatabaseBase {
|
||||
init() {
|
||||
const ra = <T>(sql: string, ...params: SQLInputValue[]) => this.readAll<T>(sql, ...params);
|
||||
const tx = <T>(op: () => T) => this.transaction(op);
|
||||
initializeDatabase(this.db, ra, tx, (reason) => this.backupMod.createAutomaticBackup(reason));
|
||||
this.connectorsCrud.seedConnectors();
|
||||
this.modelCapabilities.seedModelCapabilities(MODEL_CAPABILITY_SEED);
|
||||
initializeDatabase(this.db, ra, tx, (reason) => this.core.backup.createAutomaticBackup(reason));
|
||||
this.core.connectors.seedConnectors();
|
||||
this.core.modelCapabilities.seedModelCapabilities(MODEL_CAPABILITY_SEED);
|
||||
// Crash-recovery: a run still at `running` on boot means the worker
|
||||
// died mid-turn. Flip it to `interrupted`, surface the related task
|
||||
// as `blocked` for operator attention, and release workspace locks.
|
||||
@@ -189,15 +222,15 @@ export class AppDatabaseBase {
|
||||
// all other "data-repair" passes have moved to explicit runtime
|
||||
// state-machine guards (see `tasks-status.ts`) or maintenance IPC.
|
||||
this._lastInterruptedCount = this.interruptStaleRuns();
|
||||
this.settings.purgeExpiredRunLogs();
|
||||
this.wakeup.purgeExpiredWakeupRequests();
|
||||
this.core.settings.purgeExpiredRunLogs();
|
||||
this.automation.wakeup.purgeExpiredWakeupRequests();
|
||||
}
|
||||
|
||||
close() { this.db.close(); }
|
||||
|
||||
// ─── Internal helper: relock releaseAllWorkspaceLocks is used by
|
||||
// crash-recovery below; callers should prefer
|
||||
// `db.workspacesMod.releaseAllWorkspaceLocks()` directly.
|
||||
// `db.core.workspaces.releaseAllWorkspaceLocks()` directly.
|
||||
//
|
||||
// ─── Internal infrastructure ───────────────────────────────────────────────
|
||||
protected _lastInterruptedCount = 0;
|
||||
@@ -222,7 +255,7 @@ export class AppDatabaseBase {
|
||||
// so a boot that crashed while workers were already idle can
|
||||
// still reset agent rows parked at `status = 'running'`.
|
||||
this.db.prepare("update agents set status = 'idle', updated_at = ? where status = 'running'").run(ts);
|
||||
this.workspacesMod.releaseAllWorkspaceLocks();
|
||||
this.core.workspaces.releaseAllWorkspaceLocks();
|
||||
return;
|
||||
}
|
||||
const runIds = staleRuns.map((r) => r.id);
|
||||
@@ -244,7 +277,7 @@ export class AppDatabaseBase {
|
||||
)
|
||||
.run(...runIds);
|
||||
this.db.prepare("update agents set status = 'idle', updated_at = ? where status = 'running'").run(ts);
|
||||
this.workspacesMod.releaseAllWorkspaceLocks();
|
||||
this.core.workspaces.releaseAllWorkspaceLocks();
|
||||
});
|
||||
return interruptedCount;
|
||||
}
|
||||
@@ -267,7 +300,7 @@ export class AppDatabaseBase {
|
||||
}
|
||||
|
||||
protected incrementActiveSprintVelocity(companyId: string, delta: number) {
|
||||
this.entityCrud.incrementActiveSprintVelocity(companyId, delta);
|
||||
this.core.entity.incrementActiveSprintVelocity(companyId, delta);
|
||||
}
|
||||
|
||||
protected getSetting(key: string): string | null {
|
||||
|
||||
+15
-15
@@ -4,10 +4,10 @@ import { AppDatabaseBase } from "./database-base";
|
||||
export { STALE_LOCK_TIMEOUT_MS } from "./constants";
|
||||
|
||||
/**
|
||||
* AppDatabase — concrete persistence surface wired to the twenty-six
|
||||
* domain modules declared on AppDatabaseBase. New code must call the
|
||||
* owning module directly (e.g. `db.tasks.saveTask(...)`, not
|
||||
* `db.saveTask(...)`); the ESLint rule in .eslintrc enforces this.
|
||||
* AppDatabase — concrete persistence surface wired to the grouped
|
||||
* modules declared on AppDatabaseBase. New code must call the owning
|
||||
* module directly (e.g. `db.core.tasks.saveTask(...)`); the ESLint
|
||||
* rule in .eslintrc enforces this.
|
||||
*
|
||||
* The only members living here are cross-cutting composites whose
|
||||
* single-module home does not exist yet (createHireRequest,
|
||||
@@ -17,28 +17,28 @@ export { STALE_LOCK_TIMEOUT_MS } from "./constants";
|
||||
export class AppDatabase extends AppDatabaseBase {
|
||||
/**
|
||||
* Create a hire request by persisting the provisional agent row and
|
||||
* its approval atomically. The composition spans `agents` and
|
||||
* `approvalsMod`, so it lives here until a dedicated hiring module
|
||||
* its approval atomically. The composition spans `core.agents` and
|
||||
* `core.approvals`, so it lives here until a dedicated hiring module
|
||||
* owns both sides.
|
||||
*/
|
||||
createHireRequest(input: Parameters<AppDatabaseBase["entityCrud"]["createHireRequest"]>[0]) {
|
||||
return this.entityCrud.createHireRequest(input, {
|
||||
saveAgent: (agentInput) => this.agents.saveAgent(agentInput),
|
||||
requestApproval: (approvalInput) => this.approvalsMod.requestApproval(approvalInput),
|
||||
createHireRequest(input: Parameters<AppDatabaseBase["core"]["entity"]["createHireRequest"]>[0]) {
|
||||
return this.core.entity.createHireRequest(input, {
|
||||
saveAgent: (agentInput) => this.core.agents.saveAgent(agentInput),
|
||||
requestApproval: (approvalInput) => this.core.approvals.requestApproval(approvalInput),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll back an agent configuration revision. Spans documents
|
||||
* (revision store), entityCrud (agent lookup), and a recorded
|
||||
* (revision store), core.entity (agent lookup), and a recorded
|
||||
* rollback entry — again a multi-module composite the subclass
|
||||
* carries until a dedicated agent-config module owns the flow.
|
||||
*/
|
||||
rollbackConfigRevision(agentId: string, revisionId: string, companyId: string) {
|
||||
return this.documentsMod.rollbackConfigRevision(agentId, revisionId, companyId, {
|
||||
getConfigRevision: (rid, cid) => this.documentsMod.getConfigRevision(rid, cid),
|
||||
getAgent: (id) => this.entityCrud.getAgent(id),
|
||||
recordConfigRevision: (input) => this.documentsMod.recordConfigRevision(input),
|
||||
return this.core.documents.rollbackConfigRevision(agentId, revisionId, companyId, {
|
||||
getConfigRevision: (rid, cid) => this.core.documents.getConfigRevision(rid, cid),
|
||||
getAgent: (id) => this.core.entity.getAgent(id),
|
||||
recordConfigRevision: (input) => this.core.documents.recordConfigRevision(input),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export class DocumentLifecycleProcessor {
|
||||
|
||||
/** Immediate drain scoped to one document, used right after commit. */
|
||||
drainForDocument(documentId: string): void {
|
||||
const batch = this.options.db.documentLifecycleEvents.claimPendingBatch({
|
||||
const batch = this.options.db.projection.documentLifecycleEvents.claimPendingBatch({
|
||||
limit: this.options.batchSize,
|
||||
maxAttempts: this.options.maxAttempts,
|
||||
documentId,
|
||||
@@ -92,7 +92,7 @@ export class DocumentLifecycleProcessor {
|
||||
|
||||
/** Periodic sweep: rescues stragglers across all documents. */
|
||||
processBatch(): number {
|
||||
const batch = this.options.db.documentLifecycleEvents.claimPendingBatch({
|
||||
const batch = this.options.db.projection.documentLifecycleEvents.claimPendingBatch({
|
||||
limit: this.options.batchSize,
|
||||
maxAttempts: this.options.maxAttempts,
|
||||
});
|
||||
@@ -103,7 +103,7 @@ export class DocumentLifecycleProcessor {
|
||||
/** Move rows stuck in `running` back to `pending`. */
|
||||
reclaimStale(): number {
|
||||
const cutoff = new Date(Date.now() - this.options.staleRunningMs).toISOString();
|
||||
const reclaimed = this.options.db.documentLifecycleEvents.reclaimStaleRunning(cutoff);
|
||||
const reclaimed = this.options.db.projection.documentLifecycleEvents.reclaimStaleRunning(cutoff);
|
||||
if (reclaimed > 0) {
|
||||
this.options.logger.info(
|
||||
`[document-lifecycle] reclaimed ${reclaimed} stale running row(s) past ${this.options.staleRunningMs}ms cutoff`,
|
||||
@@ -155,7 +155,7 @@ export class DocumentLifecycleProcessor {
|
||||
const handler = this.handlers.get(row.hookType);
|
||||
if (!handler) {
|
||||
const reason = `No handler registered for hook type "${row.hookType}"`;
|
||||
this.options.db.documentLifecycleEvents.markFailed(row.id, reason);
|
||||
this.options.db.projection.documentLifecycleEvents.markFailed(row.id, reason);
|
||||
this.options.logger.error(`[document-lifecycle] ${reason} (event ${row.id})`);
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export class DocumentLifecycleProcessor {
|
||||
payload = row.payloadJson ? (JSON.parse(row.payloadJson) as Record<string, unknown>) : {};
|
||||
} catch (error) {
|
||||
const reason = `payload_json parse failed: ${describeError(error)}`;
|
||||
this.options.db.documentLifecycleEvents.markFailed(row.id, reason);
|
||||
this.options.db.projection.documentLifecycleEvents.markFailed(row.id, reason);
|
||||
this.options.logger.error(`[document-lifecycle] ${reason} (event ${row.id})`);
|
||||
return;
|
||||
}
|
||||
@@ -191,21 +191,21 @@ export class DocumentLifecycleProcessor {
|
||||
private recordOutcome(row: DocumentLifecycleEventRow, outcome: LifecycleHookOutcome): void {
|
||||
try {
|
||||
if (outcome.kind === "succeeded") {
|
||||
this.options.db.documentLifecycleEvents.markSucceeded(row.id);
|
||||
this.options.db.projection.documentLifecycleEvents.markSucceeded(row.id);
|
||||
return;
|
||||
}
|
||||
if (outcome.kind === "failed") {
|
||||
this.options.db.documentLifecycleEvents.markFailed(row.id, outcome.reason);
|
||||
this.options.db.projection.documentLifecycleEvents.markFailed(row.id, outcome.reason);
|
||||
return;
|
||||
}
|
||||
if (row.attempts >= this.options.maxAttempts) {
|
||||
this.options.db.documentLifecycleEvents.markFailed(
|
||||
this.options.db.projection.documentLifecycleEvents.markFailed(
|
||||
row.id,
|
||||
`Retry budget exhausted (${row.attempts}/${this.options.maxAttempts}): ${outcome.reason}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.options.db.documentLifecycleEvents.markRetryable(row.id, outcome.reason);
|
||||
this.options.db.projection.documentLifecycleEvents.markRetryable(row.id, outcome.reason);
|
||||
} catch (error) {
|
||||
this.options.logger.warn(
|
||||
`[document-lifecycle] recordOutcome failed for ${row.id}: ${describeError(error)}`,
|
||||
|
||||
@@ -84,7 +84,7 @@ export function createDocumentWithLifecycle(
|
||||
): DocumentLifecycleOutcome {
|
||||
const outcome = deps.db.withTransaction(() => {
|
||||
const isNew = !input.id;
|
||||
const id = deps.db.documentsMod.saveDocument(input);
|
||||
const id = deps.db.core.documents.saveDocument(input);
|
||||
const taskId = deriveTaskIdFromRun(deps.db, input.sourceRunId, input.companyId, deps.logger);
|
||||
|
||||
let deliverableReviewApprovalId: string | null = null;
|
||||
@@ -114,7 +114,7 @@ export function createDocumentWithLifecycle(
|
||||
// memoised closure — so a crash between this commit and the first
|
||||
// handler attempt is self-healing: the periodic sweeper picks the
|
||||
// row up on its next tick.
|
||||
deps.db.documentLifecycleEvents.enqueue({
|
||||
deps.db.projection.documentLifecycleEvents.enqueue({
|
||||
companyId: input.companyId,
|
||||
documentId: id,
|
||||
hookType: "automation_dispatch",
|
||||
@@ -126,7 +126,7 @@ export function createDocumentWithLifecycle(
|
||||
});
|
||||
|
||||
if (input.reviewerAgentId && input.reviewerAgentId !== input.authorAgentId) {
|
||||
deps.db.documentLifecycleEvents.enqueue({
|
||||
deps.db.projection.documentLifecycleEvents.enqueue({
|
||||
companyId: input.companyId,
|
||||
documentId: id,
|
||||
hookType: "reviewer_wake",
|
||||
@@ -164,7 +164,7 @@ function deriveTaskIdFromRun(
|
||||
): string | null {
|
||||
if (!runId) return null;
|
||||
try {
|
||||
const run = db.runs.getRun(runId);
|
||||
const run = db.core.runs.getRun(runId);
|
||||
if (run.companyId !== companyId) return null;
|
||||
return run.taskId ?? null;
|
||||
} catch (error) {
|
||||
@@ -187,12 +187,12 @@ function maybeRaiseDeliverableReviewApproval(
|
||||
documentId: string,
|
||||
taskId: string | null,
|
||||
): string | null {
|
||||
const companySettings = deps.db.company.getCompanyApprovalSettings(input.companyId);
|
||||
const companySettings = deps.db.core.company.getCompanyApprovalSettings(input.companyId);
|
||||
if (!companySettings?.reviewDeliverables) return null;
|
||||
if (deps.db.company.getCompanyExecutionMode(input.companyId) === "ai_cofounder") return null;
|
||||
if (deps.db.tasks.hasPendingDeliverableReviews(input.companyId)) return null;
|
||||
if (deps.db.core.company.getCompanyExecutionMode(input.companyId) === "ai_cofounder") return null;
|
||||
if (deps.db.core.tasks.hasPendingDeliverableReviews(input.companyId)) return null;
|
||||
|
||||
const approvalId = deps.db.approvalsMod.requestApproval({
|
||||
const approvalId = deps.db.core.approvals.requestApproval({
|
||||
companyId: input.companyId,
|
||||
relatedTaskId: taskId,
|
||||
requestedByAgentId: input.authorAgentId ?? "",
|
||||
@@ -207,9 +207,9 @@ function maybeRaiseDeliverableReviewApproval(
|
||||
|
||||
if (input.authorAgentId) {
|
||||
try {
|
||||
const agent = deps.db.entityCrud.getAgent(input.authorAgentId);
|
||||
const agent = deps.db.core.entity.getAgent(input.authorAgentId);
|
||||
if (agent.heartbeatEnabled) {
|
||||
deps.db.agents.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120);
|
||||
deps.db.core.agents.setHeartbeat(input.authorAgentId, false, agent.heartbeatIntervalSec || 120);
|
||||
}
|
||||
} catch (error) {
|
||||
deps.logger.warn(
|
||||
|
||||
@@ -27,12 +27,12 @@ const REVIEW_WAIT_STATE_TYPES = new Set(["user_reply", "user_review", "user_appr
|
||||
|
||||
function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: string): Set<string> {
|
||||
const agentIds = new Set<string>();
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
for (const waitState of waitStates) {
|
||||
const thread = db.threads.getThread(waitState.threadId, companyId);
|
||||
const thread = db.core.threads.getThread(waitState.threadId, companyId);
|
||||
if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue;
|
||||
try {
|
||||
const task = db.entityCrud.getTask(thread.relatedEntityId);
|
||||
const task = db.core.entity.getTask(thread.relatedEntityId);
|
||||
if (task.assigneeAgentId) {
|
||||
agentIds.add(task.assigneeAgentId);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri
|
||||
}
|
||||
if (!approval.relatedTaskId) continue;
|
||||
try {
|
||||
const task = db.entityCrud.getTask(approval.relatedTaskId);
|
||||
const task = db.core.entity.getTask(approval.relatedTaskId);
|
||||
if (task.assigneeAgentId) {
|
||||
agentIds.add(task.assigneeAgentId);
|
||||
}
|
||||
@@ -84,11 +84,11 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri
|
||||
function resumeAgents(db: AppDatabase, companyId: string, agentIds: Iterable<string>): void {
|
||||
for (const agentId of agentIds) {
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.companyId !== companyId) continue;
|
||||
if (["terminated", "pending_approval"].includes(agent.status)) continue;
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(agent.id, true, agent.heartbeatIntervalSec || 120);
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn(`[execution-mode-reconciler] resumeAgents failed for ${agentId}:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -143,10 +143,10 @@ export function reconcileExecutionModeChange({
|
||||
});
|
||||
}
|
||||
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
for (const waitState of waitStates) {
|
||||
if (!REVIEW_WAIT_STATE_TYPES.has(waitState.waitingFor)) continue;
|
||||
db.threads.resolveThreadWaitState(waitState.id, companyId);
|
||||
db.core.threads.resolveThreadWaitState(waitState.id, companyId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ export function reconcileExecutionModeChange({
|
||||
companyId,
|
||||
);
|
||||
for (const task of tasks) {
|
||||
db.tasks.setTaskRequiresUserReview(task.id, false);
|
||||
db.core.tasks.setTaskRequiresUserReview(task.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export function shouldAutoApprove(
|
||||
companyId: string,
|
||||
approvalType: string,
|
||||
): boolean {
|
||||
const rawMode = db.company.getCompanyExecutionMode(companyId);
|
||||
const rawMode = db.core.company.getCompanyExecutionMode(companyId);
|
||||
assertExecutionMode(rawMode);
|
||||
const mode = rawMode;
|
||||
|
||||
@@ -89,7 +89,7 @@ export function shouldAutoApprove(
|
||||
return false;
|
||||
}
|
||||
|
||||
const settings = db.company.getCompanyApprovalSettings(companyId);
|
||||
const settings = db.core.company.getCompanyApprovalSettings(companyId);
|
||||
if (!settings?.autoApproveHires) return false;
|
||||
|
||||
// When reviewDeliverables is ON, CEO strategy needs human review
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function notifyHireApproved(
|
||||
try {
|
||||
let agent;
|
||||
try {
|
||||
agent = db.entityCrud.getAgent(input.agentId);
|
||||
agent = db.core.entity.getAgent(input.agentId);
|
||||
} catch (err) {
|
||||
const classified = classifyUnknownError(err, {
|
||||
kind: "recoverable",
|
||||
@@ -77,7 +77,7 @@ export async function notifyHireApproved(
|
||||
};
|
||||
|
||||
log.info(`[hire-hook] Hire approved for agent ${agent.name} (${input.agentId}) on connector ${connectorDef.id}`);
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: input.companyId,
|
||||
actor: "system",
|
||||
action: "hire_hook.success",
|
||||
@@ -95,7 +95,7 @@ export async function notifyHireApproved(
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log.error(`[${classified.code}] ${classified.message}: ${message}`);
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: input.companyId,
|
||||
actor: "system",
|
||||
action: "hire_hook.exception",
|
||||
|
||||
@@ -56,15 +56,15 @@ export function registerAgentCoordinationHandlers({
|
||||
const parsed = markMessageReadSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const message = db.messages.getAgentMessage(parsed.messageId, parsed.companyId);
|
||||
const message = db.core.messages.getAgentMessage(parsed.messageId, parsed.companyId);
|
||||
if (!message) {
|
||||
return fail("MESSAGE_NOT_FOUND", "Message not found in the current company.");
|
||||
}
|
||||
if (parsed.readerAgentId && !db.settings.belongsToCompany("agents", parsed.readerAgentId, parsed.companyId)) {
|
||||
if (parsed.readerAgentId && !db.core.settings.belongsToCompany("agents", parsed.readerAgentId, parsed.companyId)) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", "The selected message reader does not belong to the current company.");
|
||||
}
|
||||
if (parsed.readerAgentId) {
|
||||
const visibleToReader = db.messages
|
||||
const visibleToReader = db.core.messages
|
||||
.listAgentMessages(parsed.companyId, {
|
||||
agentId: parsed.readerAgentId,
|
||||
messageId: parsed.messageId,
|
||||
@@ -75,7 +75,7 @@ export function registerAgentCoordinationHandlers({
|
||||
return fail("MESSAGE_NOT_VISIBLE", "This message is not visible to the selected reader.");
|
||||
}
|
||||
}
|
||||
db.messages.markMessageReadForReader(parsed.messageId, parsed.companyId, parsed.readerAgentId ?? null);
|
||||
db.core.messages.markMessageReadForReader(parsed.messageId, parsed.companyId, parsed.readerAgentId ?? null);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -84,7 +84,7 @@ export function registerAgentCoordinationHandlers({
|
||||
const parsed = searchMessagesSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.messages.searchMessages(parsed.companyId, parsed.query, {
|
||||
return ok(db.core.messages.searchMessages(parsed.companyId, parsed.query, {
|
||||
channel: parsed.channel,
|
||||
channelTargetId: parsed.channelTargetId,
|
||||
limit: parsed.limit,
|
||||
|
||||
@@ -29,7 +29,7 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = automationRuleInputSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const id = db.automationMod.saveAutomationRule(parsed);
|
||||
const id = db.automation.rules.saveAutomationRule(parsed);
|
||||
publishDomainChanged();
|
||||
return ok(id);
|
||||
});
|
||||
@@ -38,10 +38,10 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Automation rule not found in the current company.");
|
||||
}
|
||||
db.automationMod.deleteAutomationRule(parsed.id, parsed.companyId);
|
||||
db.automation.rules.deleteAutomationRule(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -50,10 +50,10 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = toggleAutomationRuleSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("automation_rules", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Automation rule not found in the current company.");
|
||||
}
|
||||
db.automationMod.toggleAutomationRule(parsed.id, parsed.companyId, parsed.status);
|
||||
db.automation.rules.toggleAutomationRule(parsed.id, parsed.companyId, parsed.status);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -62,14 +62,14 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = companyIdSchema.parse(companyId);
|
||||
const companyError = ensureCompanyExists(parsed);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.automationMod.getAutomationLog(parsed));
|
||||
return ok(db.automation.rules.getAutomationLog(parsed));
|
||||
});
|
||||
|
||||
registerHandle(IPC_CHANNELS.saveWorkflow, async (_event, payload) => {
|
||||
const parsed = workflowInputSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const id = db.automationMod.saveWorkflow(parsed);
|
||||
const id = db.automation.rules.saveWorkflow(parsed);
|
||||
publishDomainChanged();
|
||||
return ok(id);
|
||||
});
|
||||
@@ -78,10 +78,10 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Workflow not found in the current company.");
|
||||
}
|
||||
db.automationMod.deleteWorkflow(parsed.id, parsed.companyId);
|
||||
db.automation.rules.deleteWorkflow(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -90,7 +90,7 @@ export function registerAutomationOperationsHandlers({
|
||||
const parsed = runWorkflowSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("workflow_pipelines", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Workflow not found in the current company.");
|
||||
}
|
||||
await runWorkflowPipeline(parsed.id, parsed.companyId);
|
||||
|
||||
@@ -30,7 +30,7 @@ export function registerBrowserHandlers({
|
||||
const input = socialAccountInputSchema.parse(payload);
|
||||
const err = ensureCompanyExists(input.companyId);
|
||||
if (err) return err;
|
||||
const id = db.social.saveSocialAccount(input);
|
||||
const id = db.core.social.saveSocialAccount(input);
|
||||
publishDomainChanged();
|
||||
return ok(id);
|
||||
});
|
||||
@@ -39,12 +39,12 @@ export function registerBrowserHandlers({
|
||||
const input = deleteEntitySchema.parse(payload);
|
||||
const err = ensureCompanyExists(input.companyId);
|
||||
if (err) return err;
|
||||
const browserActions = db.social.listBrowserActions(input.companyId, { socialAccountId: input.id, limit: 500 });
|
||||
const browserActions = db.core.social.listBrowserActions(input.companyId, { socialAccountId: input.id, limit: 500 });
|
||||
await getBrowserActions()?.clearSocialAccountData(
|
||||
input.id,
|
||||
browserActions.flatMap((action: { screenshotPath: string | null }) => (action.screenshotPath ? [action.screenshotPath] : [])),
|
||||
);
|
||||
db.social.deleteSocialAccount(input.id, input.companyId);
|
||||
db.core.social.deleteSocialAccount(input.id, input.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -53,7 +53,7 @@ export function registerBrowserHandlers({
|
||||
const input = browserActionQuerySchema.parse(payload);
|
||||
const err = ensureCompanyExists(input.companyId);
|
||||
if (err) return err;
|
||||
return ok(db.social.listBrowserActions(input.companyId, {
|
||||
return ok(db.core.social.listBrowserActions(input.companyId, {
|
||||
socialAccountId: input.socialAccountId,
|
||||
status: input.status,
|
||||
limit: input.limit,
|
||||
@@ -62,12 +62,12 @@ export function registerBrowserHandlers({
|
||||
|
||||
registerHandle(IPC_CHANNELS.cancelBrowserAction, (_event, payload) => {
|
||||
const input = cancelBrowserActionSchema.parse(payload);
|
||||
const action = db.social.getBrowserAction(input.actionId, input.companyId);
|
||||
const action = db.core.social.getBrowserAction(input.actionId, input.companyId);
|
||||
if (!action) {
|
||||
return fail("BROWSER_ACTION_NOT_FOUND", "Browser action not found.");
|
||||
}
|
||||
getBrowserActions()?.cancelAction(input.actionId);
|
||||
db.social.updateBrowserAction(input.actionId, {
|
||||
db.core.social.updateBrowserAction(input.actionId, {
|
||||
status: "cancelled",
|
||||
resultSummary: "Browser action cancelled from the desktop control surface.",
|
||||
errorMessage: "Action cancelled.",
|
||||
@@ -86,7 +86,7 @@ export function registerBrowserHandlers({
|
||||
return fail("BROWSER_NOT_READY", "Browser manager not initialized");
|
||||
}
|
||||
|
||||
const account = db.social.getSocialAccount(input.socialAccountId, input.companyId);
|
||||
const account = db.core.social.getSocialAccount(input.socialAccountId, input.companyId);
|
||||
if (!account) {
|
||||
return fail("ACCOUNT_NOT_FOUND", "Social account not found");
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export function registerBrowserHandlers({
|
||||
inflightBrowserLogins.add(input.socialAccountId);
|
||||
browserActions.launchLoginBrowser(input.socialAccountId, account.platform).then(async (result) => {
|
||||
if (result.success) {
|
||||
const latestAccount = db.social.getSocialAccount(input.socialAccountId, account.companyId);
|
||||
const latestAccount = db.core.social.getSocialAccount(input.socialAccountId, account.companyId);
|
||||
if (!latestAccount) {
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ export function registerBrowserHandlers({
|
||||
} catch (err) {
|
||||
logger.warn(`[ipc/browser] extractProfileFromSession failed for social account ${input.socialAccountId}:`, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
db.social.saveSocialAccount({
|
||||
db.core.social.saveSocialAccount({
|
||||
id: latestAccount.id,
|
||||
companyId: latestAccount.companyId,
|
||||
platform: latestAccount.platform,
|
||||
|
||||
@@ -32,7 +32,7 @@ export function registerBudgetHandlers({
|
||||
const parsed = budgetPolicyInputSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const id = db.budget.saveBudgetPolicy(parsed);
|
||||
const id = db.core.budget.saveBudgetPolicy(parsed);
|
||||
// D-06: Auto-resolve incidents if limit was increased above current spend
|
||||
if (budgetService) {
|
||||
try {
|
||||
@@ -50,7 +50,7 @@ export function registerBudgetHandlers({
|
||||
const parsed = deleteBudgetPolicySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
db.budget.deleteBudgetPolicy(parsed.companyId, parsed.policyId);
|
||||
db.core.budget.deleteBudgetPolicy(parsed.companyId, parsed.policyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -60,7 +60,7 @@ export function registerBudgetHandlers({
|
||||
const parsed = listBudgetPoliciesSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.budget.listBudgetPolicies(parsed.companyId));
|
||||
return ok(db.core.budget.listBudgetPolicies(parsed.companyId));
|
||||
});
|
||||
|
||||
// ---- List budget incidents ----
|
||||
@@ -68,7 +68,7 @@ export function registerBudgetHandlers({
|
||||
const parsed = listBudgetIncidentsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.budget.listBudgetIncidents(parsed.companyId, parsed.status));
|
||||
return ok(db.core.budget.listBudgetIncidents(parsed.companyId, parsed.status));
|
||||
});
|
||||
|
||||
// ---- Resolve a budget incident manually ----
|
||||
@@ -76,7 +76,7 @@ export function registerBudgetHandlers({
|
||||
const parsed = resolveBudgetIncidentSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
db.budget.resolveBudgetIncident(parsed.incidentId, parsed.companyId);
|
||||
db.core.budget.resolveBudgetIncident(parsed.incidentId, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -63,10 +63,10 @@ export function registerDocumentHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("documents", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("documents", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Document not found in the current company.");
|
||||
}
|
||||
db.documentsMod.deleteDocument(parsed.id, parsed.companyId);
|
||||
db.core.documents.deleteDocument(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ export function registerKnowledgeEntryHandlers({
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
|
||||
const id = db.knowledgeMod.saveKnowledgeEntry(parsed);
|
||||
const id = db.core.knowledge.saveKnowledgeEntry(parsed);
|
||||
publishDomainChanged();
|
||||
return ok(id);
|
||||
});
|
||||
@@ -37,10 +37,10 @@ export function registerKnowledgeEntryHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("knowledge_base", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("knowledge_base", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Knowledge entry not found in the current company.");
|
||||
}
|
||||
db.knowledgeMod.deleteKnowledgeEntry(parsed.id, parsed.companyId);
|
||||
db.core.knowledge.deleteKnowledgeEntry(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ export function registerMeetingHandlers({
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
|
||||
const id = db.knowledgeMod.saveMeeting(parsed);
|
||||
const id = db.core.knowledge.saveMeeting(parsed);
|
||||
publishDomainChanged();
|
||||
return ok(id);
|
||||
});
|
||||
@@ -43,10 +43,10 @@ export function registerMeetingHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("meetings", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("meetings", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Meeting not found in the current company.");
|
||||
}
|
||||
db.knowledgeMod.deleteMeeting(parsed.id, parsed.companyId);
|
||||
db.core.knowledge.deleteMeeting(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -18,14 +18,14 @@ export function registerSprintHandlers({
|
||||
let previousSprintStatus: string | null = null;
|
||||
if (parsed.id) {
|
||||
try {
|
||||
previousSprintStatus = db.snap.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null;
|
||||
previousSprintStatus = db.projection.snapshot.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null;
|
||||
} catch (err) {
|
||||
logger.warn(`[ipc/sprints] listSnapshot failed while resolving sprint ${parsed.id} previous status:`, err instanceof Error ? err.message : String(err));
|
||||
previousSprintStatus = null;
|
||||
}
|
||||
}
|
||||
|
||||
const id = db.knowledgeMod.saveSprint(parsed);
|
||||
const id = db.core.knowledge.saveSprint(parsed);
|
||||
if (parsed.status === "active" && previousSprintStatus !== "active") {
|
||||
dispatchAutomation("sprint_started", { companyId: parsed.companyId });
|
||||
}
|
||||
@@ -40,10 +40,10 @@ export function registerSprintHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("sprints", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("sprints", parsed.id, parsed.companyId)) {
|
||||
return fail("ENTITY_NOT_FOUND", "Sprint not found in the current company.");
|
||||
}
|
||||
db.knowledgeMod.deleteSprint(parsed.id, parsed.companyId);
|
||||
db.core.knowledge.deleteSprint(parsed.id, parsed.companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -46,11 +46,11 @@ export function registerCoreAdminHandlers({
|
||||
>) {
|
||||
registerHandle(IPC_CHANNELS.saveCompany, async (_event, payload) => {
|
||||
const parsed = companyInputSchema.parse(payload);
|
||||
const previousMode = parsed.id ? db.company.getCompanyExecutionMode(parsed.id) : parsed.executionMode;
|
||||
const previousMode = parsed.id ? db.core.company.getCompanyExecutionMode(parsed.id) : parsed.executionMode;
|
||||
const previousApprovalSettings = parsed.id
|
||||
? db.company.getCompanyApprovalSettings(parsed.id)
|
||||
? db.core.company.getCompanyApprovalSettings(parsed.id)
|
||||
: { autoApproveHires: parsed.autoApproveHires, reviewDeliverables: parsed.reviewDeliverables };
|
||||
const id = db.company.saveCompany(parsed);
|
||||
const id = db.core.company.saveCompany(parsed);
|
||||
reconcileExecutionModeChange({
|
||||
db,
|
||||
companyId: id,
|
||||
@@ -60,7 +60,7 @@ export function registerCoreAdminHandlers({
|
||||
nextReviewDeliverables: parsed.reviewDeliverables,
|
||||
handleApprovalResolved,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: id,
|
||||
actor: "board",
|
||||
action: parsed.id ? "company.updated" : "company.created",
|
||||
@@ -76,10 +76,10 @@ export function registerCoreAdminHandlers({
|
||||
if (typeof companyId !== "string" || !companyId) {
|
||||
return fail("INVALID_COMPANY_ID", "A valid company id is required.");
|
||||
}
|
||||
if (!db.settings.hasCompany(companyId)) {
|
||||
if (!db.core.settings.hasCompany(companyId)) {
|
||||
return fail("COMPANY_NOT_FOUND", "The selected company does not exist.");
|
||||
}
|
||||
db.settings.setCurrentCompany(companyId);
|
||||
db.core.settings.setCurrentCompany(companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -93,12 +93,12 @@ export function registerCoreAdminHandlers({
|
||||
// The per-company disabled-models pool is not consulted here —
|
||||
// connector config is a global hint, and pool enforcement
|
||||
// happens at run dispatch time where the company is known.
|
||||
db.connectorsCrud.saveConnector({
|
||||
db.core.connectors.saveConnector({
|
||||
...parsed,
|
||||
model: parsed.model ?? null,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
companyId: db.settings.getCurrentCompanyId() ?? "00000000-0000-0000-0000-000000000000",
|
||||
db.core.activity.addActivity({
|
||||
companyId: db.core.settings.getCurrentCompanyId() ?? "00000000-0000-0000-0000-000000000000",
|
||||
actor: "board",
|
||||
action: "connector.updated",
|
||||
entityType: "connector",
|
||||
@@ -127,7 +127,7 @@ export function registerCoreAdminHandlers({
|
||||
|
||||
registerHandle(IPC_CHANNELS.setConnectorDisabledModels, async (_event, payload) => {
|
||||
const parsed = connectorDisabledModelsSchema.parse(payload);
|
||||
db.settings.setDisabledModels(parsed.companyId, parsed.connectorId, parsed.disabledModels);
|
||||
db.core.settings.setDisabledModels(parsed.companyId, parsed.connectorId, parsed.disabledModels);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
@@ -136,11 +136,11 @@ export function registerCoreAdminHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("secrets", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("secrets", parsed.id, parsed.companyId)) {
|
||||
return fail("SECRET_NOT_FOUND", "Secret not found in the current company.");
|
||||
}
|
||||
db.entityCrud.deleteSecret(parsed.id, parsed.companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.entity.deleteSecret(parsed.id, parsed.companyId);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: "secret.deleted",
|
||||
@@ -156,10 +156,10 @@ export function registerCoreAdminHandlers({
|
||||
if (typeof companyId !== "string" || companyId.length === 0) {
|
||||
return fail("INVALID_INPUT", "Company ID is required.");
|
||||
}
|
||||
if (!db.settings.hasCompany(companyId)) {
|
||||
if (!db.core.settings.hasCompany(companyId)) {
|
||||
return fail("COMPANY_NOT_FOUND", "Company not found.");
|
||||
}
|
||||
db.company.deleteCompany(companyId);
|
||||
db.core.company.deleteCompany(companyId);
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
});
|
||||
|
||||
@@ -40,8 +40,8 @@ export function registerCoreAgentGraphHandlers({
|
||||
if (invalidReference) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
const id = db.workspacesMod.saveWorkspace(parsed);
|
||||
db.activityMod.addActivity({
|
||||
const id = db.core.workspaces.saveWorkspace(parsed);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: parsed.id ? "workspace.updated" : "workspace.created",
|
||||
@@ -64,7 +64,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
"Create new agents through the hiring workflow so approval, onboarding, and org state stay synchronized.",
|
||||
);
|
||||
}
|
||||
const existingAgent = parsed.id ? db.entityCrud.findAgent(parsed.id) : null;
|
||||
const existingAgent = parsed.id ? db.core.entity.findAgent(parsed.id) : null;
|
||||
if (!existingAgent || existingAgent.companyId !== parsed.companyId) {
|
||||
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
if (invalidReference) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
const connectorRecord = db.connectorsCrud.findConnector(parsed.connectorId);
|
||||
const connectorRecord = db.core.connectors.findConnector(parsed.connectorId);
|
||||
if (!connectorRecord) {
|
||||
return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist.");
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
}
|
||||
let id: string;
|
||||
try {
|
||||
id = db.agents.saveAgent({
|
||||
id = db.core.agents.saveAgent({
|
||||
id: parsed.id ?? null,
|
||||
companyId: parsed.companyId,
|
||||
name: parsed.name,
|
||||
@@ -124,7 +124,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
} catch (error) {
|
||||
return fail("INVALID_AGENT_HIERARCHY", error instanceof Error ? error.message : "Agent could not be saved.");
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: parsed.id ? "agent.updated" : "agent.created",
|
||||
@@ -155,7 +155,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
if (invalidReference) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
const connectorRecord = db.connectorsCrud.findConnector(parsed.connectorId);
|
||||
const connectorRecord = db.core.connectors.findConnector(parsed.connectorId);
|
||||
if (!connectorRecord) {
|
||||
return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist.");
|
||||
}
|
||||
@@ -188,7 +188,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
const managerLabel = resolvedReportsTo
|
||||
? (() => {
|
||||
try {
|
||||
return db.entityCrud.getAgent(resolvedReportsTo).name;
|
||||
return db.core.entity.getAgent(resolvedReportsTo).name;
|
||||
} catch (err) {
|
||||
logger.warn(`[ipc/agent-graph] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
|
||||
return resolvedReportsTo;
|
||||
@@ -237,7 +237,7 @@ export function registerCoreAgentGraphHandlers({
|
||||
return fail("HIRE_REQUEST_FAILED", error instanceof Error ? error.message : "Hire request could not be created.");
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: parsed.requestedByAgentId ?? "board",
|
||||
action: "agent.hire_requested",
|
||||
@@ -268,18 +268,18 @@ export function registerCoreAgentGraphHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("agents", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("agents", parsed.id, parsed.companyId)) {
|
||||
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
|
||||
}
|
||||
const agent = db.entityCrud.getAgent(parsed.id);
|
||||
const agent = db.core.entity.getAgent(parsed.id);
|
||||
if (agent.status === "pending_approval") {
|
||||
return fail(
|
||||
"AGENT_APPROVAL_REQUIRED",
|
||||
"Pending hires cannot be deleted from the agent editor. Reject the hire request from Hiring or Approvals instead.",
|
||||
);
|
||||
}
|
||||
db.agents.deleteAgent(parsed.id, parsed.companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.agents.deleteAgent(parsed.id, parsed.companyId);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: "agent.deleted",
|
||||
@@ -295,18 +295,18 @@ export function registerCoreAgentGraphHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("approvals", parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("approvals", parsed.id, parsed.companyId)) {
|
||||
return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company.");
|
||||
}
|
||||
const approval = db.approvalsMod.getApprovalRecord(parsed.id);
|
||||
const approval = db.core.approvals.getApprovalRecord(parsed.id);
|
||||
if (approval.state === "pending" && approval.type === "hire_agent") {
|
||||
return fail(
|
||||
"APPROVAL_DELETE_BLOCKED",
|
||||
"Pending hire approvals cannot be deleted. Reject the hire request instead so the linked agent record stays consistent.",
|
||||
);
|
||||
}
|
||||
db.approvalsMod.deleteApproval(parsed.id, parsed.companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.approvals.deleteApproval(parsed.id, parsed.companyId);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: "approval.deleted",
|
||||
|
||||
@@ -48,19 +48,19 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = approvalInputSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const invalidTask = parsed.relatedTaskId && !db.settings.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId);
|
||||
const invalidAgent = parsed.requestedByAgentId && !db.settings.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId);
|
||||
const invalidRelatedAgent = parsed.relatedAgentId && !db.settings.belongsToCompany("agents", parsed.relatedAgentId, parsed.companyId);
|
||||
const invalidTask = parsed.relatedTaskId && !db.core.settings.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId);
|
||||
const invalidAgent = parsed.requestedByAgentId && !db.core.settings.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId);
|
||||
const invalidRelatedAgent = parsed.relatedAgentId && !db.core.settings.belongsToCompany("agents", parsed.relatedAgentId, parsed.companyId);
|
||||
if (invalidTask || invalidAgent || invalidRelatedAgent) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", "The approval references data outside the current company.");
|
||||
}
|
||||
let id: string;
|
||||
try {
|
||||
id = db.approvalsMod.requestApproval(parsed);
|
||||
id = db.core.approvals.requestApproval(parsed);
|
||||
} catch (error) {
|
||||
return fail("APPROVAL_INVALID", error instanceof Error ? error.message : "Approval could not be created.");
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: parsed.requestedByAgentId ?? "board",
|
||||
action: "approval.created",
|
||||
@@ -106,10 +106,10 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = approvalDecisionSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) {
|
||||
return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company.");
|
||||
}
|
||||
const approval = db.approvalsMod.getApprovalRecord(parsed.approvalId);
|
||||
const approval = db.core.approvals.getApprovalRecord(parsed.approvalId);
|
||||
if (!approval || approval.companyId !== parsed.companyId) {
|
||||
return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company.");
|
||||
}
|
||||
@@ -130,7 +130,7 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = startRunSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
return fail("TASK_NOT_FOUND", "Task not found in the current company.");
|
||||
}
|
||||
return queueTaskRun(parsed.taskId, "manual", "board");
|
||||
@@ -140,7 +140,7 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = cancelRunSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
return fail("RUN_NOT_FOUND", "Run not found in the current company.");
|
||||
}
|
||||
getWorkerProcess()?.postMessage({ type: "cancel-run", runId: parsed.runId });
|
||||
@@ -151,34 +151,34 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = runLogSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
return fail("RUN_NOT_FOUND", "Run not found in the current company.");
|
||||
}
|
||||
return ok(db.runs.readRunLog(parsed.runId));
|
||||
return ok(db.core.runs.readRunLog(parsed.runId));
|
||||
});
|
||||
|
||||
registerHandle(IPC_CHANNELS.getRunLogChunk, (_event, payload) => {
|
||||
const parsed = runLogChunkSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) {
|
||||
return fail("RUN_NOT_FOUND", "Run not found in the current company.");
|
||||
}
|
||||
return ok(db.runs.readRunLogChunk(parsed.runId, parsed.offset, parsed.limit));
|
||||
return ok(db.core.runs.readRunLogChunk(parsed.runId, parsed.offset, parsed.limit));
|
||||
});
|
||||
|
||||
registerHandle(IPC_CHANNELS.addComment, async (_event, payload) => {
|
||||
const parsed = commentInputSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
const comment = db.activityMod.addComment({
|
||||
const comment = db.core.activity.addComment({
|
||||
companyId: parsed.companyId,
|
||||
taskId: parsed.taskId,
|
||||
authorAgentId: parsed.authorAgentId ?? null,
|
||||
authorName: parsed.authorName,
|
||||
body: parsed.body,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: parsed.authorAgentId ?? "board",
|
||||
action: "comment.created",
|
||||
@@ -189,7 +189,7 @@ export function registerCoreOperationHandlers({
|
||||
emitEvent({ type: "agent-message", taskId: parsed.taskId, comment });
|
||||
emitEvent({ type: "task-updated", taskId: parsed.taskId, action: "updated" });
|
||||
try {
|
||||
const task = db.entityCrud.getTask(parsed.taskId);
|
||||
const task = db.core.entity.getTask(parsed.taskId);
|
||||
if (task.assigneeAgentId) {
|
||||
wakeAgentIfPossible(task.assigneeAgentId, task.companyId, "comment");
|
||||
}
|
||||
@@ -208,11 +208,11 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = heartbeatSettingsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) {
|
||||
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
|
||||
}
|
||||
db.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec);
|
||||
db.activityMod.addActivity({
|
||||
db.core.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: parsed.enabled ? "heartbeat.enabled" : "heartbeat.disabled",
|
||||
@@ -229,7 +229,7 @@ export function registerCoreOperationHandlers({
|
||||
const parsed = triggerHeartbeatSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) {
|
||||
return fail("AGENT_NOT_FOUND", "Agent not found in the current company.");
|
||||
}
|
||||
const runId = wakeAgentIfPossible(parsed.agentId, parsed.companyId, parsed.trigger);
|
||||
|
||||
@@ -32,7 +32,7 @@ export function registerCoreReadHandlers({
|
||||
| "listConnectorModels"
|
||||
| "logger"
|
||||
>) {
|
||||
registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.snap.listRendererSnapshot()));
|
||||
registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.projection.snapshot.listRendererSnapshot()));
|
||||
|
||||
registerHandle(IPC_CHANNELS.pickDirectory, async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
@@ -45,7 +45,7 @@ export function registerCoreReadHandlers({
|
||||
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
||||
return fail("INVALID_PATH", "A valid path is required.");
|
||||
}
|
||||
if (!db.settings.canOpenPath(targetPath)) {
|
||||
if (!db.core.settings.canOpenPath(targetPath)) {
|
||||
return fail("OPEN_PATH_FORBIDDEN", "The requested path is outside the managed desktop workspace.");
|
||||
}
|
||||
const error = await shell.openPath(targetPath);
|
||||
@@ -68,7 +68,7 @@ export function registerCoreReadHandlers({
|
||||
if (!parsed.success) return fail("INVALID_PAYLOAD", "openWorkProduct requires { companyId, id }.");
|
||||
const companyError = ensureCompanyExists(parsed.data.companyId);
|
||||
if (companyError) return companyError;
|
||||
const target = db.documentsMod.resolveWorkProductTarget(parsed.data.id, parsed.data.companyId);
|
||||
const target = db.core.documents.resolveWorkProductTarget(parsed.data.id, parsed.data.companyId);
|
||||
if (target.kind === "error") {
|
||||
const codeByReason = {
|
||||
not_found: "WORK_PRODUCT_NOT_FOUND",
|
||||
@@ -83,7 +83,7 @@ export function registerCoreReadHandlers({
|
||||
const error = await shell.openExternal(target.value).then(() => "").catch((err: unknown) => err instanceof Error ? err.message : String(err));
|
||||
return error ? fail("OPEN_PATH_FAILED", error) : ok(true);
|
||||
}
|
||||
if (!db.settings.canOpenPath(target.value)) {
|
||||
if (!db.core.settings.canOpenPath(target.value)) {
|
||||
return fail("OPEN_PATH_FORBIDDEN", "The requested path is outside the managed desktop workspace.");
|
||||
}
|
||||
const error = await shell.openPath(target.value);
|
||||
@@ -98,12 +98,12 @@ export function registerCoreReadHandlers({
|
||||
return ok(null);
|
||||
}
|
||||
const backupPath = join(result.filePaths[0]!, `agentcompany-backup-${Date.now()}`);
|
||||
await db.backupMod.exportBackup(backupPath);
|
||||
await db.core.backup.exportBackup(backupPath);
|
||||
return ok(backupPath);
|
||||
});
|
||||
|
||||
registerHandle(IPC_CHANNELS.restoreProfile, async () => {
|
||||
const hasActiveRuns = db.snap.listSnapshot().runs.some((run: { status: string }) => run.status === "queued" || run.status === "running");
|
||||
const hasActiveRuns = db.projection.snapshot.listSnapshot().runs.some((run: { status: string }) => run.status === "queued" || run.status === "running");
|
||||
if (hasActiveRuns) {
|
||||
return fail("RESTORE_BLOCKED", "Stop all active runs before restoring a profile backup.");
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export function registerCoreReadHandlers({
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return ok(false);
|
||||
}
|
||||
await db.backupMod.restoreFromBackup(result.filePaths[0]!);
|
||||
await db.core.backup.restoreFromBackup(result.filePaths[0]!);
|
||||
resetAfterRestore();
|
||||
publishDomainChanged();
|
||||
return ok(true);
|
||||
@@ -121,7 +121,7 @@ export function registerCoreReadHandlers({
|
||||
|
||||
registerHandle(IPC_CHANNELS.updateSettings, async (_event, payload) => {
|
||||
const parsed = settingsSchema.parse(payload);
|
||||
db.settings.updateSettings(parsed);
|
||||
db.core.settings.updateSettings(parsed);
|
||||
if (parsed.theme) {
|
||||
nativeTheme.themeSource = parsed.theme;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export function registerCoreReadHandlers({
|
||||
registerHandle(IPC_CHANNELS.getConnectorDisabledModels, (_event, payload) => {
|
||||
try {
|
||||
const parsed = connectorModelPoolQuerySchema.parse(payload);
|
||||
return ok(db.settings.getDisabledModels(parsed.companyId, parsed.connectorId));
|
||||
return ok(db.core.settings.getDisabledModels(parsed.companyId, parsed.connectorId));
|
||||
} catch (err) {
|
||||
logAppError(logger, err, {
|
||||
kind: "user_input",
|
||||
@@ -168,14 +168,14 @@ export function registerCoreReadHandlers({
|
||||
const input = metricsQuerySchema.parse(payload);
|
||||
const err = ensureCompanyExists(input.companyId);
|
||||
if (err) return err;
|
||||
return ok(db.company.getCompanyMetrics(input.companyId, input.days));
|
||||
return ok(db.core.company.getCompanyMetrics(input.companyId, input.days));
|
||||
});
|
||||
|
||||
registerHandle(IPC_CHANNELS.getStandupReport, (_event, payload) => {
|
||||
const companyId = z.string().uuid().parse(payload);
|
||||
const err = ensureCompanyExists(companyId);
|
||||
if (err) return err;
|
||||
return ok(db.company.getStandupReport(companyId));
|
||||
return ok(db.core.company.getStandupReport(companyId));
|
||||
});
|
||||
|
||||
// --- Entity getters for granular IPC updates ---
|
||||
@@ -185,7 +185,7 @@ export function registerCoreReadHandlers({
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
try {
|
||||
const task = db.entityCrud.getTask(parsed.id);
|
||||
const task = db.core.entity.getTask(parsed.id);
|
||||
if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company.");
|
||||
return ok(task);
|
||||
} catch (err) {
|
||||
@@ -204,7 +204,7 @@ export function registerCoreReadHandlers({
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(parsed.id);
|
||||
const agent = db.core.entity.getAgent(parsed.id);
|
||||
if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company.");
|
||||
return ok(agent);
|
||||
} catch (err) {
|
||||
@@ -223,7 +223,7 @@ export function registerCoreReadHandlers({
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
try {
|
||||
const run = db.runs.getRun(parsed.id);
|
||||
const run = db.core.runs.getRun(parsed.id);
|
||||
if (run.companyId !== parsed.companyId) return fail("RUN_NOT_FOUND", "Run not found in current company.");
|
||||
return ok(run);
|
||||
} catch (err) {
|
||||
@@ -242,7 +242,7 @@ export function registerCoreReadHandlers({
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
try {
|
||||
const approval = db.approvalsMod.getApprovalRecord(parsed.id);
|
||||
const approval = db.core.approvals.getApprovalRecord(parsed.id);
|
||||
if (approval.companyId !== parsed.companyId) return fail("APPROVAL_NOT_FOUND", "Approval not found in current company.");
|
||||
return ok(approval);
|
||||
} catch (err) {
|
||||
|
||||
@@ -43,8 +43,8 @@ export function registerCoreTaskGraphHandlers({
|
||||
if (invalidReference) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
const id = db.entityCrud.saveGoal(parsed);
|
||||
db.activityMod.addActivity({
|
||||
const id = db.core.entity.saveGoal(parsed);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: parsed.id ? "goal.updated" : "goal.created",
|
||||
@@ -70,8 +70,8 @@ export function registerCoreTaskGraphHandlers({
|
||||
if (invalidReference) {
|
||||
return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`);
|
||||
}
|
||||
const id = db.entityCrud.saveProject(parsed);
|
||||
db.activityMod.addActivity({
|
||||
const id = db.core.entity.saveProject(parsed);
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: parsed.id ? "project.updated" : "project.created",
|
||||
@@ -113,7 +113,7 @@ export function registerCoreTaskGraphHandlers({
|
||||
const taskId = parsed.id;
|
||||
if (!taskId) return fail("INVALID_TASK_STATE", "Task ID required for updates.");
|
||||
try {
|
||||
const existingTask = db.entityCrud.getTask(taskId);
|
||||
const existingTask = db.core.entity.getTask(taskId);
|
||||
previousStatus = existingTask.status;
|
||||
previousAssigneeAgentId = existingTask.assigneeAgentId;
|
||||
} catch (err) {
|
||||
@@ -124,11 +124,11 @@ export function registerCoreTaskGraphHandlers({
|
||||
}
|
||||
let id: string;
|
||||
try {
|
||||
id = db.tasks.saveTask(parsed);
|
||||
id = db.core.tasks.saveTask(parsed);
|
||||
} catch (error) {
|
||||
return fail("INVALID_TASK_STATE", error instanceof Error ? error.message : "Task could not be saved.");
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: isNew ? "task.created" : "task.updated",
|
||||
@@ -145,7 +145,7 @@ export function registerCoreTaskGraphHandlers({
|
||||
// User (board) manually advancing a flagged task to done: clear the review flag
|
||||
if (parsed.status === "done") {
|
||||
try {
|
||||
db.tasks.setTaskRequiresUserReview(id, false);
|
||||
db.core.tasks.setTaskRequiresUserReview(id, false);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[ipc-task] setTaskRequiresUserReview(false) failed for task ${id}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -175,10 +175,10 @@ export function registerCoreTaskGraphHandlers({
|
||||
});
|
||||
|
||||
const deleteHandlers = [
|
||||
{ channel: IPC_CHANNELS.deleteTask, table: "tasks", entity: "task", deleteFn: (id: string, companyId: string) => db.tasks.deleteTask(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteGoal, table: "goals", entity: "goal", deleteFn: (id: string, companyId: string) => db.entityCrud.deleteGoal(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteProject, table: "projects", entity: "project", deleteFn: (id: string, companyId: string) => db.entityCrud.deleteProject(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteWorkspace, table: "workspaces", entity: "workspace", deleteFn: (id: string, companyId: string) => db.workspacesMod.deleteWorkspace(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteTask, table: "tasks", entity: "task", deleteFn: (id: string, companyId: string) => db.core.tasks.deleteTask(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteGoal, table: "goals", entity: "goal", deleteFn: (id: string, companyId: string) => db.core.entity.deleteGoal(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteProject, table: "projects", entity: "project", deleteFn: (id: string, companyId: string) => db.core.entity.deleteProject(id, companyId) },
|
||||
{ channel: IPC_CHANNELS.deleteWorkspace, table: "workspaces", entity: "workspace", deleteFn: (id: string, companyId: string) => db.core.workspaces.deleteWorkspace(id, companyId) },
|
||||
] as const;
|
||||
|
||||
for (const { channel, table, entity, deleteFn } of deleteHandlers) {
|
||||
@@ -186,11 +186,11 @@ export function registerCoreTaskGraphHandlers({
|
||||
const parsed = deleteEntitySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany(table, parsed.id, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany(table, parsed.id, parsed.companyId)) {
|
||||
return fail(`${entity.toUpperCase()}_NOT_FOUND`, `${entity.charAt(0).toUpperCase() + entity.slice(1)} not found in the current company.`);
|
||||
}
|
||||
deleteFn(parsed.id, parsed.companyId);
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: parsed.companyId,
|
||||
actor: "board",
|
||||
action: `${entity}.deleted`,
|
||||
|
||||
@@ -33,10 +33,10 @@ export function registerRecoveryHandlers({
|
||||
const parsed = recoveryRetrySchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
return fail("TASK_NOT_FOUND", "Task not found in the current company.");
|
||||
}
|
||||
db.tasks.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId);
|
||||
db.core.tasks.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId);
|
||||
try {
|
||||
wakeAgentIfPossible(parsed.agentId, parsed.companyId, "retry_interrupted");
|
||||
} catch (err) {
|
||||
@@ -50,10 +50,10 @@ export function registerRecoveryHandlers({
|
||||
const parsed = recoveryTaskSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) {
|
||||
return fail("TASK_NOT_FOUND", "Task not found in the current company.");
|
||||
}
|
||||
db.tasks.markInterruptedTaskFailed(parsed.taskId);
|
||||
db.core.tasks.markInterruptedTaskFailed(parsed.taskId);
|
||||
publishDomainChanged();
|
||||
return ok(null);
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@ export function registerSkillHandlers({
|
||||
const parsed = listSkillAgentsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.skills.listSkillAgentAssignments(parsed.skillId));
|
||||
return ok(db.core.skills.listSkillAgentAssignments(parsed.skillId));
|
||||
});
|
||||
|
||||
// ---- List skill IDs assigned to an agent ----
|
||||
@@ -176,6 +176,6 @@ export function registerSkillHandlers({
|
||||
const parsed = listAgentSkillsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(parsed.companyId);
|
||||
if (companyError) return companyError;
|
||||
return ok(db.skills.listAgentSkillAssignments(parsed.agentId));
|
||||
return ok(db.core.skills.listAgentSkillAssignments(parsed.agentId));
|
||||
});
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists
|
||||
const input = threadGetterSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(input.companyId);
|
||||
if (companyError) return companyError;
|
||||
const thread = db.threads.getThread(input.threadId, input.companyId);
|
||||
const thread = db.core.threads.getThread(input.threadId, input.companyId);
|
||||
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found");
|
||||
return ok(thread);
|
||||
});
|
||||
@@ -22,7 +22,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists
|
||||
const input = listThreadEventsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(input.companyId);
|
||||
if (companyError) return companyError;
|
||||
const events = db.threads.listThreadEvents(input.threadId, input.companyId, {
|
||||
const events = db.core.threads.listThreadEvents(input.threadId, input.companyId, {
|
||||
limit: input.limit,
|
||||
offset: input.offset,
|
||||
});
|
||||
@@ -33,7 +33,7 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists
|
||||
const input = listThreadWaitStatesSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(input.companyId);
|
||||
if (companyError) return companyError;
|
||||
const waitStates = db.threads.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly });
|
||||
const waitStates = db.core.threads.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly });
|
||||
return ok(waitStates);
|
||||
});
|
||||
|
||||
@@ -41,9 +41,9 @@ export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists
|
||||
const input = listThreadParticipantsSchema.parse(payload);
|
||||
const companyError = ensureCompanyExists(input.companyId);
|
||||
if (companyError) return companyError;
|
||||
const thread = db.threads.getThread(input.threadId, input.companyId);
|
||||
const thread = db.core.threads.getThread(input.threadId, input.companyId);
|
||||
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found");
|
||||
const participants = db.threads.listParticipants(input.threadId);
|
||||
const participants = db.core.threads.listParticipants(input.threadId);
|
||||
return ok(participants);
|
||||
});
|
||||
}
|
||||
@@ -140,8 +140,8 @@ export function createMainRuntimeSupport(state: MainRuntimeState) {
|
||||
function seedDefaultAutomationRules(companyId: string) {
|
||||
const db = requireDb();
|
||||
seedDefaultAutomationRulesImpl(companyId, {
|
||||
listAutomationRuleNames: (targetCompanyId) => db.automationMod.listAutomationRuleNames(targetCompanyId),
|
||||
saveAutomationRule: (input) => db.automationMod.saveAutomationRule(input),
|
||||
listAutomationRuleNames: (targetCompanyId) => db.automation.rules.listAutomationRuleNames(targetCompanyId),
|
||||
saveAutomationRule: (input) => db.automation.rules.saveAutomationRule(input),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ export function validateAgentMessageInput(
|
||||
input: NormalizedAgentMessageInput,
|
||||
): MessageValidationError | null {
|
||||
// Human sender (BOARD_READER_ID) is always a valid sender — skip agent ownership check.
|
||||
if (!input.fromHumanUser && !db.settings.belongsToCompany("agents", input.fromAgentId, input.companyId)) {
|
||||
if (!input.fromHumanUser && !db.core.settings.belongsToCompany("agents", input.fromAgentId, input.companyId)) {
|
||||
return {
|
||||
code: "INVALID_COMPANY_REFERENCE",
|
||||
message: "The message sender does not belong to the current company.",
|
||||
@@ -154,7 +154,7 @@ export function validateAgentMessageInput(
|
||||
};
|
||||
}
|
||||
|
||||
if (input.toAgentId && input.toAgentId !== HUMAN_SENDER_ID && !db.settings.belongsToCompany("agents", input.toAgentId, input.companyId)) {
|
||||
if (input.toAgentId && input.toAgentId !== HUMAN_SENDER_ID && !db.core.settings.belongsToCompany("agents", input.toAgentId, input.companyId)) {
|
||||
return {
|
||||
code: "INVALID_COMPANY_REFERENCE",
|
||||
message: "The message recipient does not belong to the current company.",
|
||||
@@ -168,7 +168,7 @@ export function validateAgentMessageInput(
|
||||
message: "Project messages require a project channel target.",
|
||||
};
|
||||
}
|
||||
if (!db.settings.belongsToCompany("projects", input.channelTargetId, input.companyId)) {
|
||||
if (!db.core.settings.belongsToCompany("projects", input.channelTargetId, input.companyId)) {
|
||||
return {
|
||||
code: "INVALID_COMPANY_REFERENCE",
|
||||
message: "The selected project channel does not belong to the current company.",
|
||||
@@ -184,7 +184,7 @@ export function validateAgentMessageInput(
|
||||
}
|
||||
|
||||
if (input.parentMessageId) {
|
||||
const parentMessage = db.messages.getAgentMessage(input.parentMessageId, input.companyId);
|
||||
const parentMessage = db.core.messages.getAgentMessage(input.parentMessageId, input.companyId);
|
||||
if (!parentMessage) {
|
||||
return {
|
||||
code: "PARENT_MESSAGE_NOT_FOUND",
|
||||
@@ -201,17 +201,17 @@ function resolveProjectWakeTargets(
|
||||
input: NormalizedAgentMessageInput,
|
||||
): string[] {
|
||||
if (!input.channelTargetId) return [];
|
||||
const project = db.entityCrud.getProjectForUpdate(input.channelTargetId, input.companyId);
|
||||
const project = db.core.entity.getProjectForUpdate(input.channelTargetId, input.companyId);
|
||||
const participantIds = new Set<string>();
|
||||
if (project?.leadAgentId) participantIds.add(project.leadAgentId);
|
||||
for (const assigneeId of db.tasks.listProjectTaskAssigneeIds(input.channelTargetId, input.companyId)) {
|
||||
for (const assigneeId of db.core.tasks.listProjectTaskAssigneeIds(input.channelTargetId, input.companyId)) {
|
||||
participantIds.add(assigneeId);
|
||||
}
|
||||
|
||||
if (participantIds.size === 0) return [];
|
||||
const ids = Array.from(participantIds).filter((id) => id !== input.fromAgentId);
|
||||
if (ids.length === 0) return [];
|
||||
return db.agents.listActiveAgentIdsFromIds(input.companyId, ids);
|
||||
return db.core.agents.listActiveAgentIdsFromIds(input.companyId, ids);
|
||||
}
|
||||
|
||||
/** Resolve the agents mentioned in a message body, stripped of the
|
||||
@@ -222,7 +222,7 @@ export function resolveMentionedAgentIds(
|
||||
db: AppDatabase,
|
||||
input: Pick<NormalizedAgentMessageInput, "companyId" | "fromAgentId" | "body">,
|
||||
): string[] {
|
||||
const mentioned = db.agents.findMentionedAgentIds(input.companyId, input.body);
|
||||
const mentioned = db.core.agents.findMentionedAgentIds(input.companyId, input.body);
|
||||
const filtered: string[] = [];
|
||||
for (const agentId of mentioned) {
|
||||
if (!agentId || agentId === input.fromAgentId || agentId === HUMAN_SENDER_ID) continue;
|
||||
@@ -255,7 +255,7 @@ export function resolveMessageWakeTargets(
|
||||
}
|
||||
|
||||
if (input.channel === "department" && input.channelTargetId) {
|
||||
for (const agentId of db.agents.listDepartmentAgentIds(input.companyId, input.channelTargetId)) {
|
||||
for (const agentId of db.core.agents.listDepartmentAgentIds(input.companyId, input.channelTargetId)) {
|
||||
if (agentId !== input.fromAgentId) wakeTargets.add(agentId);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +265,7 @@ export function resolveMessageWakeTargets(
|
||||
// workspace is a listener. Board-only fan-outs use the dedicated
|
||||
// `incident` channel; the previous top-level-only fan-out made the
|
||||
// "company" label a misnomer that silently dropped ICs.
|
||||
for (const agentId of db.agents.listCompanyBroadcastAgentIds(input.companyId)) {
|
||||
for (const agentId of db.core.agents.listCompanyBroadcastAgentIds(input.companyId)) {
|
||||
if (agentId !== input.fromAgentId) wakeTargets.add(agentId);
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ export function resolveMessageWakeTargets(
|
||||
}
|
||||
|
||||
if (input.channel === "incident") {
|
||||
for (const agentId of db.agents.listIncidentPingableAgentIds(input.companyId)) {
|
||||
for (const agentId of db.core.agents.listIncidentPingableAgentIds(input.companyId)) {
|
||||
if (agentId !== input.fromAgentId) wakeTargets.add(agentId);
|
||||
}
|
||||
}
|
||||
@@ -288,7 +288,7 @@ export function resolveMessageWakeTargets(
|
||||
|
||||
// The human participant (`__board__`) is a reader, not a wake-able
|
||||
// agent: it has no runtime, no heartbeat, no DB row. Leaving it in the
|
||||
// set makes `wakeAgentIfPossible` call `db.entityCrud.getAgent("__board__")`,
|
||||
// set makes `wakeAgentIfPossible` call `db.core.entity.getAgent("__board__")`,
|
||||
// which throws "Agent not found" and spams the log. Filter once, at
|
||||
// the point of resolution, so every downstream consumer is clean.
|
||||
wakeTargets.delete(HUMAN_SENDER_ID);
|
||||
@@ -317,8 +317,8 @@ export function dispatchAgentMessage(
|
||||
const persistedMessage = normalized.channel === "direct"
|
||||
? { ...normalized, senderKind, mentionedAgentIds }
|
||||
: { ...normalized, toAgentId: null, senderKind, mentionedAgentIds };
|
||||
const id = deps.db.messages.sendAgentMessage(persistedMessage);
|
||||
const message = deps.db.messages.getAgentMessage(id, normalized.companyId);
|
||||
const id = deps.db.core.messages.sendAgentMessage(persistedMessage);
|
||||
const message = deps.db.core.messages.getAgentMessage(id, normalized.companyId);
|
||||
if (message && deps.emitEvent) {
|
||||
deps.emitEvent({ type: "new-message", message });
|
||||
}
|
||||
@@ -330,7 +330,7 @@ export function dispatchAgentMessage(
|
||||
!normalized.fromHumanUser &&
|
||||
(message.channel !== "direct" || message.toAgentId === HUMAN_SENDER_ID)
|
||||
) {
|
||||
const senderName = deps.db.agents.findAgentName(message.fromAgentId) ?? "Agent";
|
||||
const senderName = deps.db.core.agents.findAgentName(message.fromAgentId) ?? "Agent";
|
||||
deps.notify({
|
||||
title: truncateForNotification(`${senderName} · ${message.subject}`, 80),
|
||||
body: truncateForNotification(message.body, 140),
|
||||
@@ -350,7 +350,7 @@ export function dispatchAgentMessage(
|
||||
const mentionResults: MentionResult[] = [];
|
||||
|
||||
if (deps.wakeAgentIfPossible) {
|
||||
const agentNameRows = deps.db.agents.listAgentNamesByIds(normalized.companyId, wakeTargets);
|
||||
const agentNameRows = deps.db.core.agents.listAgentNamesByIds(normalized.companyId, wakeTargets);
|
||||
const agentNameMap = new Map(agentNameRows.map((a) => [a.id, a.name]));
|
||||
for (const wakeTarget of wakeTargets) {
|
||||
const agentName = agentNameMap.get(wakeTarget) ?? wakeTarget;
|
||||
@@ -367,7 +367,7 @@ export function dispatchAgentMessage(
|
||||
continue;
|
||||
}
|
||||
const runId = deps.wakeAgentIfPossible(wakeTarget, normalized.companyId, wakeTrigger);
|
||||
const hasQueuedWake = !runId && deps.db.wakeup.listWakeupRequests(normalized.companyId, {
|
||||
const hasQueuedWake = !runId && deps.db.automation.wakeup.listWakeupRequests(normalized.companyId, {
|
||||
agentId: wakeTarget,
|
||||
limit: 10,
|
||||
}).some((request) => request.status === "queued" || request.status === "deferred");
|
||||
|
||||
@@ -85,9 +85,9 @@ export function createOnboardingService({
|
||||
}: OnboardingDependencies) {
|
||||
function updateAgentWorkspaceBinding(agentId: string, workspaceId: string | null) {
|
||||
if (!workspaceId) return;
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.workspaceId === workspaceId) return;
|
||||
db.agents.saveAgent({
|
||||
db.core.agents.saveAgent({
|
||||
id: agent.id,
|
||||
companyId: agent.companyId,
|
||||
name: agent.name,
|
||||
@@ -113,7 +113,7 @@ export function createOnboardingService({
|
||||
const trimmedPath = workspacePath.trim();
|
||||
if (!trimmedPath) return null;
|
||||
const wsLocale = getLocale();
|
||||
return db.workspacesMod.saveWorkspace({
|
||||
return db.core.workspaces.saveWorkspace({
|
||||
companyId,
|
||||
name: wsLocale === "zh" ? "默认工作区" : "Default Workspace",
|
||||
localPath: trimmedPath,
|
||||
@@ -158,7 +158,7 @@ export function createOnboardingService({
|
||||
? `Turn the founding goal "${activeGoal.title}" into an execution plan, delegation map, and the first wave of real work.`
|
||||
: "Establish the first operating plan, assign work, and start the company execution loop.";
|
||||
|
||||
const projectId = db.entityCrud.saveProject({
|
||||
const projectId = db.core.entity.saveProject({
|
||||
companyId,
|
||||
goalId: activeGoal?.id ?? null,
|
||||
name: kickoffProjectName,
|
||||
@@ -187,7 +187,7 @@ export function createOnboardingService({
|
||||
`Review the current company state, create the first real tasks, delegate initial work, and report progress through tasks, approvals, and messages.`,
|
||||
].join("\n\n");
|
||||
|
||||
const taskId = db.tasks.saveTask({
|
||||
const taskId = db.core.tasks.saveTask({
|
||||
companyId,
|
||||
projectId,
|
||||
goalId: activeGoal?.id ?? null,
|
||||
@@ -222,10 +222,10 @@ export function createOnboardingService({
|
||||
}
|
||||
|
||||
function bootstrapOnboarding(input: BootstrapInput): OnboardingBootstrapSummary {
|
||||
const connectors = db.snap.listSnapshot({ companyId: null }).connectors;
|
||||
const connectors = db.projection.snapshot.listSnapshot({ companyId: null }).connectors;
|
||||
const selectedConnectorId = resolveOnboardingConnector(connectors, input.connectorId);
|
||||
|
||||
const companyId = db.company.saveCompany({
|
||||
const companyId = db.core.company.saveCompany({
|
||||
name: input.companyName.trim(),
|
||||
description: input.companyDescription.trim(),
|
||||
status: "active",
|
||||
@@ -239,7 +239,7 @@ export function createOnboardingService({
|
||||
// pre-commit; the pool settings arrive in the input payload and
|
||||
// land in the DB only once a company exists to scope them.
|
||||
for (const pool of input.connectorPoolSelections ?? []) {
|
||||
db.settings.setDisabledModels(companyId, pool.connectorId, pool.disabledModels);
|
||||
db.core.settings.setDisabledModels(companyId, pool.connectorId, pool.disabledModels);
|
||||
}
|
||||
|
||||
// Validate the CEO model selection against the now-persisted pool
|
||||
@@ -254,7 +254,7 @@ export function createOnboardingService({
|
||||
|
||||
const goalTitle = input.goalTitle.trim();
|
||||
if (goalTitle) {
|
||||
db.entityCrud.saveGoal({
|
||||
db.core.entity.saveGoal({
|
||||
companyId,
|
||||
title: goalTitle,
|
||||
description: input.goalDescription.trim() || `Achieve: ${goalTitle}`,
|
||||
@@ -265,7 +265,7 @@ export function createOnboardingService({
|
||||
}
|
||||
|
||||
const ceoLocale = getLocale();
|
||||
const ceoId = db.agents.saveAgent({
|
||||
const ceoId = db.core.agents.saveAgent({
|
||||
companyId,
|
||||
name: "CEO",
|
||||
role: "CEO",
|
||||
@@ -284,8 +284,8 @@ export function createOnboardingService({
|
||||
modelStrategy: "explicit",
|
||||
roleLevel: "c_suite",
|
||||
});
|
||||
db.agents.setHeartbeat(ceoId, true, 120);
|
||||
db.settings.setCurrentCompany(companyId);
|
||||
db.core.agents.setHeartbeat(ceoId, true, 120);
|
||||
db.core.settings.setCurrentCompany(companyId);
|
||||
seedDefaultAutomationRules(companyId);
|
||||
|
||||
if (scanAndAssignSkills) {
|
||||
@@ -297,7 +297,7 @@ export function createOnboardingService({
|
||||
|
||||
const kickoff = createInitialExecutionLoop(companyId, workspaceId);
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "board",
|
||||
action: "company.bootstrapped",
|
||||
|
||||
@@ -58,9 +58,9 @@ export function createHeartbeat(
|
||||
} = deps;
|
||||
|
||||
function ensureAgentHeartbeatEnabled(agentId: string) {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (!agent.heartbeatEnabled) {
|
||||
db.agents.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120);
|
||||
db.core.agents.setHeartbeat(agentId, true, agent.heartbeatIntervalSec || 120);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,22 +69,22 @@ export function createHeartbeat(
|
||||
const isMessageWake = trigger === "message";
|
||||
const isCommunicationWake = isMessageWake || trigger === "peer_request";
|
||||
if (!hasWorkerReady()) return SKIPPED("worker_not_ready");
|
||||
const companyStatus = db.company.getCompanyStatus(companyId);
|
||||
const companyStatus = db.core.company.getCompanyStatus(companyId);
|
||||
if (companyStatus !== "active") return SKIPPED("company_inactive");
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.companyId !== companyId) {
|
||||
logger.warn(`[heartbeat] Refusing cross-company run for agent ${agentId}: ${agent.companyId} !== ${companyId}`);
|
||||
return SKIPPED("cross_company");
|
||||
}
|
||||
const pick = pickModelForAgent(agent, {
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (id) => db.settings.getDisabledModels(agent.companyId, id),
|
||||
listModelCapabilities: () => db.modelCapabilities.listModelCapabilities(),
|
||||
readConnectors: () => db.connectorsCrud.readConnectors(),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (id) => db.core.settings.getDisabledModels(agent.companyId, id),
|
||||
listModelCapabilities: () => db.core.modelCapabilities.listModelCapabilities(),
|
||||
readConnectors: () => db.core.connectors.readConnectors(),
|
||||
});
|
||||
if (!pick.ok) {
|
||||
logger.warn(`[heartbeat] Model pick failed for agent ${agent.name}: ${pick.message}`);
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "heartbeat.model_pick_failed",
|
||||
@@ -95,7 +95,7 @@ export function createHeartbeat(
|
||||
return SKIPPED("model_pick_failed");
|
||||
}
|
||||
const assignment = pick.assignment;
|
||||
const connector = db.connectorsCrud.getConnector(assignment.connectorId);
|
||||
const connector = db.core.connectors.getConnector(assignment.connectorId);
|
||||
const effectiveAgentForEligibility: AgentRecord = { ...agent, model: assignment.modelId };
|
||||
const eligibilityFailure = getRunEligibilityFailure(effectiveAgentForEligibility, connector, null);
|
||||
if (eligibilityFailure) {
|
||||
@@ -115,9 +115,9 @@ export function createHeartbeat(
|
||||
// Let getHeartbeatPolicy throw — a missing row signals data corruption
|
||||
// and silently falling back hides the gate that decides whether the
|
||||
// agent runs at all.
|
||||
const heartbeatPolicy = db.agents.getHeartbeatPolicy(agent.id);
|
||||
const heartbeatPolicy = db.core.agents.getHeartbeatPolicy(agent.id);
|
||||
|
||||
const activeRunCount = db.agents.countActiveRunsForAgent(agent.id, companyId);
|
||||
const activeRunCount = db.core.agents.countActiveRunsForAgent(agent.id, companyId);
|
||||
const runAttemptDecision = decideRunAttempt({
|
||||
kind: "heartbeat",
|
||||
trigger,
|
||||
@@ -136,10 +136,10 @@ export function createHeartbeat(
|
||||
if (!heartbeatPolicy.wakeOnDemand && trigger !== "timer" && trigger !== "bootstrap") {
|
||||
return SKIPPED("wake_on_demand_disabled");
|
||||
}
|
||||
if (db.agents.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval");
|
||||
if (db.core.agents.isAgentPendingApproval(agentId)) return SKIPPED("pending_approval");
|
||||
|
||||
const companyMode = db.company.getCompanyExecutionMode(companyId);
|
||||
const approvalSettings = db.company.getCompanyApprovalSettings(companyId);
|
||||
const companyMode = db.core.company.getCompanyExecutionMode(companyId);
|
||||
const approvalSettings = db.core.company.getCompanyApprovalSettings(companyId);
|
||||
const shouldEnforceHumanReview =
|
||||
companyMode === "ai_cofounder" || approvalSettings?.reviewDeliverables === true;
|
||||
|
||||
@@ -147,7 +147,7 @@ export function createHeartbeat(
|
||||
if (
|
||||
shouldEnforceHumanReview &&
|
||||
!isMessageWake &&
|
||||
(db.tasks.hasAgentPendingDeliverableReview(agentId, companyId) || db.tasks.hasAgentTaskAwaitingReview(agentId, companyId))
|
||||
(db.core.tasks.hasAgentPendingDeliverableReview(agentId, companyId) || db.core.tasks.hasAgentTaskAwaitingReview(agentId, companyId))
|
||||
) {
|
||||
return SKIPPED("awaiting_user_review");
|
||||
}
|
||||
@@ -157,22 +157,22 @@ export function createHeartbeat(
|
||||
shouldEnforceHumanReview &&
|
||||
!isMessageWake &&
|
||||
!agent.reportsTo &&
|
||||
db.tasks.hasPendingDeliverableReviews(companyId)
|
||||
db.core.tasks.hasPendingDeliverableReviews(companyId)
|
||||
) {
|
||||
return SKIPPED("pending_deliverable_review");
|
||||
}
|
||||
|
||||
const assignedTasks = isCommunicationWake
|
||||
? []
|
||||
: db.tasks.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]);
|
||||
: db.core.tasks.listAgentTasks(agentId, companyId, ["todo", "in_progress", "blocked"]);
|
||||
const isTimer = trigger === "timer";
|
||||
const candidateTasks = assignedTasks;
|
||||
|
||||
const candidateTask = candidateTasks.find((entry) => !entry.activeRunId) ?? null;
|
||||
let task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null = null;
|
||||
let task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null = null;
|
||||
|
||||
if (candidateTask) {
|
||||
task = db.entityCrud.getTask(candidateTask.id);
|
||||
task = db.core.entity.getTask(candidateTask.id);
|
||||
} else {
|
||||
const hasActiveAssignedRun = candidateTasks.some(
|
||||
(entry) => entry.activeRunId && hasActiveRun({ taskId: entry.id, companyId }),
|
||||
@@ -180,9 +180,9 @@ export function createHeartbeat(
|
||||
if (hasActiveAssignedRun) return SKIPPED("max_concurrent_runs");
|
||||
|
||||
const isCeo = !agent.reportsTo;
|
||||
const isManager = db.agents.getDirectReports(agent.id, companyId).length > 0;
|
||||
const goalCount = db.entityCrud.countActiveGoalsForCompany(companyId);
|
||||
const taskCount = db.tasks.countActiveTasksForCompany(companyId);
|
||||
const isManager = db.core.agents.getDirectReports(agent.id, companyId).length > 0;
|
||||
const goalCount = db.core.entity.countActiveGoalsForCompany(companyId);
|
||||
const taskCount = db.core.tasks.countActiveTasksForCompany(companyId);
|
||||
const companyNeedsBootstrap = goalCount > 0 && taskCount < goalCount;
|
||||
|
||||
if (isTimer && !isCeo && !isManager && !companyNeedsBootstrap) {
|
||||
@@ -200,7 +200,7 @@ export function createHeartbeat(
|
||||
workspaceResolution = resolveWorkspace(task, agent, companyId);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceConfigurationError) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "heartbeat.workspace_unresolved",
|
||||
@@ -219,14 +219,14 @@ export function createHeartbeat(
|
||||
);
|
||||
|
||||
const run = task
|
||||
? db.runs.createTaskRun({
|
||||
? db.core.runs.createTaskRun({
|
||||
companyId,
|
||||
taskId: task.id,
|
||||
agentId: agent.id,
|
||||
workspaceId: workspaceResolution.lockMode === "exclusive" ? (workspace?.id ?? null) : null,
|
||||
connectorId: connector.id,
|
||||
})
|
||||
: db.runs.createRun({
|
||||
: db.core.runs.createRun({
|
||||
companyId,
|
||||
taskId: null,
|
||||
agentId: agent.id,
|
||||
@@ -237,7 +237,7 @@ export function createHeartbeat(
|
||||
if (!task) {
|
||||
throw new Error("Taskless heartbeat run creation returned null.");
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "execution.checkout_rejected",
|
||||
@@ -248,7 +248,7 @@ export function createHeartbeat(
|
||||
return SKIPPED("checkout_race_lost");
|
||||
}
|
||||
|
||||
const companyBasic = db.company.getCompanyBasic(companyId);
|
||||
const companyBasic = db.core.company.getCompanyBasic(companyId);
|
||||
dispatchRunToWorker({
|
||||
runId: run.id,
|
||||
companyId,
|
||||
@@ -260,25 +260,25 @@ export function createHeartbeat(
|
||||
observationPath: workspaceResolution.observationPath,
|
||||
companyName: companyBasic?.name ?? "Unknown",
|
||||
companyDescription: companyBasic?.description ?? "",
|
||||
snapshot: db.snap.listSnapshot(),
|
||||
snapshot: db.projection.snapshot.listSnapshot(),
|
||||
trigger,
|
||||
resolvedModel: assignment.modelId,
|
||||
});
|
||||
|
||||
db.agents.markHeartbeatAt(agentId);
|
||||
db.core.agents.markHeartbeatAt(agentId);
|
||||
|
||||
// Actor audit: record what triggered this run
|
||||
try {
|
||||
const actorType = trigger === "timer" || trigger === "bootstrap" ? "system" : "agent";
|
||||
const actorId = trigger === "timer" || trigger === "bootstrap" ? null : trigger;
|
||||
db.runs.updateRunActor(run.id, actorType, actorId);
|
||||
db.core.runs.updateRunActor(run.id, actorType, actorId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[heartbeat] updateRunActor failed for run ${run.id}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "heartbeat.triggered",
|
||||
@@ -299,7 +299,7 @@ export function createHeartbeat(
|
||||
// raw) from spamming the log with "Agent not found: __board__".
|
||||
if (agentId === HUMAN_SENDER_ID) return null;
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.companyId !== companyId) {
|
||||
logger.warn(`[wake] Refusing cross-company wake for agent ${agentId}: ${agent.companyId} !== ${companyId}`);
|
||||
return null;
|
||||
@@ -317,16 +317,16 @@ export function createHeartbeat(
|
||||
|
||||
const agentBusy = hasActiveRun({ agentId, companyId });
|
||||
if (agentBusy) {
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent: ${trigger}`, context);
|
||||
db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent: ${trigger}`, context);
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = startAgentHeartbeatRun(agentId, companyId, trigger);
|
||||
if (result.status === "skipped") {
|
||||
db.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent (deferred:${result.reason}): ${trigger}`, context);
|
||||
db.automation.wakeup.enqueueWakeupRequest(companyId, agentId, trigger, `Wake for agent (deferred:${result.reason}): ${trigger}`, context);
|
||||
return null;
|
||||
}
|
||||
db.wakeup.completePendingWakeupRequests(agentId, companyId, result.runId);
|
||||
db.automation.wakeup.completePendingWakeupRequests(agentId, companyId, result.runId);
|
||||
return result.runId;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createOrchestrationService(deps: OrchestrationDependencies) {
|
||||
const { db } = deps;
|
||||
|
||||
function hasActiveRun(filter: { taskId?: string; agentId?: string; companyId: string }) {
|
||||
return db.runs.hasActiveRun(filter);
|
||||
return db.core.runs.hasActiveRun(filter);
|
||||
}
|
||||
|
||||
const runDispatch = createRunDispatch(deps);
|
||||
|
||||
@@ -32,7 +32,7 @@ const SUBJECT_PREVIEW_LEN = 80;
|
||||
export function resolveInitialUserTurn(
|
||||
params: {
|
||||
trigger: string | null;
|
||||
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
|
||||
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null;
|
||||
companyId: string;
|
||||
agentId: string;
|
||||
},
|
||||
@@ -46,7 +46,7 @@ export function resolveInitialUserTurn(
|
||||
}
|
||||
|
||||
if (trigger === "message" || trigger === "peer_request") {
|
||||
const unread = deps.db.messages.listAgentInboxUnread(companyId, agentId);
|
||||
const unread = deps.db.core.messages.listAgentInboxUnread(companyId, agentId);
|
||||
if (unread.length > 0) {
|
||||
return buildCommunicationTurn(trigger, unread, deps.db);
|
||||
}
|
||||
@@ -81,9 +81,9 @@ function tryResolveCeoTeamBuildingCue(params: {
|
||||
}): string | null {
|
||||
const { agentId, companyId, db } = params;
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.reportsTo) return null;
|
||||
const activeReports = db.agents
|
||||
const activeReports = db.core.agents
|
||||
.getDirectReports(agentId, companyId)
|
||||
.filter((r) => r.status !== "pending_approval" && r.status !== "terminated");
|
||||
if (activeReports.length > 0) return null;
|
||||
@@ -167,7 +167,7 @@ function buildGenericWakeTurn(trigger: string | null): string {
|
||||
|
||||
function resolveAgentLabel(db: AppDatabase, agentId: string): string {
|
||||
try {
|
||||
const name = db.agents.findAgentName(agentId);
|
||||
const name = db.core.agents.findAgentName(agentId);
|
||||
if (name && name.length > 0) return name;
|
||||
} catch {
|
||||
// fall through
|
||||
|
||||
@@ -44,15 +44,15 @@ export function resolveInternalToolsForAgent(
|
||||
function isInitialTeamBuildingPhase(db: AppDatabase, agent: AgentRecord): boolean {
|
||||
if (!canFileCeoStrategy(db, agent.companyId, agent.id)) return false;
|
||||
|
||||
const directReports = db.agents.getDirectReports(agent.id, agent.companyId);
|
||||
const directReports = db.core.agents.getDirectReports(agent.id, agent.companyId);
|
||||
const activeReports = directReports.filter(
|
||||
(report) => report.status !== "pending_approval" && report.status !== "terminated",
|
||||
);
|
||||
const pendingReportRows = directReports.filter((report) => report.status === "pending_approval");
|
||||
const pendingHireApprovals = db.approvalsMod
|
||||
const pendingHireApprovals = db.core.approvals
|
||||
.listCompanyApprovalsByType(agent.companyId, "hire_agent")
|
||||
.filter((approval) => approval.state === "pending");
|
||||
const pendingStrategyApprovals = db.approvalsMod
|
||||
const pendingStrategyApprovals = db.core.approvals
|
||||
.listCompanyApprovalsByType(agent.companyId, "approve_ceo_strategy")
|
||||
.filter((approval) => approval.state === "pending");
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ function inferLockMode(
|
||||
return { lockMode: "exclusive", workspaceId: agent.workspaceId, source: "agent" };
|
||||
}
|
||||
|
||||
const projectWorkspace = db.workspacesMod.getProjectPrimaryWorkspace(
|
||||
const projectWorkspace = db.core.workspaces.getProjectPrimaryWorkspace(
|
||||
task?.projectId ?? null,
|
||||
companyId,
|
||||
);
|
||||
@@ -179,7 +179,7 @@ export function resolveWorkspaceWithLockMode(
|
||||
// `getCompanyObservationWorkspace` returns the primary or oldest
|
||||
// workspace; null only when the company has no workspace row at
|
||||
// all, in which case there is genuinely nothing to observe.
|
||||
const observation = db.workspacesMod.getCompanyObservationWorkspace(companyId);
|
||||
const observation = db.core.workspaces.getCompanyObservationWorkspace(companyId);
|
||||
return {
|
||||
workspace: null,
|
||||
lockMode: "none",
|
||||
@@ -194,7 +194,7 @@ export function resolveWorkspaceWithLockMode(
|
||||
);
|
||||
}
|
||||
|
||||
const workspace = db.workspacesMod.getWorkspace(decision.workspaceId);
|
||||
const workspace = db.core.workspaces.getWorkspace(decision.workspaceId);
|
||||
if (!workspace) {
|
||||
throw new WorkspaceConfigurationError(
|
||||
`Resolved workspace ${decision.workspaceId} does not exist (companyId=${companyId}, source=${decision.source}).`,
|
||||
|
||||
@@ -57,7 +57,7 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
|
||||
): void {
|
||||
const errorMessage = formatDispatchError(error);
|
||||
logger.error(`[run] Dispatch failed before worker enqueue for ${params.runId}: ${errorMessage}`);
|
||||
db.runs.finishRun({
|
||||
db.core.runs.finishRun({
|
||||
runId: params.runId,
|
||||
status: "failed",
|
||||
summary: "Run failed before worker enqueue.",
|
||||
@@ -68,9 +68,9 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
|
||||
unattributedCost: true,
|
||||
});
|
||||
if (workspaceLockClaimed && params.workspaceMode === "exclusive" && params.workspace) {
|
||||
db.workspacesMod.releaseWorkspaceLock(params.workspace.id, params.runId);
|
||||
db.core.workspaces.releaseWorkspaceLock(params.workspace.id, params.runId);
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: params.companyId,
|
||||
actor: params.agent.id,
|
||||
action: "run.failed",
|
||||
@@ -102,8 +102,8 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
|
||||
}
|
||||
const modelSelection = validateConnectorModelSelection(
|
||||
{
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId),
|
||||
},
|
||||
agent.companyId,
|
||||
connector.id,
|
||||
@@ -142,8 +142,8 @@ export function createRunDispatch(deps: OrchestrationDependencies) {
|
||||
// `todo` via the `finishRun` never-started branch, so heartbeat picks
|
||||
// it up on the next cycle with no manual intervention.
|
||||
if (params.workspaceMode === "exclusive" && params.workspace) {
|
||||
if (!db.workspacesMod.claimWorkspaceLock(params.workspace.id, params.runId)) {
|
||||
db.runs.finishRun({
|
||||
if (!db.core.workspaces.claimWorkspaceLock(params.workspace.id, params.runId)) {
|
||||
db.core.runs.finishRun({
|
||||
runId: params.runId,
|
||||
status: "cancelled",
|
||||
summary: null,
|
||||
|
||||
@@ -51,8 +51,8 @@ export function createHeartbeatScheduler(
|
||||
lastTickAt: string,
|
||||
) {
|
||||
try {
|
||||
const existing = db.agents.getRuntimeState(agentId) as Record<string, unknown>;
|
||||
db.agents.setRuntimeState(agentId, companyId, {
|
||||
const existing = db.core.agents.getRuntimeState(agentId) as Record<string, unknown>;
|
||||
db.core.agents.setRuntimeState(agentId, companyId, {
|
||||
...existing,
|
||||
heartbeatSkip: {
|
||||
reason,
|
||||
@@ -73,11 +73,11 @@ export function createHeartbeatScheduler(
|
||||
|
||||
function clearSkip(agentId: string, companyId: string) {
|
||||
try {
|
||||
const existing = db.agents.getRuntimeState(agentId) as Record<string, unknown>;
|
||||
const existing = db.core.agents.getRuntimeState(agentId) as Record<string, unknown>;
|
||||
if (!("heartbeatSkip" in existing)) return;
|
||||
const next = { ...existing };
|
||||
delete next.heartbeatSkip;
|
||||
db.agents.setRuntimeState(agentId, companyId, next);
|
||||
db.core.agents.setRuntimeState(agentId, companyId, next);
|
||||
} catch (error) {
|
||||
logAppError(logger, error, {
|
||||
kind: "recoverable",
|
||||
@@ -123,7 +123,7 @@ export function createHeartbeatScheduler(
|
||||
: null;
|
||||
if (action) {
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action,
|
||||
@@ -154,7 +154,7 @@ export function createHeartbeatScheduler(
|
||||
if (!previous) return;
|
||||
skipStreakByAgent.delete(agentId);
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "heartbeat.skip_cleared",
|
||||
@@ -196,12 +196,12 @@ export function createHeartbeatScheduler(
|
||||
try {
|
||||
if (isQuitting() || !hasWorkerReady()) return;
|
||||
|
||||
const activeCompanies = db.company.listActiveCompanyIds();
|
||||
const activeCompanies = db.core.company.listActiveCompanyIds();
|
||||
const now = Date.now();
|
||||
|
||||
for (const company of activeCompanies) {
|
||||
try {
|
||||
const eligibleAgents = db.agents.listHeartbeatEligibleAgents(company.id);
|
||||
const eligibleAgents = db.core.agents.listHeartbeatEligibleAgents(company.id);
|
||||
|
||||
for (const agent of eligibleAgents) {
|
||||
if (hasActiveRun({ agentId: agent.id, companyId: company.id })) continue;
|
||||
@@ -223,7 +223,7 @@ export function createHeartbeatScheduler(
|
||||
tryWake(agent.id, company.id, agent.name, "timer");
|
||||
}
|
||||
|
||||
const goalCount = db.entityCrud.countActiveGoalsForCompany(company.id);
|
||||
const goalCount = db.core.entity.countActiveGoalsForCompany(company.id);
|
||||
// Bootstrap when the company has active goals but no work is
|
||||
// actually advancing. The previous gate ("taskCount === 0")
|
||||
// missed the case where every task is in_review or blocked —
|
||||
@@ -232,9 +232,9 @@ export function createHeartbeatScheduler(
|
||||
// regular timer interval. countAdvancingTasksForCompany
|
||||
// counts only todo + in_progress so bootstrapping still
|
||||
// fires once the team stalls.
|
||||
const advancingCount = db.tasks.countAdvancingTasksForCompany(company.id);
|
||||
const advancingCount = db.core.tasks.countAdvancingTasksForCompany(company.id);
|
||||
if (goalCount > 0 && advancingCount === 0) {
|
||||
const ceo = db.agents.getCeoForCompany(company.id);
|
||||
const ceo = db.core.agents.getCeoForCompany(company.id);
|
||||
const ceoLastWake = ceo?.lastHeartbeatAt ? new Date(ceo.lastHeartbeatAt).getTime() : 0;
|
||||
const ceoLastAttempt = ceo ? attemptedAtByAgent.get(ceo.id) ?? 0 : 0;
|
||||
const ceoLastTouch = Math.max(ceoLastWake, ceoLastAttempt);
|
||||
|
||||
@@ -28,15 +28,15 @@ export function createHousekeepingScheduler(
|
||||
try {
|
||||
if (isQuitting() || !hasWorkerReady()) return;
|
||||
|
||||
const activeCompanies = db.company.listActiveCompanyIds();
|
||||
const activeCompanies = db.core.company.listActiveCompanyIds();
|
||||
|
||||
for (const company of activeCompanies) {
|
||||
try {
|
||||
const currentMonth = new Date().toISOString().slice(0, 7);
|
||||
const lastResetMonth = db.settings.getLastBudgetResetMonth(company.id) ?? "";
|
||||
const lastResetMonth = db.core.settings.getLastBudgetResetMonth(company.id) ?? "";
|
||||
if (currentMonth !== lastResetMonth) {
|
||||
db.agents.resetMonthlyBudgets(company.id);
|
||||
db.settings.setLastBudgetResetMonth(company.id, currentMonth);
|
||||
db.core.agents.resetMonthlyBudgets(company.id);
|
||||
db.core.settings.setLastBudgetResetMonth(company.id, currentMonth);
|
||||
logger.info(`[housekeeping] Reset monthly budgets for company ${company.name} (${currentMonth})`);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function createHousekeepingScheduler(
|
||||
dispatchAgentMessage,
|
||||
resolveApprovalDecision,
|
||||
notify,
|
||||
getCompanyExecutionMode: (cId: string) => db.company.getCompanyExecutionMode(cId),
|
||||
getCompanyExecutionMode: (cId: string) => db.core.company.getCompanyExecutionMode(cId),
|
||||
handleTaskStatusChange: (taskId: string, cId: string, previousStatus: string, newStatus: string) => {
|
||||
deps.dispatchAutomation("task_status_changed", { companyId: cId, taskId, previousStatus, newStatus });
|
||||
publishDomainChanged();
|
||||
@@ -92,7 +92,7 @@ export function createHousekeepingScheduler(
|
||||
}
|
||||
|
||||
try {
|
||||
const staleWakeups = db.wakeup.purgeExpiredWakeupRequests();
|
||||
const staleWakeups = db.automation.wakeup.purgeExpiredWakeupRequests();
|
||||
if (staleWakeups > 0) {
|
||||
logger.info(`[housekeeping] Purged ${staleWakeups} expired wakeup request(s)`);
|
||||
}
|
||||
@@ -112,7 +112,7 @@ export function createHousekeepingScheduler(
|
||||
// so an active rotation window cannot strip the ledger out
|
||||
// from under a live run.
|
||||
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
||||
const purgedProjections = db.inboxProjections.purgeOrphanProjections(cutoff);
|
||||
const purgedProjections = db.projection.inbox.purgeOrphanProjections(cutoff);
|
||||
if (purgedProjections > 0) {
|
||||
logger.info(`[housekeeping] Purged ${purgedProjections} orphan inbox projection ledger row(s)`);
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ export function createIdleAutomationScheduler(deps: OrchestrationDependencies) {
|
||||
try {
|
||||
if (isQuitting() || !hasWorkerReady()) return;
|
||||
|
||||
const activeCompanies = db.company.listActiveCompanyIds();
|
||||
const activeCompanies = db.core.company.listActiveCompanyIds();
|
||||
const now = Date.now();
|
||||
|
||||
for (const company of activeCompanies) {
|
||||
try {
|
||||
const companyAgents = db.agents.listActiveAgentsForCompany(company.id);
|
||||
const companyAgents = db.core.agents.listActiveAgentsForCompany(company.id);
|
||||
for (const agent of companyAgents) {
|
||||
if (!db.agents.hasAgentAssignedTasks(agent.id, company.id)) {
|
||||
if (!db.core.agents.hasAgentAssignedTasks(agent.id, company.id)) {
|
||||
const lastActive = agent.lastHeartbeatAt ? new Date(agent.lastHeartbeatAt).getTime() : 0;
|
||||
const idleThreshold = 20 * 60 * 1000;
|
||||
if (now - lastActive > idleThreshold) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export function createLockRecoveryScheduler(deps: OrchestrationDependencies) {
|
||||
try {
|
||||
const recovered = db.recoverStaleLocks();
|
||||
for (const task of recovered) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: task.companyId,
|
||||
actor: "system",
|
||||
action: "execution.lock_recovered",
|
||||
@@ -36,7 +36,7 @@ export function createLockRecoveryScheduler(deps: OrchestrationDependencies) {
|
||||
}
|
||||
|
||||
try {
|
||||
const staleReleased = db.workspacesMod.releaseStaleWorkspaceLocks();
|
||||
const staleReleased = db.core.workspaces.releaseStaleWorkspaceLocks();
|
||||
if (staleReleased > 0) {
|
||||
logger.info(`[lock-recovery] Released ${staleReleased} stale workspace lock(s)`);
|
||||
publishDomainChanged();
|
||||
|
||||
@@ -37,27 +37,27 @@ export function createTaskRunner(
|
||||
logger.warn(`[run] Worker not ready — cannot queue run for task ${taskId} (trigger: ${trigger})`);
|
||||
return fail("WORKER_UNAVAILABLE", "Orchestration worker is not ready.");
|
||||
}
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const companyStatus = db.company.getCompanyStatus(task.companyId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
const companyStatus = db.core.company.getCompanyStatus(task.companyId);
|
||||
if (companyStatus !== "active") {
|
||||
return fail("COMPANY_NOT_ACTIVE", "Runs can only start while the company is active.");
|
||||
}
|
||||
if (!task.assigneeAgentId) return fail("TASK_UNASSIGNED", "Assign an agent before starting a run.");
|
||||
const agent = db.entityCrud.getAgent(task.assigneeAgentId);
|
||||
const agent = db.core.entity.getAgent(task.assigneeAgentId);
|
||||
if (agent.companyId !== task.companyId) {
|
||||
return fail("AGENT_COMPANY_MISMATCH", "The assigned agent does not belong to the task company.");
|
||||
}
|
||||
const pick = pickModelForAgent(agent, {
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (id) => db.settings.getDisabledModels(agent.companyId, id),
|
||||
listModelCapabilities: () => db.modelCapabilities.listModelCapabilities(),
|
||||
readConnectors: () => db.connectorsCrud.readConnectors(),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (id) => db.core.settings.getDisabledModels(agent.companyId, id),
|
||||
listModelCapabilities: () => db.core.modelCapabilities.listModelCapabilities(),
|
||||
readConnectors: () => db.core.connectors.readConnectors(),
|
||||
});
|
||||
if (!pick.ok) {
|
||||
return fail("MODEL_PICK_FAILED", pick.message);
|
||||
}
|
||||
const assignment = pick.assignment;
|
||||
const connector = db.connectorsCrud.getConnector(assignment.connectorId);
|
||||
const connector = db.core.connectors.getConnector(assignment.connectorId);
|
||||
const effectiveAgentForEligibility: AgentRecord = { ...agent, model: assignment.modelId };
|
||||
const eligibilityFailure = getRunEligibilityFailure(effectiveAgentForEligibility, connector, task.projectId ?? null);
|
||||
if (eligibilityFailure === "AGENT_NOT_RUNNABLE") {
|
||||
@@ -75,10 +75,10 @@ export function createTaskRunner(
|
||||
if (hasActiveRun({ agentId: agent.id, companyId: task.companyId })) {
|
||||
return fail("AGENT_ALREADY_RUNNING", "The assigned agent already has an active run.");
|
||||
}
|
||||
if (db.agents.isAgentPendingApproval(agent.id)) {
|
||||
if (db.core.agents.isAgentPendingApproval(agent.id)) {
|
||||
return fail("AGENT_PENDING_APPROVAL", "This agent is pending hire approval and cannot run tasks.");
|
||||
}
|
||||
const blockingApproval = db.approvalsMod.getBlockingApproval(task.companyId, agent.id, task.id);
|
||||
const blockingApproval = db.core.approvals.getBlockingApproval(task.companyId, agent.id, task.id);
|
||||
if (blockingApproval) {
|
||||
return fail(
|
||||
"APPROVAL_GATE_BLOCKED",
|
||||
@@ -100,7 +100,7 @@ export function createTaskRunner(
|
||||
return fail("WORKSPACE_COMPANY_MISMATCH", "The selected workspace does not belong to the task company.");
|
||||
}
|
||||
|
||||
const run = db.runs.createTaskRun({
|
||||
const run = db.core.runs.createTaskRun({
|
||||
companyId: task.companyId,
|
||||
taskId: task.id,
|
||||
agentId: agent.id,
|
||||
@@ -111,7 +111,7 @@ export function createTaskRunner(
|
||||
return fail("CHECKOUT_RACE_LOST", "Another run claimed this task. Refresh and try again.");
|
||||
}
|
||||
|
||||
const companyBasic = db.company.getCompanyBasic(task.companyId);
|
||||
const companyBasic = db.core.company.getCompanyBasic(task.companyId);
|
||||
dispatchRunToWorker({
|
||||
runId: run.id,
|
||||
companyId: task.companyId,
|
||||
@@ -123,13 +123,13 @@ export function createTaskRunner(
|
||||
observationPath: workspaceResolution.observationPath,
|
||||
companyName: companyBasic?.name ?? "Unknown",
|
||||
companyDescription: companyBasic?.description ?? "",
|
||||
snapshot: db.snap.listSnapshot(),
|
||||
snapshot: db.projection.snapshot.listSnapshot(),
|
||||
trigger,
|
||||
resolvedModel: assignment.modelId,
|
||||
});
|
||||
const postDispatchRun = db.runs.getRun(run.id);
|
||||
const postDispatchRun = db.core.runs.getRun(run.id);
|
||||
if (postDispatchRun.status === "queued" || postDispatchRun.status === "running") {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: task.companyId,
|
||||
actor,
|
||||
action: "run.queued",
|
||||
@@ -147,7 +147,7 @@ export function createTaskRunner(
|
||||
try {
|
||||
const recovered = db.recoverStaleLocks();
|
||||
for (const task of recovered) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: task.companyId,
|
||||
actor: "system",
|
||||
action: "execution.lock_recovered",
|
||||
@@ -167,10 +167,10 @@ export function createTaskRunner(
|
||||
if (isQuitting() || !hasWorkerReady()) {
|
||||
return;
|
||||
}
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const companiesById = new Map(snapshot.companies.map((company) => [company.id, company]));
|
||||
|
||||
for (const run of db.runs.listQueuedRuns()) {
|
||||
for (const run of db.core.runs.listQueuedRuns()) {
|
||||
const company = companiesById.get(run.companyId);
|
||||
if (!company || company.status !== "active") {
|
||||
continue;
|
||||
@@ -187,13 +187,13 @@ export function createTaskRunner(
|
||||
}
|
||||
|
||||
try {
|
||||
const task = run.taskId ? db.entityCrud.getTask(run.taskId) : null;
|
||||
const agent = db.entityCrud.getAgent(run.agentId);
|
||||
const connector = db.connectorsCrud.getConnector(run.connectorId);
|
||||
const task = run.taskId ? db.core.entity.getTask(run.taskId) : null;
|
||||
const agent = db.core.entity.getAgent(run.agentId);
|
||||
const connector = db.core.connectors.getConnector(run.connectorId);
|
||||
const eligibilityFailure = getRunEligibilityFailure(agent, connector, task?.projectId ?? null);
|
||||
if (eligibilityFailure) {
|
||||
if (eligibilityFailure === "CONNECTOR_MODEL_NOT_ALLOWED") {
|
||||
db.runs.finishRun({
|
||||
db.core.runs.finishRun({
|
||||
runId: run.id,
|
||||
status: "failed",
|
||||
summary: "Queued run rejected during recovery because the agent model is disabled or invalid for its connector.",
|
||||
@@ -203,7 +203,7 @@ export function createTaskRunner(
|
||||
model: null,
|
||||
unattributedCost: true,
|
||||
});
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: run.companyId,
|
||||
actor: "system",
|
||||
action: "run.recovery_blocked",
|
||||
|
||||
@@ -20,13 +20,13 @@ import { logAppError } from "../errors/index.js";
|
||||
export interface RunDispatchParams {
|
||||
runId: string;
|
||||
companyId: string;
|
||||
agent: ReturnType<AppDatabase["entityCrud"]["getAgent"]>;
|
||||
agent: ReturnType<AppDatabase["core"]["entity"]["getAgent"]>;
|
||||
// `task` is null for heartbeat runs (agent wake-ups with no assigned task).
|
||||
// Downstream code (prompt builder, worker payload, session pointer)
|
||||
// branches on this — when null, skip task-scoped state.
|
||||
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
|
||||
connector: ReturnType<AppDatabase["connectorsCrud"]["getConnector"]>;
|
||||
workspace: ReturnType<AppDatabase["workspacesMod"]["getWorkspace"]>;
|
||||
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null;
|
||||
connector: ReturnType<AppDatabase["core"]["connectors"]["getConnector"]>;
|
||||
workspace: ReturnType<AppDatabase["core"]["workspaces"]["getWorkspace"]>;
|
||||
workspaceMode: WorkspaceLockMode;
|
||||
/**
|
||||
* Filesystem root the artifact projector observes for files this run
|
||||
@@ -40,7 +40,7 @@ export interface RunDispatchParams {
|
||||
observationPath?: string | null;
|
||||
companyName: string;
|
||||
companyDescription: string;
|
||||
snapshot: ReturnType<AppDatabase["snap"]["listSnapshot"]>;
|
||||
snapshot: ReturnType<AppDatabase["projection"]["snapshot"]["listSnapshot"]>;
|
||||
trigger: string;
|
||||
/**
|
||||
* Model id resolved by `pickModelForAgent` at dispatch time. When the
|
||||
@@ -75,7 +75,7 @@ function assembleApiEnv(
|
||||
trigger: string,
|
||||
deps: Pick<TurnContextDeps, "getApiPort" | "createRunToken">,
|
||||
) {
|
||||
const env = db.entityCrud.resolveSecretEnv(connector.envBindingText, companyId);
|
||||
const env = db.core.entity.resolveSecretEnv(connector.envBindingText, companyId);
|
||||
const apiPort = deps.getApiPort();
|
||||
const apiUrl = apiPort ? `http://127.0.0.1:${apiPort}` : "";
|
||||
const apiKey = apiUrl ? deps.createRunToken(agentId, companyId, runId) : "";
|
||||
@@ -93,8 +93,8 @@ function assembleApiEnv(
|
||||
}
|
||||
|
||||
function assembleRecentComments(
|
||||
assignedTasks: ReturnType<AppDatabase["tasks"]["listAgentTasks"]>,
|
||||
reviewTasks: ReturnType<AppDatabase["tasks"]["listAgentTasks"]>,
|
||||
assignedTasks: ReturnType<AppDatabase["core"]["tasks"]["listAgentTasks"]>,
|
||||
reviewTasks: ReturnType<AppDatabase["core"]["tasks"]["listAgentTasks"]>,
|
||||
snapshot: RunDispatchParams["snapshot"],
|
||||
) {
|
||||
const relevantTaskIds = new Set([
|
||||
@@ -160,8 +160,8 @@ function resolveEnabledConnectorModels(
|
||||
): string[] {
|
||||
return getConnectorModelPolicy(
|
||||
{
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId),
|
||||
},
|
||||
companyId,
|
||||
connector.id as ConnectorId,
|
||||
@@ -179,7 +179,7 @@ function resolveSession(
|
||||
// Per-agent session: one ongoing transcript per agent; switching tasks
|
||||
// (or running taskless heartbeats) becomes a "now work on X" user turn
|
||||
// on the ongoing transcript rather than starting a fresh session.
|
||||
const agentSessionId = db.tasks.ensureAgentSession({
|
||||
const agentSessionId = db.core.tasks.ensureAgentSession({
|
||||
agentId: agent.id,
|
||||
companyId,
|
||||
adapterType: connectorId,
|
||||
@@ -281,7 +281,7 @@ function buildConnectorCatalog(
|
||||
) {
|
||||
return connectors
|
||||
.filter((c) => c.status === "ready")
|
||||
.filter((c) => db.settings.hasModelPoolSettings(companyId, c.id))
|
||||
.filter((c) => db.core.settings.hasModelPoolSettings(companyId, c.id))
|
||||
.map((c) => ({
|
||||
id: c.id,
|
||||
label: c.label,
|
||||
@@ -309,7 +309,7 @@ function readHeartbeatSkip(
|
||||
since: string;
|
||||
lastTickAt: string;
|
||||
} | null {
|
||||
const state = db.agents.getRuntimeState(agentId);
|
||||
const state = db.core.agents.getRuntimeState(agentId);
|
||||
const raw = state["heartbeatSkip"];
|
||||
if (!raw || typeof raw !== "object") return null;
|
||||
const record = raw as Record<string, unknown>;
|
||||
@@ -339,8 +339,8 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
const { env, apiUrl, apiKey } = assembleApiEnv(db, connector, companyId, agent.id, runId, trigger, deps);
|
||||
env.AGENT_COMPANY_TASK_ID = task?.id ?? "";
|
||||
|
||||
const assignedTasks = db.tasks.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]);
|
||||
const reviewTasks = db.tasks.listReviewTasksForManager(agent.id, companyId);
|
||||
const assignedTasks = db.core.tasks.listAgentTasks(agent.id, companyId, ["todo", "in_progress", "blocked"]);
|
||||
const reviewTasks = db.core.tasks.listReviewTasksForManager(agent.id, companyId);
|
||||
const recentComments = assembleRecentComments(assignedTasks, reviewTasks, snapshot);
|
||||
const initialHandoff = resolveSessionHandoff(agent.id, deps);
|
||||
const { taskSessionId, sessionHandoff } = resolveSession(db, agent, companyId, connector.id, initialHandoff, deps);
|
||||
@@ -355,11 +355,11 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
task,
|
||||
companyName,
|
||||
locale: snapshot.locale,
|
||||
executionMode: db.company.getCompanyExecutionMode(companyId) as ExecutionMode,
|
||||
executionMode: db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode,
|
||||
goals: snapshot.goals.filter((goal) => goal.companyId === companyId),
|
||||
projects: snapshot.projects.filter((project) => project.companyId === companyId),
|
||||
directReports: db.agents.getDirectReports(agent.id, companyId),
|
||||
chainOfCommand: db.agents.getChainOfCommand(agent.id),
|
||||
directReports: db.core.agents.getDirectReports(agent.id, companyId),
|
||||
chainOfCommand: db.core.agents.getChainOfCommand(agent.id),
|
||||
assignedTasks,
|
||||
allAgents: snapshot.agents.filter((entry) => entry.companyId === companyId),
|
||||
pendingApprovals: filterPendingApprovals(snapshot.approvals, companyId, agent.id),
|
||||
@@ -368,7 +368,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
allTasks: snapshot.tasks.filter((entry) => entry.companyId === companyId),
|
||||
recentComments: normalizeCommentsForPrompt(recentComments),
|
||||
companyDescription,
|
||||
socialAccounts: db.social.listSocialAccounts(companyId).map((account) => ({
|
||||
socialAccounts: db.core.social.listSocialAccounts(companyId).map((account) => ({
|
||||
id: account.id,
|
||||
platform: account.platform,
|
||||
accountName: account.accountName,
|
||||
@@ -382,7 +382,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
allConnectors: buildConnectorCatalog(db, companyId, snapshot.connectors),
|
||||
sessionHandoff,
|
||||
skills,
|
||||
companySkillCatalog: db.skills.listCompanySkills(companyId).map((skill) => ({
|
||||
companySkillCatalog: db.core.skills.listCompanySkills(companyId).map((skill) => ({
|
||||
name: skill.name,
|
||||
slug: skill.slug,
|
||||
description: skill.description,
|
||||
@@ -398,8 +398,8 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
const modelForRun = params.resolvedModel !== undefined ? params.resolvedModel : agent.model;
|
||||
const modelSelection = validateConnectorModelSelection(
|
||||
{
|
||||
getConnector: (id) => db.connectorsCrud.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.settings.getDisabledModels(cid, connId),
|
||||
getConnector: (id) => db.core.connectors.getConnector(id),
|
||||
getDisabledModels: (cid, connId) => db.core.settings.getDisabledModels(cid, connId),
|
||||
},
|
||||
companyId,
|
||||
connector.id,
|
||||
@@ -438,7 +438,7 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
|
||||
systemPrompt,
|
||||
trigger,
|
||||
taskSessionId: taskSessionId ?? runId,
|
||||
executionMode: db.company.getCompanyExecutionMode(companyId) as "ai_ceo" | "ai_cofounder",
|
||||
executionMode: db.core.company.getCompanyExecutionMode(companyId) as "ai_ceo" | "ai_cofounder",
|
||||
initialUserTurn,
|
||||
internalTools,
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ function shouldProduceKnowledge(
|
||||
);
|
||||
}
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
return Boolean(agent.department && KNOWLEDGE_PRODUCING_DEPARTMENTS.has(agent.department));
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
@@ -97,7 +97,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous
|
||||
try {
|
||||
let topic = `Run ${runId.slice(0, 8)}`;
|
||||
try {
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
topic = task.title;
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
@@ -105,7 +105,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous
|
||||
);
|
||||
}
|
||||
|
||||
db.knowledgeMod.saveKnowledgeEntry({
|
||||
db.core.knowledge.saveKnowledgeEntry({
|
||||
companyId,
|
||||
category: "lesson_learned",
|
||||
topic,
|
||||
@@ -121,7 +121,7 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous
|
||||
// 3. On failure, record an activity log entry
|
||||
if (status === "failed") {
|
||||
try {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId,
|
||||
actor: agentId,
|
||||
action: "run.failed",
|
||||
|
||||
@@ -108,7 +108,7 @@ export function createServiceGraph(deps: ServiceGraphDependencies) {
|
||||
}
|
||||
|
||||
function init() {
|
||||
nativeTheme.themeSource = db.snap.listSnapshot().theme;
|
||||
nativeTheme.themeSource = db.projection.snapshot.listSnapshot().theme;
|
||||
|
||||
leaf = createLeafServices({ db, logger, notify, publishDomainChanged });
|
||||
updaterService = createUpdaterService({ db, logger, notify });
|
||||
|
||||
@@ -60,13 +60,13 @@ export function createDesktopShell({
|
||||
properties: ["openDirectory", "createDirectory"],
|
||||
}));
|
||||
if (target.canceled || target.filePaths.length === 0) return;
|
||||
await db.backupMod.exportBackup(`${target.filePaths[0]!}/agentcompany-backup-${Date.now()}`);
|
||||
await db.core.backup.exportBackup(`${target.filePaths[0]!}/agentcompany-backup-${Date.now()}`);
|
||||
},
|
||||
onShortcut: (action) => emitEvent({ type: "shortcut", action }),
|
||||
notify,
|
||||
getActiveRunCount: () => {
|
||||
try {
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
return snapshot.runs.filter((r) => r.status === "running" || r.status === "queued").length;
|
||||
} catch (error) {
|
||||
logger.warn("[service-graph] getActiveRunCount failed:", error);
|
||||
|
||||
@@ -62,7 +62,7 @@ export function createLifecycle(deps: LifecycleDeps) {
|
||||
|
||||
function listActiveRunIds(): string[] {
|
||||
try {
|
||||
return db.snap.listSnapshot().runs
|
||||
return db.projection.snapshot.listSnapshot().runs
|
||||
.filter((r) => r.status === "running" || r.status === "queued")
|
||||
.map((r) => r.id);
|
||||
} catch (err) {
|
||||
|
||||
@@ -67,9 +67,9 @@ export function buildOrchestrationService({
|
||||
checkpointEngine,
|
||||
getSessionHandoff: (agentId) => sessionService.getHandoffForAgent(agentId),
|
||||
evaluatePreRunSessionRotation: (agentId, _connectorId) => {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (!agent.sessionId) return { rotated: false, handoff: null, sessionId: null };
|
||||
const session = db.tasks.getSessionById(agent.sessionId);
|
||||
const session = db.core.tasks.getSessionById(agent.sessionId);
|
||||
if (!session) return { rotated: false, handoff: null, sessionId: agent.sessionId };
|
||||
const result = sessionService.evaluateRotation(session);
|
||||
if (result.shouldRotate) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function createSessionManagementService({ db, logger }: SessionManagement
|
||||
|
||||
// D-08: Accumulate token usage on task_sessions row (called for ALL adapters per D-05)
|
||||
function accumulateTokenUsage(sessionId: string, inputTokens: number, outputTokens: number, runId: string): void {
|
||||
db.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId);
|
||||
db.core.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId);
|
||||
}
|
||||
|
||||
// Any-of thresholds — first trigger wins. Pi-mono delegates context
|
||||
@@ -133,15 +133,15 @@ export function createSessionManagementService({ db, logger }: SessionManagement
|
||||
|
||||
logger.info(`[session] Rotating session ${session.id} for agent ${session.agentId}: ${reason}`);
|
||||
|
||||
db.tasks.archiveSession(session.id, reason, handoff);
|
||||
db.core.tasks.archiveSession(session.id, reason, handoff);
|
||||
|
||||
const newSessionId = db.tasks.createNewSession({
|
||||
const newSessionId = db.core.tasks.createNewSession({
|
||||
companyId: session.companyId,
|
||||
agentId: session.agentId,
|
||||
adapterType: session.adapterType,
|
||||
});
|
||||
|
||||
db.tasks.setAgentSessionId(session.agentId, newSessionId);
|
||||
db.core.tasks.setAgentSessionId(session.agentId, newSessionId);
|
||||
|
||||
logger.info(`[session] New session ${newSessionId} created for agent ${session.agentId}`);
|
||||
return newSessionId;
|
||||
|
||||
@@ -72,16 +72,16 @@ export function autoAssignSkillsForHire(
|
||||
return { assignedSkillIds: [] };
|
||||
}
|
||||
|
||||
const alreadyAssigned = new Set(db.skills.listAgentSkillAssignments(input.agentId));
|
||||
const alreadyAssigned = new Set(db.core.skills.listAgentSkillAssignments(input.agentId));
|
||||
const wanted = new Set(categories);
|
||||
const catalogue = db.skills.listCompanySkills(input.companyId);
|
||||
const catalogue = db.core.skills.listCompanySkills(input.companyId);
|
||||
|
||||
const assigned: string[] = [];
|
||||
for (const skill of catalogue) {
|
||||
if (alreadyAssigned.has(skill.id)) continue;
|
||||
const category = readBuiltinCategory(skill.metadata);
|
||||
if (!category || !wanted.has(category)) continue;
|
||||
db.skills.assignSkillToAgent(input.agentId, skill.id);
|
||||
db.core.skills.assignSkillToAgent(input.agentId, skill.id);
|
||||
assigned.push(skill.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl
|
||||
}
|
||||
|
||||
function listForCompany(companyId: string): SkillRecord[] {
|
||||
return db.skills.listCompanySkills(companyId);
|
||||
return db.core.skills.listCompanySkills(companyId);
|
||||
}
|
||||
|
||||
function getDetail(companyId: string, skillId: string): DesktopResult<SkillRecord> {
|
||||
const skill = db.skills.getCompanySkill(companyId, skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, skillId);
|
||||
if (!skill) return fail("not_found", `Skill ${skillId} not found`);
|
||||
return ok(skill);
|
||||
}
|
||||
@@ -36,12 +36,12 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl
|
||||
companyId: string,
|
||||
skillId: string,
|
||||
): Promise<DesktopResult<void>> {
|
||||
const skill = db.skills.getCompanySkill(companyId, skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, skillId);
|
||||
if (!skill) return fail("not_found", `Skill ${skillId} not found`);
|
||||
|
||||
try {
|
||||
await cleanupSkillFiles(companyId, skill.slug);
|
||||
db.skills.deleteCompanySkill(companyId, skillId);
|
||||
db.core.skills.deleteCompanySkill(companyId, skillId);
|
||||
logger.info(`Skill deleted: ${skill.key}`);
|
||||
return ok(undefined);
|
||||
} catch (err) {
|
||||
@@ -56,9 +56,9 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl
|
||||
agentId: string,
|
||||
skillId: string,
|
||||
): DesktopResult<void> {
|
||||
const skill = db.skills.getCompanySkill(companyId, skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, skillId);
|
||||
if (!skill) return fail("not_found", `Skill ${skillId} not found for company`);
|
||||
db.skills.assignSkillToAgent(agentId, skillId);
|
||||
db.core.skills.assignSkillToAgent(agentId, skillId);
|
||||
return ok(undefined);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl
|
||||
agentId: string,
|
||||
skillId: string,
|
||||
): DesktopResult<void> {
|
||||
db.skills.removeSkillFromAgent(agentId, skillId);
|
||||
db.core.skills.removeSkillFromAgent(agentId, skillId);
|
||||
return ok(undefined);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function createSkillCrud({ db, logger, cleanupSkillFiles, parseGitHubUrl
|
||||
companyId: string,
|
||||
skillId: string,
|
||||
): Promise<DesktopResult<SkillUpdateStatus>> {
|
||||
const skill = db.skills.getCompanySkill(companyId, skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, skillId);
|
||||
if (!skill) return fail("not_found", `Skill ${skillId} not found`);
|
||||
|
||||
if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") {
|
||||
|
||||
@@ -155,7 +155,7 @@ export function createGitHubImporter({ db, logger, skillFilesDir }: GitHubImport
|
||||
|
||||
await materializeSkillFiles(companyId, rawSlug, ghSkill.markdown, inventory);
|
||||
|
||||
const { isUpdate } = db.skills.upsertCompanySkill({
|
||||
const { isUpdate } = db.core.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key,
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function importFromLocal(
|
||||
const skillId = randomUUID();
|
||||
await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory);
|
||||
|
||||
const { isUpdate } = db.skills.upsertCompanySkill({
|
||||
const { isUpdate } = db.core.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key,
|
||||
@@ -121,7 +121,7 @@ export async function importFromLocal(
|
||||
const skillId = randomUUID();
|
||||
await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory);
|
||||
|
||||
const { isUpdate } = db.skills.upsertCompanySkill({
|
||||
const { isUpdate } = db.core.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key,
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function importFromUrl(
|
||||
const skillId = randomUUID();
|
||||
await materializeSkillFiles(skillFilesDir(companyId, rawSlug), markdown, inventory);
|
||||
|
||||
const { isUpdate } = db.skills.upsertCompanySkill({
|
||||
const { isUpdate } = db.core.skills.upsertCompanySkill({
|
||||
id: skillId,
|
||||
companyId,
|
||||
key,
|
||||
|
||||
@@ -35,9 +35,9 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo
|
||||
agentId: string,
|
||||
_connectorId: string,
|
||||
): ProjectedSkill[] {
|
||||
const assignedIds = new Set(db.skills.listAgentSkillAssignments(agentId));
|
||||
const assignedIds = new Set(db.core.skills.listAgentSkillAssignments(agentId));
|
||||
if (assignedIds.size === 0) return [];
|
||||
const companySkills = db.skills.listCompanySkills(companyId);
|
||||
const companySkills = db.core.skills.listCompanySkills(companyId);
|
||||
const skillPaths = companySkills
|
||||
.filter((s) => assignedIds.has(s.id))
|
||||
.map((s) => skillFilesDir(companyId, s.slug));
|
||||
@@ -82,7 +82,7 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo
|
||||
}
|
||||
}
|
||||
|
||||
const workspacePaths = db.workspacesMod.listWorkspaceLocalPaths(companyId);
|
||||
const workspacePaths = db.core.workspaces.listWorkspaceLocalPaths(companyId);
|
||||
for (const wsPath of workspacePaths) {
|
||||
const agentcompanySkillsDir = join(wsPath, ".agentcompany", "skills");
|
||||
if (!existsSync(agentcompanySkillsDir)) continue;
|
||||
@@ -159,12 +159,12 @@ export function createSkillRuntime({ db, logger, skillsRoot, skillFilesDir, impo
|
||||
const results = await importFromLocal(companyId, categoryDir);
|
||||
for (const result of results) {
|
||||
try {
|
||||
const skill = db.skills.getCompanySkill(companyId, result.skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, result.skillId);
|
||||
if (skill) {
|
||||
const existingMeta = skill.metadata ? JSON.parse(typeof skill.metadata === "string" ? skill.metadata : JSON.stringify(skill.metadata)) : {};
|
||||
existingMeta.builtinCategory = category;
|
||||
existingMeta.sourceKind = "builtin";
|
||||
db.skills.updateCompanySkillMetadata(companyId, result.skillId, JSON.stringify(existingMeta));
|
||||
db.core.skills.updateCompanySkillMetadata(companyId, result.skillId, JSON.stringify(existingMeta));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
|
||||
@@ -25,12 +25,14 @@ interface CapabilityMatchResult {
|
||||
}
|
||||
|
||||
interface CapabilityMatchDb {
|
||||
entityCrud: {
|
||||
getAgent(id: string): { name: string; capabilities: string };
|
||||
};
|
||||
skills: {
|
||||
listAgentSkillAssignments(id: string): string[];
|
||||
getCompanySkill(companyId: string, skillId: string): { name: string; description: string | null; slug: string } | null;
|
||||
core: {
|
||||
entity: {
|
||||
getAgent(id: string): { name: string; capabilities: string };
|
||||
};
|
||||
skills: {
|
||||
listAgentSkillAssignments(id: string): string[];
|
||||
getCompanySkill(companyId: string, skillId: string): { name: string; description: string | null; slug: string } | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -132,14 +134,14 @@ export function checkCapabilityMatch(
|
||||
return { matched: true, warning: "Task description has no distinctive keywords for capability matching." };
|
||||
}
|
||||
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
const capWords = tokenize(agent.capabilities);
|
||||
const capabilityOverlap = taskWords.filter((w) => capWords.includes(w)).length;
|
||||
|
||||
let skillOverlap = 0;
|
||||
const skillIds = db.skills.listAgentSkillAssignments(agentId);
|
||||
const skillIds = db.core.skills.listAgentSkillAssignments(agentId);
|
||||
for (const skillId of skillIds) {
|
||||
const skill = db.skills.getCompanySkill(companyId, skillId);
|
||||
const skill = db.core.skills.getCompanySkill(companyId, skillId);
|
||||
if (skill) {
|
||||
const skillWords = tokenize(`${skill.name} ${skill.description ?? ""} ${skill.slug}`);
|
||||
skillOverlap += taskWords.filter((w) => skillWords.includes(w)).length;
|
||||
@@ -201,7 +203,7 @@ export function sweepStaleReviews(
|
||||
const isCofounderMode = deps.getCompanyExecutionMode(companyId) === "ai_cofounder";
|
||||
|
||||
let escalatedCount = 0;
|
||||
const reviewTasks = deps.db.tasks.listReviewTasksForSweep(companyId);
|
||||
const reviewTasks = deps.db.core.tasks.listReviewTasksForSweep(companyId);
|
||||
|
||||
for (const task of reviewTasks) {
|
||||
const reviewAge = now - new Date(task.updatedAt).getTime();
|
||||
@@ -209,14 +211,14 @@ export function sweepStaleReviews(
|
||||
if (!task.assigneeAgentId) continue;
|
||||
|
||||
// The reviewer is the assignee's direct manager
|
||||
const reviewer = deps.db.agents.getManagerForAgent(task.assigneeAgentId);
|
||||
const reviewer = deps.db.core.agents.getManagerForAgent(task.assigneeAgentId);
|
||||
if (!reviewer) continue; // No manager = CEO-assigned task, skip escalation
|
||||
|
||||
// D-09: Escalate to the reviewer's manager (manager's manager)
|
||||
const chain = deps.db.agents.getChainOfCommand(reviewer.id);
|
||||
const chain = deps.db.core.agents.getChainOfCommand(reviewer.id);
|
||||
const escalationTarget = chain.length > 0 ? chain[0] : null;
|
||||
|
||||
const pendingApproval = deps.db.approvalsMod.findPendingApprovalForTask(companyId, task.id);
|
||||
const pendingApproval = deps.db.core.approvals.findPendingApprovalForTask(companyId, task.id);
|
||||
|
||||
// In ai_cofounder mode, ALL approvals require human decision — never auto-advance.
|
||||
if (isCofounderMode && pendingApproval) {
|
||||
@@ -285,7 +287,7 @@ export function sweepStaleReviews(
|
||||
|
||||
// D-10: Advance task status from in_review to done
|
||||
try {
|
||||
deps.db.tasks.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes.");
|
||||
deps.db.core.tasks.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes.");
|
||||
deps.handleTaskStatusChange(task.id, companyId, "in_review", "done");
|
||||
} catch (err) {
|
||||
log.warn(`[task-quality-gates] advanceTaskStatus failed for task ${task.id}:`, err instanceof Error ? err.message : String(err));
|
||||
|
||||
@@ -20,12 +20,12 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
*/
|
||||
onTaskCreated(taskId: string, companyId: string): void {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
if (task.companyId !== companyId) return;
|
||||
|
||||
const thread = ensureTaskThread(db, taskId, companyId, task.title);
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: task.assigneeAgentId ?? "system",
|
||||
actorType: task.assigneeAgentId ? "agent" : "system",
|
||||
@@ -36,11 +36,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
|
||||
// Auto-enroll assignee and human as participants
|
||||
if (task.assigneeAgentId) {
|
||||
db.threads.addParticipant({ threadId: thread.id, participantId: task.assigneeAgentId, participantType: "agent", role: "owner" });
|
||||
db.threads.subscribe({ threadId: thread.id, subscriberId: task.assigneeAgentId, subscriberType: "agent" });
|
||||
db.core.threads.addParticipant({ threadId: thread.id, participantId: task.assigneeAgentId, participantType: "agent", role: "owner" });
|
||||
db.core.threads.subscribe({ threadId: thread.id, subscriberId: task.assigneeAgentId, subscriberType: "agent" });
|
||||
}
|
||||
db.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" });
|
||||
db.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" });
|
||||
db.core.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" });
|
||||
db.core.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" });
|
||||
} catch (err) {
|
||||
logger.error(`[thread-integration] onTaskCreated failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
@@ -53,12 +53,12 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
*/
|
||||
onTaskStatusChanged(taskId: string, companyId: string, previousStatus: string, newStatus: string): void {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
if (task.companyId !== companyId) return;
|
||||
|
||||
const thread = ensureTaskThread(db, taskId, companyId, task.title);
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: task.assigneeAgentId ?? "system",
|
||||
actorType: task.assigneeAgentId ? "agent" : "system",
|
||||
@@ -69,10 +69,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
|
||||
// In co-founder mode, set wait state when task enters review
|
||||
if (newStatus === "in_review") {
|
||||
const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
if (mode === "ai_cofounder") {
|
||||
const reason = `@Board review needed: Task "${task.title}" is ready for your review.`;
|
||||
db.threads.setThreadWaitState({
|
||||
db.core.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_review",
|
||||
@@ -85,10 +85,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
|
||||
// Resolve wait states when task moves past review
|
||||
if (previousStatus === "in_review" && (newStatus === "done" || newStatus === "in_progress")) {
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
for (const ws of waitStates) {
|
||||
if (ws.threadId === thread.id && ws.waitingFor === "user_review") {
|
||||
db.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
db.core.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,13 +104,13 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
*/
|
||||
onApprovalCreated(approvalId: string, companyId: string, taskId: string | null, approvalType: string, summary: string): void {
|
||||
try {
|
||||
const approval = db.approvalsMod.getApprovalRecord(approvalId);
|
||||
const task = taskId ? db.entityCrud.getTask(taskId) : null;
|
||||
const approval = db.core.approvals.getApprovalRecord(approvalId);
|
||||
const task = taskId ? db.core.entity.getTask(taskId) : null;
|
||||
const thread = task
|
||||
? ensureTaskThread(db, task.id, companyId, task.title)
|
||||
: ensureApprovalThread(db, approvalId, companyId, summary || approval.payloadSummary);
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: "system",
|
||||
actorType: "system",
|
||||
@@ -120,17 +120,17 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
});
|
||||
|
||||
if (approval.requestedByAgentId) {
|
||||
db.threads.addParticipant({ threadId: thread.id, participantId: approval.requestedByAgentId, participantType: "agent", role: "owner" });
|
||||
db.threads.subscribe({ threadId: thread.id, subscriberId: approval.requestedByAgentId, subscriberType: "agent" });
|
||||
db.core.threads.addParticipant({ threadId: thread.id, participantId: approval.requestedByAgentId, participantType: "agent", role: "owner" });
|
||||
db.core.threads.subscribe({ threadId: thread.id, subscriberId: approval.requestedByAgentId, subscriberType: "agent" });
|
||||
}
|
||||
db.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" });
|
||||
db.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" });
|
||||
db.core.threads.addParticipant({ threadId: thread.id, participantId: "user", participantType: "user", role: "observer" });
|
||||
db.core.threads.subscribe({ threadId: thread.id, subscriberId: "user", subscriberType: "user" });
|
||||
|
||||
// In co-founder mode, set wait state for user approval
|
||||
const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
if (mode === "ai_cofounder") {
|
||||
const approvalReason = `@Board approval needed: ${approvalType}`;
|
||||
db.threads.setThreadWaitState({
|
||||
db.core.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_approval",
|
||||
@@ -152,13 +152,13 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
onApprovalResolved(approvalId: string, companyId: string, decision: string): void {
|
||||
try {
|
||||
// Resolve any wait states linked to this approval
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
for (const ws of waitStates) {
|
||||
if (ws.relatedApprovalId === approvalId) {
|
||||
db.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
db.core.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
|
||||
// Add decision event to the thread
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: ws.threadId,
|
||||
actorId: "user",
|
||||
actorType: "user",
|
||||
@@ -181,11 +181,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
try {
|
||||
if (!taskId) return;
|
||||
|
||||
db.entityCrud.getTask(taskId); // validates task exists and belongs to company
|
||||
db.core.entity.getTask(taskId); // validates task exists and belongs to company
|
||||
const thread = findTaskThread(db, taskId, companyId);
|
||||
if (!thread) return;
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: authorAgentId ?? "system",
|
||||
actorType: authorAgentId ? "agent" : "system",
|
||||
@@ -208,7 +208,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
const thread = findTaskThread(db, taskId, companyId);
|
||||
if (!thread) return;
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: agentId,
|
||||
actorType: "agent",
|
||||
@@ -235,7 +235,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
? "Execution completed successfully"
|
||||
: `Execution ${status}${errorMessage ? `: ${errorMessage}` : ""}`;
|
||||
|
||||
db.threads.addThreadEvent({
|
||||
db.core.threads.addThreadEvent({
|
||||
threadId: thread.id,
|
||||
actorId: agentId,
|
||||
actorType: "agent",
|
||||
@@ -247,11 +247,11 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
// In co-founder mode, successful runs create a user_reply wait state
|
||||
// so the heartbeat pauses until the human reviews the output
|
||||
if (status === "succeeded") {
|
||||
const mode = db.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
const mode = db.core.company.getCompanyExecutionMode(companyId) as ExecutionMode;
|
||||
if (mode === "ai_cofounder") {
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
const completionReason = `@Board review needed: Agent completed work on "${task.title}" — awaiting your review.`;
|
||||
db.threads.setThreadWaitState({
|
||||
db.core.threads.setThreadWaitState({
|
||||
threadId: thread.id,
|
||||
companyId,
|
||||
waitingFor: "user_reply",
|
||||
@@ -280,7 +280,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
try {
|
||||
// Most precise: resolve a single identified wait state
|
||||
if (waitStateId) {
|
||||
db.threads.resolveThreadWaitState(waitStateId, companyId);
|
||||
db.core.threads.resolveThreadWaitState(waitStateId, companyId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
const SCOPED_WAIT_TYPES = new Set(["user_reply", "user_review"]);
|
||||
const isScoped = Boolean(threadId || toAgentId);
|
||||
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
for (const ws of waitStates) {
|
||||
if (isScoped) {
|
||||
if (!SCOPED_WAIT_TYPES.has(ws.waitingFor)) continue;
|
||||
@@ -301,10 +301,10 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
if (threadId) {
|
||||
if (ws.threadId !== threadId) continue;
|
||||
} else if (toAgentId) {
|
||||
const thread = db.threads.getThread(ws.threadId, companyId);
|
||||
const thread = db.core.threads.getThread(ws.threadId, companyId);
|
||||
if (!thread || thread.relatedEntityType !== "task" || !thread.relatedEntityId) continue;
|
||||
try {
|
||||
const task = db.entityCrud.getTask(thread.relatedEntityId);
|
||||
const task = db.core.entity.getTask(thread.relatedEntityId);
|
||||
if (task.assigneeAgentId !== toAgentId) continue;
|
||||
} catch (err) {
|
||||
logger.warn(`[thread-integration] getTask failed for thread ${ws.threadId}:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -312,7 +312,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
}
|
||||
}
|
||||
|
||||
db.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
db.core.threads.resolveThreadWaitState(ws.id, companyId);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[thread-integration] onHumanMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
@@ -321,7 +321,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
|
||||
hasUnresolvedWaitStates(companyId: string): boolean {
|
||||
try {
|
||||
const waitStates = db.threads.listThreadWaitStates(companyId);
|
||||
const waitStates = db.core.threads.listThreadWaitStates(companyId);
|
||||
return waitStates.length > 0;
|
||||
} catch (err) {
|
||||
logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}:`, err instanceof Error ? err.message : String(err));
|
||||
@@ -337,7 +337,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
*/
|
||||
hasUnresolvedWaitStatesForAgent(companyId: string, agentId: string): boolean {
|
||||
try {
|
||||
return db.threads.hasUnresolvedWaitStateForAgent(companyId, agentId);
|
||||
return db.core.threads.hasUnresolvedWaitStateForAgent(companyId, agentId);
|
||||
} catch (err) {
|
||||
logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}):`, err instanceof Error ? err.message : String(err));
|
||||
return false;
|
||||
@@ -350,7 +350,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh
|
||||
*/
|
||||
countUnresolvedWaitStates(companyId: string): number {
|
||||
try {
|
||||
return db.threads.listThreadWaitStates(companyId).length;
|
||||
return db.core.threads.listThreadWaitStates(companyId).length;
|
||||
} catch (err) {
|
||||
logger.warn(`[thread-integration] countUnresolvedWaitStates failed for company ${companyId}:`, err instanceof Error ? err.message : String(err));
|
||||
return 0;
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface ThreadIntegrationDeps {
|
||||
}
|
||||
|
||||
export function findTaskThread(db: AppDatabase, taskId: string, companyId: string) {
|
||||
const existing = db.threads.listThreads({
|
||||
const existing = db.core.threads.listThreads({
|
||||
companyId,
|
||||
type: "task" as ThreadType,
|
||||
relatedEntityType: "task",
|
||||
@@ -26,7 +26,7 @@ export function ensureTaskThread(db: AppDatabase, taskId: string, companyId: str
|
||||
const existing = findTaskThread(db, taskId, companyId);
|
||||
if (existing) return existing;
|
||||
|
||||
return db.threads.createThread({
|
||||
return db.core.threads.createThread({
|
||||
companyId,
|
||||
title,
|
||||
type: "task" as ThreadType,
|
||||
@@ -36,7 +36,7 @@ export function ensureTaskThread(db: AppDatabase, taskId: string, companyId: str
|
||||
}
|
||||
|
||||
export function findAgentCommunicationThread(db: AppDatabase, agentId: string, companyId: string) {
|
||||
const existing = db.threads.listThreads({
|
||||
const existing = db.core.threads.listThreads({
|
||||
companyId,
|
||||
type: "general" as ThreadType,
|
||||
relatedEntityType: "agent",
|
||||
@@ -62,7 +62,7 @@ export function ensureAgentCommunicationThread(
|
||||
const existing = findAgentCommunicationThread(db, agentId, companyId);
|
||||
if (existing) return existing;
|
||||
|
||||
return db.threads.createThread({
|
||||
return db.core.threads.createThread({
|
||||
companyId,
|
||||
title: `${agentLabel} · communication`,
|
||||
type: "general" as ThreadType,
|
||||
@@ -72,7 +72,7 @@ export function ensureAgentCommunicationThread(
|
||||
}
|
||||
|
||||
export function findApprovalThread(db: AppDatabase, approvalId: string, companyId: string) {
|
||||
const existing = db.threads.listThreads({
|
||||
const existing = db.core.threads.listThreads({
|
||||
companyId,
|
||||
type: "approval" as ThreadType,
|
||||
relatedEntityType: "approval",
|
||||
@@ -86,7 +86,7 @@ export function ensureApprovalThread(db: AppDatabase, approvalId: string, compan
|
||||
const existing = findApprovalThread(db, approvalId, companyId);
|
||||
if (existing) return existing;
|
||||
|
||||
return db.threads.createThread({
|
||||
return db.core.threads.createThread({
|
||||
companyId,
|
||||
title,
|
||||
type: "approval" as ThreadType,
|
||||
@@ -104,7 +104,7 @@ function resolveApprovalAnnouncementChannel(
|
||||
): { channel: "department" | "project" | "company"; channelTargetId: string | null } | null {
|
||||
if (requestedByAgentId) {
|
||||
try {
|
||||
const requester = db.entityCrud.getAgent(requestedByAgentId);
|
||||
const requester = db.core.entity.getAgent(requestedByAgentId);
|
||||
if (requester.companyId === companyId && requester.department) {
|
||||
return { channel: "department", channelTargetId: requester.department };
|
||||
}
|
||||
@@ -115,7 +115,7 @@ function resolveApprovalAnnouncementChannel(
|
||||
|
||||
if (taskId) {
|
||||
try {
|
||||
const task = db.entityCrud.getTask(taskId);
|
||||
const task = db.core.entity.getTask(taskId);
|
||||
if (task.companyId === companyId && task.projectId) {
|
||||
return { channel: "project", channelTargetId: task.projectId };
|
||||
}
|
||||
@@ -150,7 +150,7 @@ export function announceApprovalToBoard(
|
||||
);
|
||||
if (!target) return;
|
||||
|
||||
const messageId = db.messages.sendAgentMessage({
|
||||
const messageId = db.core.messages.sendAgentMessage({
|
||||
companyId: approval.companyId,
|
||||
fromAgentId: approval.requestedByAgentId,
|
||||
channel: target.channel,
|
||||
@@ -160,7 +160,7 @@ export function announceApprovalToBoard(
|
||||
priority: "urgent",
|
||||
senderKind: "agent",
|
||||
});
|
||||
const message = db.messages.getAgentMessage(messageId, approval.companyId);
|
||||
const message = db.core.messages.getAgentMessage(messageId, approval.companyId);
|
||||
if (message) {
|
||||
deps.emitEvent({ type: "new-message", message });
|
||||
}
|
||||
@@ -176,7 +176,7 @@ export function notifyThreadSubscribers(
|
||||
notify?: (options: { title: string; body: string }) => void,
|
||||
): void {
|
||||
if (!notify) return;
|
||||
const subscriptions = db.threads.listSubscriptions(threadId);
|
||||
const subscriptions = db.core.threads.listSubscriptions(threadId);
|
||||
for (const sub of subscriptions) {
|
||||
if (sub.subscriberType !== "user") continue;
|
||||
if (sub.notifyOn === "none") continue;
|
||||
|
||||
@@ -107,7 +107,7 @@ export function createUpdaterService({
|
||||
}
|
||||
|
||||
async function checkForUpdates(options: { manual?: boolean } = {}) {
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const updateUrl = process.env.AGENTCOMPANY_UPDATE_URL?.trim();
|
||||
const skipReason = shouldSkipUpdateCheck({
|
||||
isPackaged: app.isPackaged,
|
||||
|
||||
@@ -181,10 +181,10 @@ export function createWorkerService({
|
||||
|
||||
// Mark all running/queued runs as interrupted so DB state is clean on restart
|
||||
try {
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
for (const run of snapshot.runs) {
|
||||
if (run.status === "running" || run.status === "queued") {
|
||||
db.runs.finishRun({
|
||||
db.core.runs.finishRun({
|
||||
runId: run.id,
|
||||
status: "interrupted",
|
||||
summary: "Application shut down.",
|
||||
@@ -195,10 +195,10 @@ export function createWorkerService({
|
||||
unattributedCost: true,
|
||||
});
|
||||
if (run.workspaceId) {
|
||||
db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
}
|
||||
if (run.taskId) {
|
||||
db.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
db.core.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export function handleWorkerExit(
|
||||
|
||||
logger.warn(`[worker] Worker exited with code ${code}, recovering...`);
|
||||
|
||||
const snapshot = db.snap.listSnapshot();
|
||||
const snapshot = db.projection.snapshot.listSnapshot();
|
||||
const affectedAgentIds = new Set<string>();
|
||||
const affectedCompanyIds = new Map<string, string>();
|
||||
const requeueCandidates: Array<{
|
||||
@@ -36,7 +36,7 @@ export function handleWorkerExit(
|
||||
|
||||
for (const run of snapshot.runs) {
|
||||
if (run.status === "running") {
|
||||
db.runs.finishRun({
|
||||
db.core.runs.finishRun({
|
||||
runId: run.id,
|
||||
status: "interrupted",
|
||||
summary: "Worker process crashed.",
|
||||
@@ -47,16 +47,16 @@ export function handleWorkerExit(
|
||||
unattributedCost: true,
|
||||
});
|
||||
if (run.workspaceId) {
|
||||
db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
}
|
||||
// EXEC-02: Release execution lock on crash recovery
|
||||
if (run.taskId) {
|
||||
db.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
db.core.tasks.releaseTaskLock(run.taskId, run.id);
|
||||
}
|
||||
affectedAgentIds.add(run.agentId);
|
||||
affectedCompanyIds.set(run.agentId, run.companyId);
|
||||
|
||||
const chain = db.agents.getChainOfCommand(run.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(run.agentId);
|
||||
if (chain.length > 0) {
|
||||
wakeAgentIfPossible(chain[0].id, run.companyId, "report_failed");
|
||||
}
|
||||
@@ -75,11 +75,11 @@ export function handleWorkerExit(
|
||||
for (const agentId of affectedAgentIds) {
|
||||
// EXEC-05: Promote deferred wakes after crash recovery
|
||||
const cid = affectedCompanyIds.get(agentId) ?? "";
|
||||
const promoted = db.wakeup.promoteWakeupRequests(agentId, cid);
|
||||
const promoted = db.automation.wakeup.promoteWakeupRequests(agentId, cid);
|
||||
if (promoted > 0) {
|
||||
logger.info(`[worker] Promoted ${promoted} deferred wake(s) for agent ${agentId} after crash recovery`);
|
||||
}
|
||||
const pendingWakeTrigger = db.wakeup.getPreferredWakeTrigger(agentId, cid);
|
||||
const pendingWakeTrigger = db.automation.wakeup.getPreferredWakeTrigger(agentId, cid);
|
||||
if (pendingWakeTrigger) {
|
||||
wakeAgentIfPossible(agentId, cid, pendingWakeTrigger);
|
||||
}
|
||||
@@ -92,18 +92,18 @@ export function handleWorkerExit(
|
||||
const worker = getWorker();
|
||||
if (requeueCandidates.length > 0 && worker) {
|
||||
logger.info(`[worker] Re-enqueuing ${requeueCandidates.length} queued run(s) after worker restart.`);
|
||||
const recoverySnapshot = db.snap.listSnapshot();
|
||||
const recoverySnapshot = db.projection.snapshot.listSnapshot();
|
||||
for (const candidate of requeueCandidates) {
|
||||
try {
|
||||
// The crashed run may still own a workspace lock acquired during the
|
||||
// original dispatch; release it before re-dispatch so the replay
|
||||
// does not self-collide with "Workspace is locked by another run".
|
||||
if (candidate.workspaceId) {
|
||||
db.workspacesMod.releaseWorkspaceLock(candidate.workspaceId, candidate.id);
|
||||
db.core.workspaces.releaseWorkspaceLock(candidate.workspaceId, candidate.id);
|
||||
}
|
||||
const task = candidate.taskId ? db.entityCrud.getTask(candidate.taskId) : null;
|
||||
const agent = db.entityCrud.getAgent(candidate.agentId);
|
||||
const connector = db.connectorsCrud.getConnector(agent.connectorId);
|
||||
const task = candidate.taskId ? db.core.entity.getTask(candidate.taskId) : null;
|
||||
const agent = db.core.entity.getAgent(candidate.agentId);
|
||||
const connector = db.core.connectors.getConnector(agent.connectorId);
|
||||
const { workspace, lockMode } = resolveWorkspaceWithLockMode(db, task, agent, candidate.companyId);
|
||||
const recoveryCompany = recoverySnapshot.companies.find((company) => company.id === candidate.companyId);
|
||||
|
||||
@@ -122,7 +122,7 @@ export function handleWorkerExit(
|
||||
});
|
||||
} catch (error) {
|
||||
logger.warn(`[worker] Failed to re-enqueue run ${candidate.id}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
db.runs.finishRun({
|
||||
db.core.runs.finishRun({
|
||||
runId: candidate.id,
|
||||
status: "interrupted",
|
||||
summary: "Could not re-enqueue after worker restart.",
|
||||
@@ -133,7 +133,7 @@ export function handleWorkerExit(
|
||||
unattributedCost: true,
|
||||
});
|
||||
if (candidate.workspaceId) {
|
||||
db.workspacesMod.releaseWorkspaceLock(candidate.workspaceId, candidate.id);
|
||||
db.core.workspaces.releaseWorkspaceLock(candidate.workspaceId, candidate.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +111,14 @@ export interface WorkerServiceDependencies {
|
||||
dispatchRunToWorker: (params: {
|
||||
runId: string;
|
||||
companyId: string;
|
||||
agent: ReturnType<AppDatabase["entityCrud"]["getAgent"]>;
|
||||
task: ReturnType<AppDatabase["entityCrud"]["getTask"]> | null;
|
||||
connector: ReturnType<AppDatabase["connectorsCrud"]["getConnector"]>;
|
||||
workspace: ReturnType<AppDatabase["workspacesMod"]["getWorkspace"]>;
|
||||
agent: ReturnType<AppDatabase["core"]["entity"]["getAgent"]>;
|
||||
task: ReturnType<AppDatabase["core"]["entity"]["getTask"]> | null;
|
||||
connector: ReturnType<AppDatabase["core"]["connectors"]["getConnector"]>;
|
||||
workspace: ReturnType<AppDatabase["core"]["workspaces"]["getWorkspace"]>;
|
||||
workspaceMode: "exclusive" | "shared" | "none";
|
||||
companyName: string;
|
||||
companyDescription: string;
|
||||
snapshot: ReturnType<AppDatabase["snap"]["listSnapshot"]>;
|
||||
snapshot: ReturnType<AppDatabase["projection"]["snapshot"]["listSnapshot"]>;
|
||||
trigger: string;
|
||||
}) => void;
|
||||
processRunCompletion: (run: RunRecord, event: z.infer<typeof workerMessageSchema> & { type: "run-finished" }) => void;
|
||||
|
||||
@@ -6,7 +6,7 @@ export function handleContextCompaction(
|
||||
deps: HandlerDeps,
|
||||
_context: HandlerContext,
|
||||
): void {
|
||||
const runState = deps.db.runs.getRunAuthState(event.runId);
|
||||
const runState = deps.db.core.runs.getRunAuthState(event.runId);
|
||||
if (!runState) {
|
||||
deps.logger.warn(`[context-compaction] run ${event.runId} disappeared before telemetry could be recorded.`);
|
||||
return;
|
||||
|
||||
@@ -7,13 +7,13 @@ import { isUsageStatusUnattributed } from "../../run-usage-status";
|
||||
import { decideRunAttempt, runAttemptKey } from "../../run-attempt-policy";
|
||||
|
||||
type RunFinishedEvent = Extract<WorkerMessageEvent, { type: "run-finished" }>;
|
||||
type RunRecord = ReturnType<HandlerDeps["db"]["runs"]["getRun"]>;
|
||||
type TaskRecord = ReturnType<HandlerDeps["db"]["entityCrud"]["getTask"]>;
|
||||
type RunRecord = ReturnType<HandlerDeps["db"]["core"]["runs"]["getRun"]>;
|
||||
type TaskRecord = ReturnType<HandlerDeps["db"]["core"]["entity"]["getTask"]>;
|
||||
|
||||
export interface RunFinishedContext {
|
||||
run: RunRecord;
|
||||
task: TaskRecord | null;
|
||||
runOutcome: ReturnType<HandlerDeps["db"]["runs"]["finishRun"]>;
|
||||
runOutcome: ReturnType<HandlerDeps["db"]["core"]["runs"]["finishRun"]>;
|
||||
}
|
||||
|
||||
export function finalizeRun(
|
||||
@@ -26,7 +26,7 @@ export function finalizeRun(
|
||||
|
||||
let run: RunRecord;
|
||||
try {
|
||||
run = db.runs.getRun(event.runId);
|
||||
run = db.core.runs.getRun(event.runId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[finalizeRun] getRun(${event.runId}) failed; run may have been deleted: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -60,7 +60,7 @@ export function finalizeRun(
|
||||
let task: TaskRecord | null = null;
|
||||
if (run.taskId) {
|
||||
try {
|
||||
task = db.entityCrud.getTask(run.taskId);
|
||||
task = db.core.entity.getTask(run.taskId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[finalizeRun] getTask(${run.taskId}) failed; task may have been deleted: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -70,11 +70,11 @@ export function finalizeRun(
|
||||
}
|
||||
|
||||
leaseRenewalMap.delete(event.runId);
|
||||
const runOutcome = db.runs.finishRun({ ...event, unattributedCost });
|
||||
const runOutcome = db.core.runs.finishRun({ ...event, unattributedCost });
|
||||
if (run.workspaceId) {
|
||||
db.workspacesMod.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
db.core.workspaces.releaseWorkspaceLock(run.workspaceId, run.id);
|
||||
}
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: run.companyId,
|
||||
actor: run.agentId,
|
||||
action: `run.${event.status}`,
|
||||
@@ -101,9 +101,9 @@ export function accumulateAndRotateSession(
|
||||
// heartbeat run — the exact failure mode that lets a taskless transcript
|
||||
// grow past the model's context window. Resolve the session through the
|
||||
// agent, never through the task.
|
||||
let agentRecord: ReturnType<HandlerDeps["db"]["entityCrud"]["getAgent"]>;
|
||||
let agentRecord: ReturnType<HandlerDeps["db"]["core"]["entity"]["getAgent"]>;
|
||||
try {
|
||||
agentRecord = db.entityCrud.getAgent(run.agentId);
|
||||
agentRecord = db.core.entity.getAgent(run.agentId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[session] accumulateAndRotateSession: getAgent(${run.agentId}) failed; skipping: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -111,7 +111,7 @@ export function accumulateAndRotateSession(
|
||||
return;
|
||||
}
|
||||
if (!agentRecord.sessionId) return;
|
||||
const session = db.tasks.getSessionById(agentRecord.sessionId);
|
||||
const session = db.core.tasks.getSessionById(agentRecord.sessionId);
|
||||
if (!session) return;
|
||||
|
||||
sessionService.accumulateTokenUsage(
|
||||
@@ -124,7 +124,7 @@ export function accumulateAndRotateSession(
|
||||
// that include this run, otherwise a run that just crossed the
|
||||
// threshold waits a full additional cycle to rotate — the exact
|
||||
// failure mode that let transcripts balloon past the context window.
|
||||
const updatedSession = db.tasks.getSessionById(agentRecord.sessionId);
|
||||
const updatedSession = db.core.tasks.getSessionById(agentRecord.sessionId);
|
||||
if (!updatedSession) return;
|
||||
const rotationResult = sessionService.evaluateRotation(updatedSession);
|
||||
if (rotationResult.shouldRotate) {
|
||||
@@ -158,9 +158,9 @@ export function emitAndNotifyCompletion(
|
||||
});
|
||||
}
|
||||
|
||||
let updatedAgent: ReturnType<HandlerDeps["db"]["entityCrud"]["getAgent"]> | null = null;
|
||||
let updatedAgent: ReturnType<HandlerDeps["db"]["core"]["entity"]["getAgent"]> | null = null;
|
||||
try {
|
||||
updatedAgent = db.entityCrud.getAgent(run.agentId);
|
||||
updatedAgent = db.core.entity.getAgent(run.agentId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[emitAndNotifyCompletion] getAgent(${run.agentId}) failed; skipping budget-paused notification: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -169,7 +169,7 @@ export function emitAndNotifyCompletion(
|
||||
}
|
||||
|
||||
if (updatedAgent && runOutcome.budgetHardStopped) {
|
||||
db.activityMod.addActivity({
|
||||
db.core.activity.addActivity({
|
||||
companyId: updatedAgent.companyId,
|
||||
actor: "system",
|
||||
action: "agent.auto_paused",
|
||||
@@ -233,8 +233,8 @@ export function runPostCompletionAutomation(
|
||||
// EXEC-05: Promote deferred wakes after run completion
|
||||
let hadDeferredWakes = false;
|
||||
if (!isQuitting() && !destroyed) {
|
||||
const promoted = db.wakeup.promoteWakeupRequests(run.agentId, run.companyId);
|
||||
const pendingWakeTrigger = db.wakeup.getPreferredWakeTrigger(run.agentId, run.companyId);
|
||||
const promoted = db.automation.wakeup.promoteWakeupRequests(run.agentId, run.companyId);
|
||||
const pendingWakeTrigger = db.automation.wakeup.getPreferredWakeTrigger(run.agentId, run.companyId);
|
||||
if (promoted > 0) {
|
||||
logger.info(`[worker] Promoted ${promoted} deferred wake(s) for agent ${run.agentId}`);
|
||||
}
|
||||
@@ -297,7 +297,7 @@ export function triggerSelfWake(
|
||||
if (hadDeferredWakes || isQuitting() || destroyed) return;
|
||||
|
||||
try {
|
||||
const actionableTasks = db.tasks.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]);
|
||||
const actionableTasks = db.core.tasks.listAgentTasks(run.agentId, run.companyId, ["todo", "in_progress"]);
|
||||
const hasWorkToContinue = actionableTasks.length > 0 || hasUnfinishedTeamBuilding(db, run.agentId, run.companyId);
|
||||
const consecutiveSelfWakes = selfWakeAttemptMap.get(attemptKey) ?? 0;
|
||||
const decision = decideRunAttempt({
|
||||
@@ -316,7 +316,7 @@ export function triggerSelfWake(
|
||||
);
|
||||
// Escalate to manager since agent is stuck
|
||||
try {
|
||||
const chain = db.agents.getChainOfCommand(run.agentId);
|
||||
const chain = db.core.agents.getChainOfCommand(run.agentId);
|
||||
if (chain.length > 0) {
|
||||
wakeAgentIfPossible(chain[0].id, run.companyId, "report_stuck");
|
||||
}
|
||||
@@ -354,15 +354,15 @@ function hasUnfinishedTeamBuilding(
|
||||
companyId: string,
|
||||
): boolean {
|
||||
try {
|
||||
const agent = db.entityCrud.getAgent(agentId);
|
||||
const agent = db.core.entity.getAgent(agentId);
|
||||
if (agent.reportsTo !== null) return false;
|
||||
const activeReports = db.agents
|
||||
const activeReports = db.core.agents
|
||||
.getDirectReports(agentId, companyId)
|
||||
.filter((r) => r.status !== "pending_approval" && r.status !== "terminated");
|
||||
if (activeReports.length > 0) return false;
|
||||
const goalCount = db.entityCrud.countActiveGoalsForCompany(companyId);
|
||||
const goalCount = db.core.entity.countActiveGoalsForCompany(companyId);
|
||||
if (goalCount === 0) return false;
|
||||
const pendingHires = db.approvalsMod
|
||||
const pendingHires = db.core.approvals
|
||||
.listCompanyApprovalsByType(companyId, "hire_agent")
|
||||
.filter((a) => a.state === "pending");
|
||||
return pendingHires.length === 0;
|
||||
|
||||
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user