From 821855931d0b669882f7f8a51cefc661771e8ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 23:02:13 -0400 Subject: [PATCH] refactor(db): add findAgent/findConnector; wrap linkManyTasksToApproval in tx; collapse saveAgent IIFEs (P1-28, P1-29) --- src/main/database-base.ts | 2 ++ src/main/database/approvals.ts | 12 +++++++---- src/main/database/connectors-crud.ts | 17 ++++++++++++--- src/main/database/entity-crud.ts | 11 ++++++++++ src/main/ipc-core-agent-graph-handlers.ts | 26 +++-------------------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/main/database-base.ts b/src/main/database-base.ts index 798e1dfe..9fe20271 100644 --- a/src/main/database-base.ts +++ b/src/main/database-base.ts @@ -197,12 +197,14 @@ export class AppDatabaseBase { 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); } + findConnector(id: ConnectorId) { return this.connectorsCrud.findConnector(id); } // ─── Agents ──────────────────────────────────────────────────────────────── saveAgent(input: { id?: string | null; companyId: string; name: string; role: string; title: string; department?: string | null; status: AgentStatus; reportsTo?: string | null; connectorId: ConnectorId; workspaceId?: string | null; model?: string | null; metadataJson?: string; capabilities: string; budgetMonthlyUsd: number }) { return this.agents.saveAgent(input); } deleteAgent(id: string, companyId: string) { return this.agents.deleteAgent(id, companyId); } getAgent(id: string) { return this.entityCrud.getAgent(id); } + findAgent(id: string) { return this.entityCrud.findAgent(id); } setHeartbeat(agentId: string, enabled: boolean, intervalSec: number) { return this.agents.setHeartbeat(agentId, enabled, intervalSec); } markHeartbeatAt(agentId: string) { return this.agents.markHeartbeatAt(agentId); } resetMonthlyBudgets(companyId: string) { return this.agents.resetMonthlyBudgets(companyId); } diff --git a/src/main/database/approvals.ts b/src/main/database/approvals.ts index e3c791a7..5074cf50 100644 --- a/src/main/database/approvals.ts +++ b/src/main/database/approvals.ts @@ -335,12 +335,16 @@ export function buildApprovalMethods( }, linkManyTasksToApproval(approvalId: string, taskIds: string[], companyId: string, linkedByAgentId?: string | null): void { + if (taskIds.length === 0) return; const ts = nowIso(); - for (const taskId of taskIds) { - getDb().prepare( + transaction(() => { + const stmt = getDb().prepare( "INSERT OR IGNORE INTO task_approval_links (task_id, approval_id, company_id, linked_by_agent_id, created_at) VALUES (?, ?, ?, ?, ?)", - ).run(taskId, approvalId, companyId, linkedByAgentId ?? null, ts); - } + ); + for (const taskId of taskIds) { + stmt.run(taskId, approvalId, companyId, linkedByAgentId ?? null, ts); + } + }); }, }; } diff --git a/src/main/database/connectors-crud.ts b/src/main/database/connectors-crud.ts index d3b71960..001e6700 100644 --- a/src/main/database/connectors-crud.ts +++ b/src/main/database/connectors-crud.ts @@ -79,7 +79,7 @@ export function buildConnectorMethods( .run(input.status, input.authState, input.version, nowIso(), input.lastError, nowIso(), input.id); }, - getConnector(id: ConnectorId): ConnectorRecord { + findConnector(id: ConnectorId): ConnectorRecord | null { const row = getDb() .prepare( "select id, label, description, status, command, version, auth_state, last_checked_at, last_error, model, env_binding_text, notes from connectors where id = ?", @@ -100,8 +100,13 @@ export function buildConnectorMethods( notes: string; } | undefined; - if (!row) throw new Error(`Connector not found: ${id}`); - const definition = getConnectorDefinition(row.id as ConnectorId); + if (!row) return null; + let definition; + try { + definition = getConnectorDefinition(row.id as ConnectorId); + } catch { + return null; + } return { id: row.id as ConnectorId, label: row.label, @@ -120,6 +125,12 @@ export function buildConnectorMethods( }; }, + getConnector(id: ConnectorId): ConnectorRecord { + const result = this.findConnector(id); + if (!result) throw new Error(`Connector not found: ${id}`); + return result; + }, + seedConnectors() { getDb().prepare("delete from connectors where id not in ('codex_local', 'claude_local', 'gemini_local') and id not like 'claude_compat%'").run(); const currentRows = readAll>( diff --git a/src/main/database/entity-crud.ts b/src/main/database/entity-crud.ts index 89ed5666..5e124130 100644 --- a/src/main/database/entity-crud.ts +++ b/src/main/database/entity-crud.ts @@ -71,6 +71,17 @@ export function buildEntityCrudMethods( return row; }, + findAgent(id: string): AgentRecord | null { + const row = getDb() + .prepare( + "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where id = ?", + ) + .get(id) as AgentRecord | undefined; + if (!row) return null; + row.heartbeatEnabled = Boolean(row.heartbeatEnabled); + return row; + }, + /** * Returns all goals for a company. Used by GET /api/companies/:id/goals. */ diff --git a/src/main/ipc-core-agent-graph-handlers.ts b/src/main/ipc-core-agent-graph-handlers.ts index 24e2924c..20371d5e 100644 --- a/src/main/ipc-core-agent-graph-handlers.ts +++ b/src/main/ipc-core-agent-graph-handlers.ts @@ -61,15 +61,7 @@ export function registerCoreAgentGraphHandlers({ "Create new agents through the hiring workflow so approval, onboarding, and org state stay synchronized.", ); } - const existingAgent = (() => { - try { - const agentId = parsed.id; - if (!agentId) return null; - return db.getAgent(agentId); - } catch { - return null; - } - })(); + const existingAgent = parsed.id ? db.findAgent(parsed.id) : null; if (!existingAgent || existingAgent.companyId !== parsed.companyId) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } @@ -92,13 +84,7 @@ export function registerCoreAgentGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const connectorRecord = (() => { - try { - return db.getConnector(parsed.connectorId); - } catch { - return null; - } - })(); + const connectorRecord = db.findConnector(parsed.connectorId); if (!connectorRecord) { return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist."); } @@ -162,13 +148,7 @@ export function registerCoreAgentGraphHandlers({ if (invalidReference) { return fail("INVALID_COMPANY_REFERENCE", `The selected ${invalidReference.label} does not belong to the current company.`); } - const connectorRecord = (() => { - try { - return db.getConnector(parsed.connectorId); - } catch { - return null; - } - })(); + const connectorRecord = db.findConnector(parsed.connectorId); if (!connectorRecord) { return fail("CONNECTOR_NOT_FOUND", "The selected connector does not exist."); }