update
This commit is contained in:
1 parent
66158e222e
commit
67c757f481
314 files changed
+37945
-22962
No files matched your search
@@ -0,0 +1,400 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync, type SQLInputValue } from "node:sqlite";
|
||||
import { buildThreadMethods } from "./database/threads";
|
||||
import { buildCompanyMethods } from "./database/company";
|
||||
import { buildAgentMethods } from "./database/agents";
|
||||
import { buildTaskMethods } from "./database/tasks";
|
||||
import { buildMessageMethods } from "./database/messages";
|
||||
import { buildRunMethods } from "./database/runs";
|
||||
import { buildSnapshotMethods } from "./database/snapshot";
|
||||
import { buildSettingsMethods } from "./database/settings";
|
||||
import { buildConnectorMethods } from "./database/connectors-crud";
|
||||
import { buildWorkspaceMethods } from "./database/workspaces";
|
||||
import { buildApprovalMethods } from "./database/approvals";
|
||||
import { buildSocialMethods } from "./database/social";
|
||||
import { buildActivityMethods } from "./database/activity";
|
||||
import { buildSkillMethods } from "./database/skills";
|
||||
import { buildBudgetMethods } from "./database/budget";
|
||||
import { buildEntityCrudMethods } from "./database/entity-crud";
|
||||
import { buildDocumentMethods } from "./database/documents";
|
||||
import { buildAutomationMethods } from "./database/automation";
|
||||
import { buildKnowledgeMethods } from "./database/knowledge";
|
||||
import { buildWakeupMethods } from "./database/wakeup";
|
||||
import { buildBackupMethods } from "./database/backup";
|
||||
import { initializeDatabase } from "./database/schema";
|
||||
import { SecretVault } from "./vault";
|
||||
import type { AgentStatus, ConnectorId, ProfileSnapshot, RunStatus } from "@shared/types";
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
export const STALE_LOCK_TIMEOUT_MS = 15 * 60 * 1000;
|
||||
|
||||
export class AppDatabaseBase {
|
||||
readonly profileDir: string;
|
||||
readonly dbPath: string;
|
||||
readonly logsDir: string;
|
||||
readonly artifactsDir: string;
|
||||
readonly backupsDir: string;
|
||||
readonly vault: SecretVault;
|
||||
protected db: DatabaseSync;
|
||||
|
||||
protected readonly threads: ReturnType<typeof buildThreadMethods>;
|
||||
protected readonly company: ReturnType<typeof buildCompanyMethods>;
|
||||
protected readonly agents: ReturnType<typeof buildAgentMethods>;
|
||||
protected readonly tasks: ReturnType<typeof buildTaskMethods>;
|
||||
protected readonly messages: ReturnType<typeof buildMessageMethods>;
|
||||
protected readonly runs: ReturnType<typeof buildRunMethods>;
|
||||
protected readonly snap: ReturnType<typeof buildSnapshotMethods>;
|
||||
protected readonly settings: ReturnType<typeof buildSettingsMethods>;
|
||||
protected readonly connectorsCrud: ReturnType<typeof buildConnectorMethods>;
|
||||
protected readonly workspacesMod: ReturnType<typeof buildWorkspaceMethods>;
|
||||
protected readonly approvalsMod: ReturnType<typeof buildApprovalMethods>;
|
||||
protected readonly social: ReturnType<typeof buildSocialMethods>;
|
||||
protected readonly activityMod: ReturnType<typeof buildActivityMethods>;
|
||||
protected readonly skills: ReturnType<typeof buildSkillMethods>;
|
||||
protected readonly budget: ReturnType<typeof buildBudgetMethods>;
|
||||
protected readonly entityCrud: ReturnType<typeof buildEntityCrudMethods>;
|
||||
protected readonly documentsMod: ReturnType<typeof buildDocumentMethods>;
|
||||
protected readonly automationMod: ReturnType<typeof buildAutomationMethods>;
|
||||
protected readonly knowledgeMod: ReturnType<typeof buildKnowledgeMethods>;
|
||||
protected readonly wakeup: ReturnType<typeof buildWakeupMethods>;
|
||||
protected readonly backupMod: ReturnType<typeof buildBackupMethods>;
|
||||
|
||||
constructor(rootDir: string) {
|
||||
this.profileDir = rootDir;
|
||||
this.dbPath = join(rootDir, "profile.sqlite");
|
||||
this.logsDir = join(rootDir, "logs");
|
||||
this.artifactsDir = join(rootDir, "artifacts");
|
||||
this.backupsDir = join(rootDir, "backups");
|
||||
mkdirSync(rootDir, { recursive: true });
|
||||
mkdirSync(this.logsDir, { recursive: true });
|
||||
mkdirSync(this.artifactsDir, { recursive: true });
|
||||
mkdirSync(this.backupsDir, { recursive: true });
|
||||
this.vault = new SecretVault(rootDir);
|
||||
this.db = this.openConnection();
|
||||
|
||||
const getDb = () => this.db;
|
||||
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, {
|
||||
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(),
|
||||
getSetting: (key) => this.getSetting(key),
|
||||
readConnectors: () => this.connectorsCrud.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.listSnapshot(options),
|
||||
addComment: (input) => this.addComment(input),
|
||||
});
|
||||
this.agents = buildAgentMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => this.getAgent(id),
|
||||
wouldCreateAgentCycle: (agentId, reportsTo) => this.wouldCreateAgentCycle(agentId, reportsTo),
|
||||
recordConfigRevision: (input) => this.recordConfigRevision(input),
|
||||
cancelRunForDeletion: (runId, taskId, workspaceId, summary) => this.runs.cancelRunForDeletion(runId, taskId, workspaceId, summary),
|
||||
addActivity: (input) => this.addActivity(input),
|
||||
});
|
||||
this.tasks = buildTaskMethods(getDb, ra, tx, {
|
||||
addComment: (input) => this.addComment(input),
|
||||
addActivity: (input) => this.addActivity(input),
|
||||
});
|
||||
this.messages = buildMessageMethods(getDb, ra, {
|
||||
getAgent: (id) => this.getAgent(id),
|
||||
addActivity: (input) => this.addActivity(input),
|
||||
});
|
||||
this.runs = buildRunMethods(getDb, ra, tx, { logsDir: this.logsDir });
|
||||
this.workspacesMod = buildWorkspaceMethods(getDb, ra, tx);
|
||||
this.approvalsMod = buildApprovalMethods(getDb, ra, tx, {
|
||||
getAgent: (id) => this.getAgent(id),
|
||||
});
|
||||
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.addActivity(input),
|
||||
});
|
||||
this.documentsMod = buildDocumentMethods(getDb, ra, tx);
|
||||
this.automationMod = buildAutomationMethods(getDb, ra, {
|
||||
addActivity: (input) => this.addActivity(input),
|
||||
});
|
||||
this.knowledgeMod = buildKnowledgeMethods(getDb, ra);
|
||||
this.wakeup = buildWakeupMethods(getDb, ra, tx);
|
||||
this.backupMod = buildBackupMethods({
|
||||
getDb, setDb: (db) => { this.db = db; },
|
||||
openConnection: () => this.openConnection(),
|
||||
init: () => this.init(),
|
||||
close: () => this.close(),
|
||||
profileDir: this.profileDir, dbPath: this.dbPath,
|
||||
logsDir: this.logsDir, artifactsDir: this.artifactsDir,
|
||||
backupsDir: this.backupsDir,
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
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);
|
||||
this.connectorsCrud.seedConnectors();
|
||||
this._lastInterruptedCount = this.interruptStaleRuns();
|
||||
this.settings.purgeExpiredRunLogs();
|
||||
this.wakeup.purgeExpiredWakeupRequests();
|
||||
}
|
||||
|
||||
close() { this.db.close(); }
|
||||
|
||||
// ─── Settings ──────────────────────────────────────────────────────────────
|
||||
|
||||
getCurrentCompanyId() { return this.settings.getCurrentCompanyId(); }
|
||||
setCurrentCompany(companyId: string) { this.settings.setCurrentCompany(companyId); }
|
||||
hasCompany(companyId: string) { return this.settings.hasCompany(companyId); }
|
||||
belongsToCompany(table: "workspaces" | "agents" | "goals" | "projects" | "tasks" | "approvals" | "runs" | "secrets" | "meetings" | "documents" | "knowledge_base" | "sprints" | "automation_rules" | "workflow_pipelines", id: string, companyId: string) { return this.settings.belongsToCompany(table, id, companyId); }
|
||||
updateSettings(input: { theme?: "system" | "light" | "dark"; locale?: "en" | "zh"; updatesEnabled?: boolean; logRetentionDays?: number }) { this.settings.updateSettings(input); }
|
||||
getLogRetentionDays() { return this.settings.getLogRetentionDays(); }
|
||||
purgeExpiredRunLogs() { return this.settings.purgeExpiredRunLogs(); }
|
||||
canOpenPath(targetPath: string) { return this.settings.canOpenPath(targetPath); }
|
||||
getDisabledModels(connectorId: string) { return this.settings.getDisabledModels(connectorId); }
|
||||
setDisabledModels(connectorId: string, models: Array<{ id: string; label: string }>) { this.settings.setDisabledModels(connectorId, models); }
|
||||
getLastBudgetResetMonth(companyId: string) { return this.settings.getLastBudgetResetMonth(companyId); }
|
||||
setLastBudgetResetMonth(companyId: string, month: string) { this.settings.setLastBudgetResetMonth(companyId, month); }
|
||||
|
||||
// ─── Snapshot ──────────────────────────────────────────────────────────────
|
||||
|
||||
listSnapshot(options: { companyId?: string | null } = {}): ProfileSnapshot { return this.snap.listSnapshot(options); }
|
||||
listRendererSnapshot(): ProfileSnapshot { return this.snap.listRendererSnapshot(); }
|
||||
|
||||
// ─── Company ───────────────────────────────────────────────────────────────
|
||||
|
||||
saveCompany(input: { id?: string | null; name: string; description: string; status: string; autoApproveHires?: boolean; reviewDeliverables?: boolean; executionMode?: string }) { return this.company.saveCompany(input); }
|
||||
deleteCompany(id: string) { return this.company.deleteCompany(id); }
|
||||
getCompanyStatus(companyId: string) { return this.company.getCompanyStatus(companyId); }
|
||||
getCompanyBasic(companyId: string) { return this.company.getCompanyBasic(companyId); }
|
||||
getCompanyExecutionMode(companyId: string) { return this.company.getCompanyExecutionMode(companyId); }
|
||||
getCompanyApprovalSettings(companyId: string) { return this.company.getCompanyApprovalSettings(companyId); }
|
||||
listActiveCompanyIds() { return this.company.listActiveCompanyIds(); }
|
||||
getSidebarBadges(companyId: string) { return this.company.getSidebarBadges(companyId); }
|
||||
getInbox(companyId: string) { return this.company.getInbox(companyId); }
|
||||
getCompanyMetrics(companyId: string, days: number = 30) { return this.company.getCompanyMetrics(companyId, days); }
|
||||
getStandupReport(companyId: string) { return this.company.getStandupReport(companyId); }
|
||||
|
||||
// ─── Connectors ────────────────────────────────────────────────────────────
|
||||
|
||||
saveConnector(input: { id: ConnectorId; command: string; model: string | null; envBindingText: string; notes: string }) { this.connectorsCrud.saveConnector(input); }
|
||||
updateConnectorHealth(input: { id: ConnectorId; status: string; authState: string; version: string | null; lastError: string | null }) { this.connectorsCrud.updateConnectorHealth(input); }
|
||||
getConnector(id: ConnectorId) { return this.connectorsCrud.getConnector(id); }
|
||||
|
||||
// ─── Agents ────────────────────────────────────────────────────────────────
|
||||
|
||||
saveAgent(input: { id?: string | null; companyId: string; name: string; role: string; title: string; department?: string | null; status: AgentStatus; reportsTo?: string | null; connectorId: ConnectorId; workspaceId?: string | null; model?: string | null; metadataJson?: string; capabilities: string; budgetMonthlyUsd: number }) { return this.agents.saveAgent(input); }
|
||||
deleteAgent(id: string, companyId: string) { return this.agents.deleteAgent(id, companyId); }
|
||||
getAgent(id: string) { return this.entityCrud.getAgent(id); }
|
||||
setHeartbeat(agentId: string, enabled: boolean, intervalSec: number) { return this.agents.setHeartbeat(agentId, enabled, intervalSec); }
|
||||
markHeartbeatAt(agentId: string) { return this.agents.markHeartbeatAt(agentId); }
|
||||
resetMonthlyBudgets(companyId: string) { return this.agents.resetMonthlyBudgets(companyId); }
|
||||
listHeartbeatEligibleAgents(companyId: string) { return this.agents.listHeartbeatEligibleAgents(companyId); }
|
||||
getChainOfCommand(agentId: string) { return this.agents.getChainOfCommand(agentId); }
|
||||
getDirectReports(agentId: string, companyId: string) { return this.agents.getDirectReports(agentId, companyId); }
|
||||
getCeoForCompany(companyId: string) { return this.agents.getCeoForCompany(companyId); }
|
||||
listActiveAgentsForCompany(companyId: string) { return this.agents.listActiveAgentsForCompany(companyId); }
|
||||
hasAgentAssignedTasks(agentId: string, companyId: string) { return this.agents.hasAgentAssignedTasks(agentId, companyId); }
|
||||
updateAgentStatus(agentId: string, status: string) { return this.agents.updateAgentStatus(agentId, status); }
|
||||
isAgentPendingApproval(agentId: string) { return this.agents.isAgentPendingApproval(agentId); }
|
||||
findMentionedAgentIds(companyId: string, text: string) { return this.agents.findMentionedAgentIds(companyId, text); }
|
||||
getIdleAgentsForDepartment(companyId: string, department: string) { return this.agents.getIdleAgentsForDepartment(companyId, department); }
|
||||
getManagerForAgent(agentId: string) { return this.agents.getManagerForAgent(agentId); }
|
||||
getAgentPermissions(agentId: string) { return this.agents.getAgentPermissions(agentId); }
|
||||
setAgentPermission(agentId: string, companyId: string, permissionKey: string, enabled: boolean) { return this.agents.setAgentPermission(agentId, companyId, permissionKey, enabled); }
|
||||
hasAgentPermission(agentId: string, permissionKey: string) { return this.agents.hasAgentPermission(agentId, permissionKey); }
|
||||
getRuntimeState(agentId: string) { return this.agents.getRuntimeState(agentId); }
|
||||
setRuntimeState(agentId: string, companyId: string, state: Record<string, unknown>) { return this.agents.setRuntimeState(agentId, companyId, state); }
|
||||
getHeartbeatPolicy(agentId: string) { return this.agents.getHeartbeatPolicy(agentId); }
|
||||
setHeartbeatPolicy(agentId: string, companyId: string, policy: { enabled?: boolean; intervalSec?: number; cooldownSec?: number; maxConcurrentRuns?: number; wakeOnDemand?: boolean }) { return this.agents.setHeartbeatPolicy(agentId, companyId, policy); }
|
||||
countActiveRunsForAgent(agentId: string, companyId: string) { return this.agents.countActiveRunsForAgent(agentId, companyId); }
|
||||
|
||||
// ─── Tasks ─────────────────────────────────────────────────────────────────
|
||||
|
||||
saveTask(input: { id?: string | null; companyId: string; projectId?: string | null; goalId?: string | null; parentId?: string | null; title: string; description: string; assigneeAgentId?: string | null; workspaceId?: string | null; priority: string; status: string }) { return this.tasks.saveTask(input); }
|
||||
getTask(id: string) { return this.entityCrud.getTask(id); }
|
||||
deleteTask(id: string, companyId: string) { return this.tasks.deleteTask(id, companyId); }
|
||||
updateTaskSession(taskId: string, sessionJson: Record<string, unknown> | null, sessionDisplayId: string | null) { return this.tasks.updateTaskSession(taskId, sessionJson, sessionDisplayId); }
|
||||
getTaskSession(taskId: string) { return this.tasks.getTaskSession(taskId); }
|
||||
getActiveTaskSession(taskId: string) { return this.tasks.getActiveTaskSession(taskId); }
|
||||
incrementSessionUsage(sessionId: string, inputTokens: number, outputTokens: number, runId: string) { return this.tasks.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId); }
|
||||
archiveSession(sessionId: string, reason: string, handoffMarkdown: string) { return this.tasks.archiveSession(sessionId, reason, handoffMarkdown); }
|
||||
createNewSession(input: { taskId: string; companyId: string; agentId: string; adapterType: string; sessionParamsJson: string | null; sessionDisplayId: string | null }) { return this.tasks.createNewSession(input); }
|
||||
getRecentRunsForTask(taskId: string, limit: number) { return this.tasks.getRecentRunsForTask(taskId, limit); }
|
||||
getRecentActivityForTask(taskId: string, limit: number) { return this.tasks.getRecentActivityForTask(taskId, limit); }
|
||||
checkoutTask(taskId: string, runId: string) { return this.tasks.checkoutTask(taskId, runId); }
|
||||
releaseTaskLock(taskId: string, runId: string) { return this.tasks.releaseTaskLock(taskId, runId); }
|
||||
renewTaskLease(taskId: string, runId: string) { return this.tasks.renewTaskLease(taskId, runId); }
|
||||
listAgentTasks(agentId: string, companyId: string, statuses: string[]) { return this.tasks.listAgentTasks(agentId, companyId, statuses); }
|
||||
findReusableHeartbeatTask(agentId: string, companyId: string) { return this.tasks.findReusableHeartbeatTask(agentId, companyId); }
|
||||
cleanupDuplicateHeartbeatTasks(companyId: string) { return this.tasks.cleanupDuplicateHeartbeatTasks(companyId); }
|
||||
listReviewTasksForManager(managerId: string, companyId: string) { return this.tasks.listReviewTasksForManager(managerId, companyId); }
|
||||
countActiveTasksForCompany(companyId: string) { return this.tasks.countActiveTasksForCompany(companyId); }
|
||||
listChildTasks(parentId: string, companyId: string) { return this.tasks.listChildTasks(parentId, companyId); }
|
||||
advanceTaskStatus(taskId: string, newStatus: string, comment?: string) { return this.tasks.advanceTaskStatus(taskId, newStatus, comment); }
|
||||
setTaskRequiresUserReview(taskId: string, value: boolean) { return this.tasks.setTaskRequiresUserReview(taskId, value); }
|
||||
hasPendingDeliverableReviews(companyId: string) { return this.tasks.hasPendingDeliverableReviews(companyId); }
|
||||
hasAgentPendingDeliverableReview(agentId: string, companyId: string) { return this.tasks.hasAgentPendingDeliverableReview(agentId, companyId); }
|
||||
hasAgentTaskAwaitingReview(agentId: string, companyId: string) { return this.tasks.hasAgentTaskAwaitingReview(agentId, companyId); }
|
||||
autoAssignTask(taskId: string, companyId: string) { return this.tasks.autoAssignTask(taskId, companyId); }
|
||||
getTaskRetryCount(taskId: string) { return this.tasks.getTaskRetryCount(taskId); }
|
||||
incrementTaskRetryCount(taskId: string) { return this.tasks.incrementTaskRetryCount(taskId); }
|
||||
resetTaskRetryCount(taskId: string) { return this.tasks.resetTaskRetryCount(taskId); }
|
||||
retryInterruptedTask(taskId: string, _agentId: string, _companyId: string) { return this.tasks.retryInterruptedTask(taskId, _agentId, _companyId); }
|
||||
dismissInterruptedTask(taskId: string) { return this.tasks.dismissInterruptedTask(taskId); }
|
||||
markInterruptedTaskFailed(taskId: string) { return this.tasks.markInterruptedTaskFailed(taskId); }
|
||||
setTaskType(taskId: string, taskType: string) { return this.tasks.setTaskType(taskId, taskType); }
|
||||
|
||||
// ─── Runs ──────────────────────────────────────────────────────────────────
|
||||
|
||||
getRun(id: string) { return this.runs.getRun(id); }
|
||||
getRunAuthState(runId: string) { return this.runs.getRunAuthState(runId); }
|
||||
createRun(input: { id?: string; companyId: string; taskId: string; agentId: string; workspaceId: string | null; connectorId: ConnectorId }) { return this.runs.createRun(input); }
|
||||
appendRunLog(runId: string, stream: "stdout" | "stderr", chunk: string) { return this.runs.appendRunLog(runId, stream, chunk); }
|
||||
readRunLog(runId: string) { return this.runs.readRunLog(runId); }
|
||||
readRunLogChunk(runId: string, offset = 0, limit = 65_536) { return this.runs.readRunLogChunk(runId, offset, limit); }
|
||||
markRunStatus(runId: string, status: RunStatus, message: string) { return this.runs.markRunStatus(runId, status, message); }
|
||||
finishRun(input: { runId: string; status: RunStatus; summary: string | null; errorMessage: string | null; exitCode: number | null; signal: string | null; model: string | null; sessionDisplayId: string | null; costUsd: number | null; unattributedCost: boolean }) { return this.runs.finishRun(input); }
|
||||
hasActiveRun(filter: { companyId: string; taskId?: string; agentId?: string }) { return this.runs.hasActiveRun(filter); }
|
||||
listQueuedRuns() { return this.runs.listQueuedRuns(); }
|
||||
getInterruptedRunsForRecovery() { return this.runs.getInterruptedRunsForRecovery(); }
|
||||
appendRunEvent(input: { companyId: string; runId: string; agentId: string; eventType: string; stream?: string | null; level?: string | null; message?: string | null; payload?: Record<string, unknown> | null }) { return this.runs.appendRunEvent(input); }
|
||||
listRunEvents(runId: string, companyId: string, opts?: { offset?: number; limit?: number }) { return this.runs.listRunEvents(runId, companyId, opts); }
|
||||
updateRunActor(runId: string, actorType: string, actorId: string | null) { return this.runs.updateRunActor(runId, actorType, actorId); }
|
||||
|
||||
// ─── Approvals ─────────────────────────────────────────────────────────────
|
||||
|
||||
requestApproval(input: { companyId: string; relatedTaskId?: string | null; requestedByAgentId?: string | null; relatedAgentId?: string | null; type: string; payloadSummary: string; impactSummary: string; payloadJson?: string | null }) { return this.approvalsMod.requestApproval(input); }
|
||||
decideApproval(input: { companyId?: string | null; approvalId: string; state: string; decisionNote: string }) { return this.approvalsMod.decideApproval(input); }
|
||||
getApprovalRecord(id: string) { return this.approvalsMod.getApprovalRecord(id); }
|
||||
deleteApproval(id: string, companyId: string) { this.approvalsMod.deleteApproval(id, companyId); }
|
||||
getBlockingApproval(companyId: string, agentId: string, taskId: string | null) { return this.approvalsMod.getBlockingApproval(companyId, agentId, taskId); }
|
||||
resubmitApproval(approvalId: string, companyId: string) { this.approvalsMod.resubmitApproval(approvalId, companyId); }
|
||||
addApprovalComment(input: { companyId: string; approvalId: string; authorAgentId?: string | null; authorUserId?: string | null; body: string }) { return this.approvalsMod.addApprovalComment(input); }
|
||||
listApprovalComments(approvalId: string, companyId: string) { return this.approvalsMod.listApprovalComments(approvalId, companyId); }
|
||||
linkTaskToApproval(taskId: string, approvalId: string, companyId: string, linkedByAgentId?: string | null) { this.approvalsMod.linkTaskToApproval(taskId, approvalId, companyId, linkedByAgentId); }
|
||||
unlinkTaskFromApproval(taskId: string, approvalId: string) { this.approvalsMod.unlinkTaskFromApproval(taskId, approvalId); }
|
||||
listApprovalsForTask(taskId: string) { return this.approvalsMod.listApprovalsForTask(taskId); }
|
||||
listTasksForApproval(approvalId: string) { return this.approvalsMod.listTasksForApproval(approvalId); }
|
||||
linkManyTasksToApproval(approvalId: string, taskIds: string[], companyId: string, linkedByAgentId?: string | null) { this.approvalsMod.linkManyTasksToApproval(approvalId, taskIds, companyId, linkedByAgentId); }
|
||||
|
||||
// ─── Workspaces ────────────────────────────────────────────────────────────
|
||||
|
||||
saveWorkspace(input: { id?: string | null; companyId: string; name: string; localPath: string; repoUrl: string; repoRef: string; projectId?: string | null; isPrimary: boolean }) { return this.workspacesMod.saveWorkspace(input); }
|
||||
getWorkspace(id: string | null | undefined) { return this.workspacesMod.getWorkspace(id); }
|
||||
getProjectPrimaryWorkspace(projectId: string | null | undefined, companyId: string) { return this.workspacesMod.getProjectPrimaryWorkspace(projectId, companyId); }
|
||||
listWorkspaceLocalPaths(companyId: string) { return this.workspacesMod.listWorkspaceLocalPaths(companyId); }
|
||||
getAnyCompanyWorkspace(companyId: string) { return this.workspacesMod.getAnyCompanyWorkspace(companyId); }
|
||||
deleteWorkspace(id: string, companyId: string) { this.workspacesMod.deleteWorkspace(id, companyId); }
|
||||
acquireWorkspaceLock(workspaceId: string, runId: string) { return this.workspacesMod.acquireWorkspaceLock(workspaceId, runId); }
|
||||
claimWorkspaceLock(workspaceId: string, runId: string) { return this.workspacesMod.claimWorkspaceLock(workspaceId, runId); }
|
||||
releaseWorkspaceLock(workspaceId: string, runId: string) { this.workspacesMod.releaseWorkspaceLock(workspaceId, runId); }
|
||||
releaseAllWorkspaceLocks() { this.workspacesMod.releaseAllWorkspaceLocks(); }
|
||||
releaseStaleWorkspaceLocks() { return this.workspacesMod.releaseStaleWorkspaceLocks(); }
|
||||
isWorkspaceLocked(workspaceId: string) { return this.workspacesMod.isWorkspaceLocked(workspaceId); }
|
||||
|
||||
// ─── Internal infrastructure ───────────────────────────────────────────────
|
||||
protected _lastInterruptedCount = 0;
|
||||
get lastInterruptedCount(): number { return this._lastInterruptedCount; }
|
||||
|
||||
interruptStaleRuns(): number {
|
||||
const ts = nowIso();
|
||||
let interruptedCount = 0;
|
||||
this.transaction(() => {
|
||||
const result = this.db
|
||||
.prepare("update runs set status = 'interrupted', error_message = 'AgentCompany restarted before the run could report completion.', finished_at = ?, updated_at = ? where status = 'running'")
|
||||
.run(ts, ts);
|
||||
interruptedCount = Number(result.changes);
|
||||
if (interruptedCount > 0) {
|
||||
this.db.prepare(
|
||||
`update tasks set status = 'blocked', updated_at = ? where id in (select task_id from runs where status = 'interrupted' and finished_at = ?) and status in ('in_progress', 'todo')`,
|
||||
).run(ts, ts);
|
||||
}
|
||||
this.db.prepare("update tasks set active_run_id = null where active_run_id in (select id from runs where status = 'interrupted')").run();
|
||||
this.db.prepare("update agents set status = 'idle', updated_at = ? where status = 'running'").run(ts);
|
||||
this.releaseAllWorkspaceLocks();
|
||||
});
|
||||
return interruptedCount;
|
||||
}
|
||||
|
||||
recoverStaleLocks(): Array<{ taskId: string; companyId: string; title: string }> {
|
||||
const cutoff = new Date(Date.now() - STALE_LOCK_TIMEOUT_MS).toISOString();
|
||||
const stale = this.readAll<{ id: string; companyId: string; title: string }>(
|
||||
`SELECT id, company_id AS companyId, title FROM tasks WHERE execution_locked_at IS NOT NULL AND execution_locked_at < ? AND status = 'in_progress' AND checkout_run_id NOT IN (SELECT id FROM runs WHERE status = 'running')`,
|
||||
cutoff,
|
||||
);
|
||||
const ts = nowIso();
|
||||
for (const task of stale) {
|
||||
this.db.prepare(`UPDATE tasks SET checkout_run_id = NULL, execution_locked_at = NULL, status = 'todo', updated_at = ? WHERE id = ?`).run(ts, task.id);
|
||||
}
|
||||
return stale.map((t) => ({ taskId: t.id, companyId: t.companyId, title: t.title }));
|
||||
}
|
||||
|
||||
protected incrementActiveSprintVelocity(companyId: string, delta: number) {
|
||||
this.entityCrud.incrementActiveSprintVelocity(companyId, delta);
|
||||
}
|
||||
|
||||
protected getSetting(key: string): string | null {
|
||||
const row = this.db.prepare("select value from settings where key = ?").get(key) as { value: string } | undefined;
|
||||
return row?.value ?? null;
|
||||
}
|
||||
|
||||
protected setSetting(key: string, value: string) {
|
||||
this.db.prepare("insert into settings (key, value) values (?, ?) on conflict(key) do update set value = excluded.value").run(key, value);
|
||||
}
|
||||
|
||||
protected readAll<T>(sql: string, ...params: SQLInputValue[]): T[] {
|
||||
return this.db.prepare(sql).all(...params) as T[];
|
||||
}
|
||||
|
||||
protected wouldCreateAgentCycle(agentId: string, reportsTo: string | null) {
|
||||
if (!reportsTo) return false;
|
||||
if (reportsTo === agentId) return true;
|
||||
const visited = new Set<string>([agentId]);
|
||||
let currentId: string | null = reportsTo;
|
||||
while (currentId) {
|
||||
if (visited.has(currentId)) return true;
|
||||
visited.add(currentId);
|
||||
const current = this.db.prepare("select reports_to as reportsTo from agents where id = ?").get(currentId) as { reportsTo: string | null } | undefined;
|
||||
currentId = current?.reportsTo ?? null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected openConnection(): DatabaseSync {
|
||||
return new DatabaseSync(this.dbPath, { timeout: 5000 });
|
||||
}
|
||||
|
||||
protected transaction<T>(operation: () => T): T {
|
||||
this.db.exec("BEGIN IMMEDIATE");
|
||||
try {
|
||||
const result = operation();
|
||||
this.db.exec("COMMIT");
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.db.exec("ROLLBACK");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Methods used by subclass constructor callbacks ────────────────────────
|
||||
|
||||
addActivity(input: { companyId: string; actor: string; action: string; entityType: string; entityId: string; detail: string }) { this.activityMod.addActivity(input); }
|
||||
addComment(input: { companyId: string; taskId: string; authorAgentId: string | null; authorName: string; body: string }) { return this.activityMod.addComment(input); }
|
||||
recordConfigRevision(input: { companyId: string; agentId: string; changedKeys: string[]; configBefore: Record<string, unknown>; configAfter: Record<string, unknown>; source?: string; rolledBackFromRevisionId?: string | null; createdByAgentId?: string | null; createdByUserId?: string | null }) { return this.documentsMod.recordConfigRevision(input); }
|
||||
}
|
||||
Reference in new issue
Block a user