diff --git a/src/main/agent-runtime/runtime-bridge.ts b/src/main/agent-runtime/runtime-bridge.ts index b2f2fa05..5853425b 100644 --- a/src/main/agent-runtime/runtime-bridge.ts +++ b/src/main/agent-runtime/runtime-bridge.ts @@ -266,7 +266,8 @@ export class RuntimeBridge { let task: { title: string } | null = null; try { task = this.deps.db.getTask(envelope.taskId); - } catch { + } catch (err) { + this.deps.logger.warn(`[runtime-bridge] getTask failed for ${envelope.taskId} in recordThreadEvent:`, err instanceof Error ? err.message : String(err)); return; } if (!task) return; diff --git a/src/main/api-server/routes-agent-control.ts b/src/main/api-server/routes-agent-control.ts index 324b6a1e..e0e5b6d6 100644 --- a/src/main/api-server/routes-agent-control.ts +++ b/src/main/api-server/routes-agent-control.ts @@ -13,7 +13,8 @@ export function registerAgentControlRoutes(server: AgentApiServer) { const targetAgent = (() => { try { return db.getAgent(targetAgentId); - } catch { + } catch (err) { + log.warn(`[api/agent-control] getAgent failed for ${targetAgentId}:`, err instanceof Error ? err.message : String(err)); return null; } })(); @@ -52,7 +53,8 @@ export function registerAgentControlRoutes(server: AgentApiServer) { let targetAgent; try { targetAgent = db.getAgent(targetAgentId); - } catch { + } catch (err) { + log.warn(`[api/agent-control] getAgent failed for heartbeat ${targetAgentId}:`, err instanceof Error ? err.message : String(err)); targetAgent = null; } if (!targetAgent || targetAgent.companyId !== token.companyId) { diff --git a/src/main/api-server/routes-agents.ts b/src/main/api-server/routes-agents.ts index 9866e8ed..a3c2557b 100644 --- a/src/main/api-server/routes-agents.ts +++ b/src/main/api-server/routes-agents.ts @@ -156,7 +156,8 @@ export function registerAgentRoutes(server: AgentApiServer) { let agent; try { agent = db.getAgent(requireParam(params, "agentId")); - } catch { + } catch (err) { + log.warn(`[api/agents] getAgent failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Agent not found" }); return; } @@ -262,7 +263,8 @@ export function registerAgentRoutes(server: AgentApiServer) { const connectorRecord = (() => { try { return db.getConnector(input.connectorId); - } catch { + } catch (err) { + log.warn(`[api/agents] getConnector failed for ${input.connectorId}:`, err instanceof Error ? err.message : String(err)); return null; } })(); @@ -305,7 +307,8 @@ export function registerAgentRoutes(server: AgentApiServer) { ? (() => { try { return db.getAgent(resolvedReportsTo).name; - } catch { + } catch (err) { + log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err)); return resolvedReportsTo; } })() diff --git a/src/main/api-server/routes-meetings.ts b/src/main/api-server/routes-meetings.ts index affa15dc..d217f2e2 100644 --- a/src/main/api-server/routes-meetings.ts +++ b/src/main/api-server/routes-meetings.ts @@ -38,7 +38,8 @@ export function registerMeetingRoutes(server: AgentApiServer) { return; } } - } catch { + } catch (err) { + log.warn(`[api/meetings] participantAgentIds JSON parse failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 400, { error: "participantAgentIds must be a JSON array of agent ids" }); return; } @@ -95,7 +96,8 @@ export function registerMeetingRoutes(server: AgentApiServer) { if (data.actionItemsJson) { try { actionItems = JSON.parse(data.actionItemsJson) as Array<{ title?: string; assigneeAgentId?: string; description?: string }>; - } catch { + } catch (err) { + log.warn(`[api/meetings] actionItemsJson parse failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 400, { error: "actionItemsJson must be valid JSON" }); return; } diff --git a/src/main/api-server/routes-messages.ts b/src/main/api-server/routes-messages.ts index 571d252f..b530b42b 100644 --- a/src/main/api-server/routes-messages.ts +++ b/src/main/api-server/routes-messages.ts @@ -1,3 +1,4 @@ +import log from "electron-log/main.js"; import { agentMessageInputSchema } from "@shared/contracts"; import { dispatchAgentMessage } from "../message-dispatch"; import { requireParam, type AgentApiServer } from "./server-core"; @@ -59,7 +60,8 @@ export function registerMessageRoutes(server: AgentApiServer) { } server.json(res, 201, comment); - } catch { + } catch (err) { + log.warn(`[api/messages] addComment failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Task not found" }); } }); @@ -71,7 +73,8 @@ export function registerMessageRoutes(server: AgentApiServer) { server.json(res, 404, { error: "Task not found" }); return; } - } catch { + } catch (err) { + log.warn(`[api/messages] getTask failed for comments list:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Task not found" }); return; } diff --git a/src/main/api-server/routes-tasks.ts b/src/main/api-server/routes-tasks.ts index 8658c9d2..d4698a96 100644 --- a/src/main/api-server/routes-tasks.ts +++ b/src/main/api-server/routes-tasks.ts @@ -54,7 +54,8 @@ export function registerTaskRoutes(server: AgentApiServer) { return; } server.json(res, 200, task); - } catch { + } catch (err) { + log.warn(`[api/tasks] getTask failed:`, err instanceof Error ? err.message : String(err)); server.json(res, 404, { error: "Task not found" }); } }); @@ -73,7 +74,8 @@ export function registerTaskRoutes(server: AgentApiServer) { let task; try { task = db.getTask(requireParam(params, "issueId")); - } catch { + } 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" }); return; } diff --git a/src/main/api-server/routes-work-graph.ts b/src/main/api-server/routes-work-graph.ts index 05e7bd37..f828e902 100644 --- a/src/main/api-server/routes-work-graph.ts +++ b/src/main/api-server/routes-work-graph.ts @@ -1,3 +1,4 @@ +import log from "electron-log/main.js"; import { z } from "zod"; import { goalInputSchema, projectInputSchema } from "@shared/contracts"; import { canManageGoal, canManageProject } from "../agent-permissions"; @@ -102,7 +103,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { }); onDomainChanged(); server.json(res, 200, { ok: true }); - } catch { + } catch (err) { + log.warn(`[api/work-graph] Failed to update project:`, err instanceof Error ? err.message : String(err)); server.json(res, 500, { error: "Failed to update project" }); } }); @@ -187,7 +189,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { }); onDomainChanged(); server.json(res, 200, { ok: true }); - } catch { + } catch (err) { + log.warn(`[api/work-graph] Failed to update goal:`, err instanceof Error ? err.message : String(err)); server.json(res, 500, { error: "Failed to update goal" }); } }); @@ -241,7 +244,8 @@ export function registerWorkGraphRoutes(server: AgentApiServer) { }); onDomainChanged(); server.json(res, 201, { id }); - } catch { + } catch (err) { + log.warn(`[api/work-graph] Invalid sprint data:`, err instanceof Error ? err.message : String(err)); server.json(res, 400, { error: "Invalid sprint data" }); } }); diff --git a/src/main/automation/automation-actions.ts b/src/main/automation/automation-actions.ts index 14fbf681..fe175bab 100644 --- a/src/main/automation/automation-actions.ts +++ b/src/main/automation/automation-actions.ts @@ -151,7 +151,8 @@ export function executeAutomationAction( try { const sender = db.getAgent(fromAgentId); channelTargetId = sender.department ?? null; - } catch { + } catch (err) { + logger.warn(`[automation] getAgent failed for sender ${fromAgentId}; dropping department target:`, err instanceof Error ? err.message : String(err)); channelTargetId = null; } } @@ -221,21 +222,22 @@ export function executeAutomationAction( let agentName = "an agent"; try { agentName = db.getAgent(targetAgentId).name; - } catch { - // Fall back to the generic label when the agent was deleted mid-flight. + } catch (err) { + logger.warn(`[automation] getAgent failed for escalation target ${targetAgentId}; using generic label:`, err instanceof Error ? err.message : String(err)); } let taskTitle = ctx.taskId ?? "unknown"; try { if (ctx.taskId) taskTitle = db.getTask(ctx.taskId).title; - } catch { - // Keep the fallback title when the task no longer exists. + } catch (err) { + logger.warn(`[automation] getTask failed for escalation task ${ctx.taskId}; using id as title:`, err instanceof Error ? err.message : String(err)); } // Heartbeat tasks are internal system tasks — skip escalation messages to avoid flooding channels. let isHeartbeatTask = false; if (ctx.taskId) { try { isHeartbeatTask = db.getTask(ctx.taskId).isHeartbeat === true; - } catch { + } catch (err) { + logger.warn(`[automation] getTask failed for heartbeat check on ${ctx.taskId}; treating as non-heartbeat:`, err instanceof Error ? err.message : String(err)); isHeartbeatTask = false; } } diff --git a/src/main/automation/automation-conditions.ts b/src/main/automation/automation-conditions.ts index 12f3fdc4..b10f17d9 100644 --- a/src/main/automation/automation-conditions.ts +++ b/src/main/automation/automation-conditions.ts @@ -1,11 +1,16 @@ import type { AppDatabase } from "../database"; import type { AutomationEventContext } from "./types"; +type ConditionLogger = { + warn: (...args: unknown[]) => void; +}; + export function matchesEventConfig( db: AppDatabase, configJson: string, sourceDepartment: string | null, ctx: AutomationEventContext, + logger?: ConditionLogger, ): boolean { if (sourceDepartment && ctx.department && sourceDepartment !== ctx.department) { return false; @@ -21,8 +26,8 @@ export function matchesEventConfig( const task = ctx.taskId ? db.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 { - // Entity missing — treat as condition not applicable, continue matching. + } catch (err) { + logger?.warn(`[automation-conditions] getTask failed for ${ctx.taskId}; treating priority condition as not applicable:`, err instanceof Error ? err.message : String(err)); } } if (typeof conditions.budgetThresholdPct === "number" && ctx.agentId) { @@ -33,8 +38,8 @@ export function matchesEventConfig( if (utilization < (conditions.budgetThresholdPct as number) / 100) return false; } // If budget is 0 (unlimited), skip budget condition instead of rejecting. - } catch { - // Agent missing — treat as condition not applicable, continue matching. + } catch (err) { + logger?.warn(`[automation-conditions] getAgent failed for ${ctx.agentId}; treating budget condition as not applicable:`, err instanceof Error ? err.message : String(err)); } } if (conditions.taskId && ctx.taskId && conditions.taskId !== ctx.taskId) return false; @@ -47,6 +52,7 @@ export function matchesConditions( db: AppDatabase, rule: { conditionsJson: string; sourceDepartment: string | null }, ctx: AutomationEventContext, + logger?: ConditionLogger, ): boolean { - return matchesEventConfig(db, rule.conditionsJson, rule.sourceDepartment, ctx); + return matchesEventConfig(db, rule.conditionsJson, rule.sourceDepartment, ctx, logger); } diff --git a/src/main/automation/automation-handlers.ts b/src/main/automation/automation-handlers.ts index cc437850..b579a7ef 100644 --- a/src/main/automation/automation-handlers.ts +++ b/src/main/automation/automation-handlers.ts @@ -102,8 +102,8 @@ export function handleTaskStatusChange( } } } - } catch { - // Parent task may have been deleted or reassigned during completion cascade evaluation. + } catch (err) { + deps.logger.warn(`[automation] Parent task ${task.parentId} lookup failed during completion cascade:`, err instanceof Error ? err.message : String(err)); } } } diff --git a/src/main/automation/automation-run-completion.ts b/src/main/automation/automation-run-completion.ts index 15466bc4..ff1f9a0e 100644 --- a/src/main/automation/automation-run-completion.ts +++ b/src/main/automation/automation-run-completion.ts @@ -274,8 +274,8 @@ export function processRunCompletion( try { const agent = db.getAgent(run.agentId); automationContext.department = agent.department ?? undefined; - } catch { - // Agent can disappear after a run completes; keep the context without department. + } 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)); } if (isSuccess) { @@ -295,8 +295,8 @@ export function processRunCompletion( } } } - } catch { - // Parent task may have been deleted or reassigned during cascade evaluation. + } catch (err) { + deps.logger.warn(`[automation] Parent task ${task.parentId} cascade failed during run completion:`, err instanceof Error ? err.message : String(err)); } } } @@ -326,8 +326,8 @@ export function processRunCompletion( notify, (en, zh) => localizeNotification(budgetLocale, en, zh), ); - } catch { - // Budget follow-up is best-effort; do not fail run completion if the agent disappeared. + } catch (err) { + deps.logger.warn(`[automation] Budget follow-up failed for agent ${run.agentId}:`, err instanceof Error ? err.message : String(err)); } } } diff --git a/src/main/automation/index.ts b/src/main/automation/index.ts index 50be40e8..69dd98d0 100644 --- a/src/main/automation/index.ts +++ b/src/main/automation/index.ts @@ -109,7 +109,7 @@ export function createAutomationService({ try { const rules = db.listActiveAutomationRules(ctx.companyId, trigger); for (const rule of rules) { - if (!matchesConditions(db, rule, ctx)) continue; + if (!matchesConditions(db, rule, ctx, logger)) continue; const executionKey = JSON.stringify({ ruleId: rule.id, trigger, @@ -166,7 +166,7 @@ export function createAutomationService({ try { const workflows = db.listTriggerableWorkflows(ctx.companyId, trigger); for (const workflow of workflows) { - if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx)) continue; + if (!matchesEventConfig(db, workflow.triggerConfigJson, null, ctx, logger)) continue; const executionKey = JSON.stringify({ workflowId: workflow.id, trigger, diff --git a/src/main/browser-action-service.ts b/src/main/browser-action-service.ts index c37f9c0d..05f5f63e 100644 --- a/src/main/browser-action-service.ts +++ b/src/main/browser-action-service.ts @@ -1,4 +1,5 @@ import { join } from "node:path"; +import log from "electron-log/main.js"; import type { AppDatabase } from "./database"; import { BrowserManager } from "./browser-manager"; @@ -28,7 +29,8 @@ export function createBrowserActionService({ function getApprovalOrNull(approvalId: string): ReturnType | null { try { return db.getApprovalRecord(approvalId); - } catch { + } catch (err) { + log.warn(`[browser-action] getApprovalRecord failed for ${approvalId}:`, err instanceof Error ? err.message : String(err)); return null; } } diff --git a/src/main/browser-manager.ts b/src/main/browser-manager.ts index 2f25f4a9..0bc698bf 100644 --- a/src/main/browser-manager.ts +++ b/src/main/browser-manager.ts @@ -236,7 +236,8 @@ export class BrowserManager { rules.cookieOrigins.map(async (origin) => { try { return await ses.cookies.get({ url: origin }); - } catch { + } catch (err) { + log.warn(`[browser-manager] cookies.get failed for origin ${origin}:`, err instanceof Error ? err.message : String(err)); return []; } }), @@ -293,7 +294,8 @@ export class BrowserManager { })() `); return result && (result.accountName || result.displayName) ? result : { accountName: "", displayName: "" }; - } catch { + } catch (err) { + log.warn(`[browser-manager] extractProfileFromSession evaluation failed:`, err instanceof Error ? err.message : String(err)); return { accountName: "", displayName: "" }; } finally { if (!win.isDestroyed()) win.close(); diff --git a/src/main/budget-enforcement-service.ts b/src/main/budget-enforcement-service.ts index dd6dda07..c8b20223 100644 --- a/src/main/budget-enforcement-service.ts +++ b/src/main/budget-enforcement-service.ts @@ -24,12 +24,17 @@ export interface BudgetGateResult { * Returns a human-readable label for a budget policy scope. * Looks up entity names from the database for agent and project scopes. */ -function getScopeLabel(db: AppDatabase, policy: BudgetPolicyRecord): string { +function getScopeLabel( + db: AppDatabase, + policy: BudgetPolicyRecord, + logger: { warn: (...args: unknown[]) => void }, +): string { if (policy.scopeType === "agent") { try { const agent = db.getAgent(policy.scopeId); return `Agent "${agent.name}"`; - } catch { + } catch (err) { + logger.warn(`[budget] getAgent failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err)); return `Agent ${policy.scopeId.slice(0, 8)}`; } } @@ -37,7 +42,8 @@ function getScopeLabel(db: AppDatabase, policy: BudgetPolicyRecord): string { try { const row = db.queryAll<{ name: string }>("select name from projects where id = ?", policy.scopeId); return row.length > 0 ? `Project "${row[0].name}"` : `Project ${policy.scopeId.slice(0, 8)}`; - } catch { + } catch (err) { + logger.warn(`[budget] project name lookup failed for scope ${policy.scopeId}:`, err instanceof Error ? err.message : String(err)); return `Project ${policy.scopeId.slice(0, 8)}`; } } @@ -106,7 +112,7 @@ export function createBudgetEnforcementService({ const spent = db.getSpentInWindow(policy); const utilization = spent / policy.amount; const warnThreshold = policy.warnPercent / 100; - const scopeLabel = getScopeLabel(db, policy); + const scopeLabel = getScopeLabel(db, policy, logger); if (utilization >= warnThreshold) { db.upsertBudgetIncident(policy, "warn_threshold", spent); diff --git a/src/main/connector-auth-service.ts b/src/main/connector-auth-service.ts index 0ee39901..8b6c1cab 100644 --- a/src/main/connector-auth-service.ts +++ b/src/main/connector-auth-service.ts @@ -4,6 +4,7 @@ import { homedir, tmpdir } from "node:os"; import { join } from "node:path"; import { randomBytes } from "node:crypto"; import { spawnSync } from "node:child_process"; +import log from "electron-log/main.js"; import type { ConnectorId } from "@shared/types"; function quoteShell(value: string) { @@ -79,8 +80,8 @@ function spawnTerminalWithScript(script: string, _title: string, _cwd: string): }); child.unref(); return true; - } catch { - // Try next terminal candidate. + } catch (err) { + log.warn(`[connector-auth] Failed to spawn terminal ${binary}; trying next candidate:`, err instanceof Error ? err.message : String(err)); } } diff --git a/src/main/database/agents-crud.ts b/src/main/database/agents-crud.ts index bcddfb73..8dd94702 100644 --- a/src/main/database/agents-crud.ts +++ b/src/main/database/agents-crud.ts @@ -1,4 +1,5 @@ import type { DatabaseSync, SQLInputValue } from "node:sqlite"; +import log from "electron-log/main.js"; import type { AgentRecord, AgentRoleLevel, ConnectorId } from "@shared/types"; import { type ReadAll, @@ -125,8 +126,8 @@ export function buildAgentCrudMethods( configBefore: beforeConfig, configAfter: afterConfig, }); - } catch { - /* Non-fatal: revision recording should never block agent save */ + } catch (err) { + log.warn(`[database/agents] recordConfigRevision failed for agent ${id}:`, err instanceof Error ? err.message : String(err)); } } } diff --git a/src/main/database/backup.ts b/src/main/database/backup.ts index fe22986b..d626b2a5 100644 --- a/src/main/database/backup.ts +++ b/src/main/database/backup.ts @@ -2,6 +2,7 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { copyFile, cp, rm } from "node:fs/promises"; import { join } from "node:path"; import { DatabaseSync, backup as backupDatabase } from "node:sqlite"; +import log from "electron-log/main.js"; async function copyIfExists(source: string, destination: string) { if (!existsSync(source)) { @@ -107,8 +108,8 @@ export function buildBackupMethods( try { deps.setDb(deps.openConnection()); deps.init(); - } catch { - // Reopen failed. Caller sees original + recovery messages. + } catch (reopenError) { + log.error(`[database/backup] Reopen failed after restore/recovery failure:`, reopenError instanceof Error ? reopenError.message : String(reopenError)); } throw new Error( `Restore failed: ${message}. Recovery also failed: ${recoveryMessage}. Previous profile is at ${recoveryDir}.`, diff --git a/src/main/database/connectors-crud.ts b/src/main/database/connectors-crud.ts index 6d55a302..c2c91705 100644 --- a/src/main/database/connectors-crud.ts +++ b/src/main/database/connectors-crud.ts @@ -1,4 +1,5 @@ import type { DatabaseSync } from "node:sqlite"; +import log from "electron-log/main.js"; import type { ConnectorId, ConnectorRecord } from "@shared/types"; import type { ReadAll } from "./helpers"; import { nowIso } from "./helpers"; @@ -30,7 +31,8 @@ export function buildConnectorMethods( let definition; try { definition = getConnectorDefinition(row.id as ConnectorId); - } catch { + } catch (err) { + log.warn(`[database/connectors] Unknown connector id ${row.id}; skipping row:`, err instanceof Error ? err.message : String(err)); continue; } result.push({ @@ -110,7 +112,8 @@ export function buildConnectorMethods( let definition; try { definition = getConnectorDefinition(row.id as ConnectorId); - } catch { + } catch (err) { + log.warn(`[database/connectors] Unknown connector id ${row.id} in findConnector:`, err instanceof Error ? err.message : String(err)); return null; } return { diff --git a/src/main/database/messages.ts b/src/main/database/messages.ts index 8a332d66..960ab1c1 100644 --- a/src/main/database/messages.ts +++ b/src/main/database/messages.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto"; import type { DatabaseSync, SQLInputValue } from "node:sqlite"; +import log from "electron-log/main.js"; import type { AgentMessageRecord, AgentRecord } from "@shared/types"; import { BOARD_READER_ID, @@ -139,7 +140,8 @@ export function buildMessageMethods( if (agent.companyId === companyId) { department = agent.department; } - } catch { + } catch (err) { + log.warn(`[database/messages] getAgent failed for ${opts.agentId}; department filter disabled:`, err instanceof Error ? err.message : String(err)); department = null; } @@ -277,7 +279,8 @@ export function buildMessageMethods( if (agent.companyId === companyId) { department = agent.department; } - } catch { + } catch (err) { + log.warn(`[database/messages] getAgent failed for ${agentId} in listAgentInboxUnread; department filter disabled:`, err instanceof Error ? err.message : String(err)); department = null; } diff --git a/src/main/database/runs.ts b/src/main/database/runs.ts index 54b45cbc..997a44fc 100644 --- a/src/main/database/runs.ts +++ b/src/main/database/runs.ts @@ -3,6 +3,7 @@ import { writeFileSync, readFileSync, existsSync, statSync, openSync, readSync, import { appendFile } from "node:fs/promises"; import { join } from "node:path"; import type { DatabaseSync, SQLInputValue } from "node:sqlite"; +import log from "electron-log/main.js"; import type { ConnectorId, RunLogChunk, RunRecord } from "@shared/types"; import { type ReadAll, type SqliteRow, type Transaction, nowIso, toBoolean } from "./helpers"; @@ -90,7 +91,8 @@ export function buildRunMethods( if (!run?.logPath) return ""; try { return readFileSync(run.logPath, "utf8"); - } catch { + } catch (err) { + log.warn(`[database/runs] readFileSync failed for run ${runId} log ${run.logPath}:`, err instanceof Error ? err.message : String(err)); return ""; } }, diff --git a/src/main/desktop-shell-window.ts b/src/main/desktop-shell-window.ts index 9aedcd6a..0647c480 100644 --- a/src/main/desktop-shell-window.ts +++ b/src/main/desktop-shell-window.ts @@ -12,6 +12,7 @@ import { session, shell, } from "electron"; +import log from "electron-log/main.js"; import { attachWindowStatePersistence, loadWindowState } from "./window-state"; import type { NotificationOptions } from "./notification-service"; @@ -96,8 +97,8 @@ export function createDesktopShellWindow(deps: DesktopShellWindowDependencies) { let activeRunCount = 0; try { activeRunCount = getActiveRunCount(); - } catch { - // Cannot determine active runs — proceed with immediate quit as fail-safe + } catch (err) { + log.warn(`[desktop-shell-window] getActiveRunCount failed; quitting as fail-safe:`, err instanceof Error ? err.message : String(err)); onRequestQuit(); app.quit(); return; @@ -150,8 +151,8 @@ export function createDesktopShellWindow(deps: DesktopShellWindowDependencies) { } } // Cancel: do nothing - } catch { - // Dialog failed (e.g. window destroyed mid-flight) — quit as fail-safe + } catch (err) { + log.warn(`[desktop-shell-window] Quit confirmation dialog failed; quitting as fail-safe:`, err instanceof Error ? err.message : String(err)); onRequestQuit(); app.quit(); } finally { diff --git a/src/main/execution-mode-reconciler.ts b/src/main/execution-mode-reconciler.ts index c064c533..59f80ff7 100644 --- a/src/main/execution-mode-reconciler.ts +++ b/src/main/execution-mode-reconciler.ts @@ -1,3 +1,4 @@ +import log from "electron-log/main.js"; import { resolveApprovalDecision } from "./approval-policy"; import type { AppDatabase } from "./database"; @@ -35,7 +36,8 @@ function collectImpactedAgentIdsFromWaitStates(db: AppDatabase, companyId: strin if (task.assigneeAgentId) { agentIds.add(task.assigneeAgentId); } - } catch { + } catch (err) { + log.warn(`[execution-mode-reconciler] getTask failed for ${thread.relatedEntityId}:`, err instanceof Error ? err.message : String(err)); continue; } } @@ -71,7 +73,8 @@ function collectImpactedAgentIdsFromReviewState(db: AppDatabase, companyId: stri if (task.assigneeAgentId) { agentIds.add(task.assigneeAgentId); } - } catch { + } catch (err) { + log.warn(`[execution-mode-reconciler] getTask failed for approval ${approval.relatedTaskId}:`, err instanceof Error ? err.message : String(err)); continue; } } @@ -87,7 +90,8 @@ function resumeAgents(db: AppDatabase, companyId: string, agentIds: Iterable void; + warn: (...args: unknown[]) => void; }; } @@ -29,7 +30,8 @@ function enrichWindowsPath(logger: HostEnvironmentDependencies["logger"]) { return readdirSync(pythonBase) .filter((name) => /^Python\d+$/.test(name)) .map((name) => join(pythonBase, name, "Scripts")); - } catch { + } catch (err) { + logger.warn(`[path] readdirSync failed for ${pythonBase}:`, err instanceof Error ? err.message : String(err)); return []; } })(); @@ -94,8 +96,8 @@ export async function prepareHostEnvironment({ logger.info(`[path] Added ${toAdd.length} shell PATH entries.`); } } - } catch { - // Non-fatal environment enrichment. + } catch (err) { + logger.warn(`[path] Shell PATH probe failed:`, err instanceof Error ? err.message : String(err)); } } else { logger.info("[path] Skipping interactive shell PATH probe; using deterministic PATH setup only."); diff --git a/src/main/ipc-browser-handlers.ts b/src/main/ipc-browser-handlers.ts index cdc0b7d8..b54c0148 100644 --- a/src/main/ipc-browser-handlers.ts +++ b/src/main/ipc-browser-handlers.ts @@ -107,8 +107,8 @@ export function registerBrowserHandlers({ const profile = await browserActions.extractProfileFromSession(input.socialAccountId, account.platform); detectedName = profile.accountName; detectedDisplay = profile.displayName; - } catch { - // Profile extraction is best-effort; proceed with login status update + } catch (err) { + logger.warn(`[ipc/browser] extractProfileFromSession failed for social account ${input.socialAccountId}:`, err instanceof Error ? err.message : String(err)); } db.saveSocialAccount({ id: latestAccount.id, diff --git a/src/main/ipc-budget-handlers.ts b/src/main/ipc-budget-handlers.ts index 1693640b..c3e6dc7c 100644 --- a/src/main/ipc-budget-handlers.ts +++ b/src/main/ipc-budget-handlers.ts @@ -13,7 +13,7 @@ import type { AppDatabase } from "./database"; interface BudgetHandlerDependencies extends Pick< RegisterDomainHandlersDependencies, - "registerHandle" | "ensureCompanyExists" | "publishDomainChanged" + "registerHandle" | "ensureCompanyExists" | "publishDomainChanged" | "logger" > { budgetService: ReturnType | null; db: AppDatabase; @@ -25,6 +25,7 @@ export function registerBudgetHandlers({ publishDomainChanged, budgetService, db, + logger, }: BudgetHandlerDependencies) { // ---- Save (create/update) a budget policy ---- registerHandle(IPC_CHANNELS.saveBudgetPolicy, async (_event, payload) => { @@ -36,8 +37,8 @@ export function registerBudgetHandlers({ if (budgetService) { try { budgetService.checkAutoResolve(parsed.companyId, id); - } catch { - // Best effort -- service may not be ready + } catch (err) { + logger.warn(`[ipc/budget] checkAutoResolve failed for policy ${id} in company ${parsed.companyId}:`, err instanceof Error ? err.message : String(err)); } } publishDomainChanged(); diff --git a/src/main/ipc-content-operations-handlers.ts b/src/main/ipc-content-operations-handlers.ts index c5c3da22..92b092ab 100644 --- a/src/main/ipc-content-operations-handlers.ts +++ b/src/main/ipc-content-operations-handlers.ts @@ -32,6 +32,7 @@ export function registerContentOperationsHandlers({ publishDomainChanged, wakeAgentIfPossible, dispatchAutomation, + logger, }: Pick< RegisterDomainHandlersDependencies, | "registerHandle" @@ -41,6 +42,7 @@ export function registerContentOperationsHandlers({ | "publishDomainChanged" | "wakeAgentIfPossible" | "dispatchAutomation" + | "logger" >) { registerHandle(IPC_CHANNELS.saveMeeting, async (_event, payload) => { const parsed = meetingInputSchema.parse(payload); @@ -52,7 +54,8 @@ export function registerContentOperationsHandlers({ participantAgentIds = Array.isArray(parsedParticipantIds) ? parsedParticipantIds.filter((value): value is string => typeof value === "string") : []; - } catch { + } catch (err) { + logger.warn(`[ipc/content] Meeting participants JSON parse failed:`, err instanceof Error ? err.message : String(err)); return fail("INVALID_PARTICIPANTS", "Meeting participants must be valid JSON."); } const invalidReference = findInvalidReference(parsed.companyId, [ @@ -163,7 +166,8 @@ export function registerContentOperationsHandlers({ if (parsed.id) { try { previousSprintStatus = db.listSnapshot().sprints.find((s: { id: string }) => s.id === parsed.id)?.status ?? null; - } catch { + } catch (err) { + logger.warn(`[ipc/content] listSnapshot failed while resolving sprint ${parsed.id} previous status:`, err instanceof Error ? err.message : String(err)); previousSprintStatus = null; } } diff --git a/src/main/ipc-core-agent-graph-handlers.ts b/src/main/ipc-core-agent-graph-handlers.ts index 20371d5e..0eaae98f 100644 --- a/src/main/ipc-core-agent-graph-handlers.ts +++ b/src/main/ipc-core-agent-graph-handlers.ts @@ -18,6 +18,7 @@ export function registerCoreAgentGraphHandlers({ findInvalidReference, publishDomainChanged, handleApprovalResolved, + logger, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" @@ -26,6 +27,7 @@ export function registerCoreAgentGraphHandlers({ | "findInvalidReference" | "publishDomainChanged" | "handleApprovalResolved" + | "logger" >) { registerHandle(IPC_CHANNELS.saveWorkspace, async (_event, payload) => { const parsed = workspaceInputSchema.parse(payload); @@ -182,7 +184,8 @@ export function registerCoreAgentGraphHandlers({ ? (() => { try { return db.getAgent(resolvedReportsTo).name; - } catch { + } catch (err) { + logger.warn(`[ipc/agent-graph] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err)); return resolvedReportsTo; } })() diff --git a/src/main/ipc-core-operations-handlers.ts b/src/main/ipc-core-operations-handlers.ts index 5604d53d..9b9de339 100644 --- a/src/main/ipc-core-operations-handlers.ts +++ b/src/main/ipc-core-operations-handlers.ts @@ -32,6 +32,7 @@ export function registerCoreOperationHandlers({ queueTaskRun, getWorkerProcess, notify, + logger, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" @@ -46,6 +47,7 @@ export function registerCoreOperationHandlers({ | "queueTaskRun" | "getWorkerProcess" | "notify" + | "logger" >) { registerHandle(IPC_CHANNELS.requestApproval, async (_event, payload) => { const parsed = approvalInputSchema.parse(payload); @@ -201,8 +203,8 @@ export function registerCoreOperationHandlers({ taskId: parsed.taskId, agentId: parsed.authorAgentId ?? undefined, }); - } catch { - // Task may have been deleted between comment creation and wake evaluation. + } catch (err) { + logger.warn(`[ipc/core-operations] Post-comment wake/dispatch failed for task ${parsed.taskId}:`, err instanceof Error ? err.message : String(err)); } return ok(comment.id); }); diff --git a/src/main/ipc-core-read-handlers.ts b/src/main/ipc-core-read-handlers.ts index d6739da6..a2097527 100644 --- a/src/main/ipc-core-read-handlers.ts +++ b/src/main/ipc-core-read-handlers.ts @@ -20,6 +20,7 @@ export function registerCoreReadHandlers({ resetAfterRestore, getApiPort, listConnectorModels, + logger, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" @@ -30,6 +31,7 @@ export function registerCoreReadHandlers({ | "resetAfterRestore" | "getApiPort" | "listConnectorModels" + | "logger" >) { registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.listRendererSnapshot())); @@ -109,7 +111,8 @@ export function registerCoreReadHandlers({ try { const parsed = connectorInputSchema.shape.id.parse(connectorId); return ok(await listConnectorModels(parsed)); - } catch { + } catch (err) { + logger.warn(`[ipc] listConnectorModels failed for connector ${String(connectorId)}:`, err instanceof Error ? err.message : String(err)); return ok([]); } }); @@ -118,7 +121,8 @@ export function registerCoreReadHandlers({ try { const parsed = connectorInputSchema.shape.id.parse(connectorId); return ok(db.getDisabledModels(parsed)); - } catch { + } catch (err) { + logger.warn(`[ipc] getConnectorDisabledModels failed for connector ${String(connectorId)}:`, err instanceof Error ? err.message : String(err)); return ok([]); } }); @@ -180,7 +184,8 @@ export function registerCoreReadHandlers({ const task = db.getTask(parsed.id); if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company."); return ok(task); - } catch { + } catch (err) { + logger.warn(`[ipc] getTask failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err)); return fail("TASK_NOT_FOUND", "Task not found."); } }); @@ -193,7 +198,8 @@ export function registerCoreReadHandlers({ const agent = db.getAgent(parsed.id); if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company."); return ok(agent); - } catch { + } catch (err) { + logger.warn(`[ipc] getAgent failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err)); return fail("AGENT_NOT_FOUND", "Agent not found."); } }); @@ -206,7 +212,8 @@ export function registerCoreReadHandlers({ const run = db.getRun(parsed.id); if (run.companyId !== parsed.companyId) return fail("RUN_NOT_FOUND", "Run not found in current company."); return ok(run); - } catch { + } catch (err) { + logger.warn(`[ipc] getRun failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err)); return fail("RUN_NOT_FOUND", "Run not found."); } }); @@ -219,7 +226,8 @@ export function registerCoreReadHandlers({ const approval = db.getApprovalRecord(parsed.id); if (approval.companyId !== parsed.companyId) return fail("APPROVAL_NOT_FOUND", "Approval not found in current company."); return ok(approval); - } catch { + } catch (err) { + logger.warn(`[ipc] getApprovalRecord failed for id ${parsed.id}:`, err instanceof Error ? err.message : String(err)); return fail("APPROVAL_NOT_FOUND", "Approval not found."); } }); diff --git a/src/main/ipc-core-task-graph-handlers.ts b/src/main/ipc-core-task-graph-handlers.ts index ae278c2d..316ad909 100644 --- a/src/main/ipc-core-task-graph-handlers.ts +++ b/src/main/ipc-core-task-graph-handlers.ts @@ -118,7 +118,8 @@ export function registerCoreTaskGraphHandlers({ const existingTask = db.getTask(taskId); previousStatus = existingTask.status; previousAssigneeAgentId = existingTask.assigneeAgentId; - } catch { + } catch (err) { + logger.warn(`[ipc/task-graph] getTask failed for ${taskId}; treating as new task:`, err instanceof Error ? err.message : String(err)); previousStatus = null; previousAssigneeAgentId = null; } diff --git a/src/main/ipc-core-work-graph-handlers.ts b/src/main/ipc-core-work-graph-handlers.ts index accb5d2d..54613fd0 100644 --- a/src/main/ipc-core-work-graph-handlers.ts +++ b/src/main/ipc-core-work-graph-handlers.ts @@ -25,6 +25,7 @@ export function registerCoreWorkGraphHandlers( findInvalidReference: deps.findInvalidReference, publishDomainChanged: deps.publishDomainChanged, handleApprovalResolved: deps.handleApprovalResolved, + logger: deps.logger, }); registerCoreTaskGraphHandlers({ registerHandle: deps.registerHandle, diff --git a/src/main/ipc-domain-common.ts b/src/main/ipc-domain-common.ts index 1dce244f..625cbfcb 100644 --- a/src/main/ipc-domain-common.ts +++ b/src/main/ipc-domain-common.ts @@ -30,6 +30,7 @@ export interface RegisterDomainHandlersDependencies { ) => void; db: AppDatabase; logger: { + warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; }; ensureCompanyExists: (companyId: string) => DesktopResult | null; diff --git a/src/main/ipc-domain-handlers.ts b/src/main/ipc-domain-handlers.ts index 58f36f6e..8c8b3c8b 100644 --- a/src/main/ipc-domain-handlers.ts +++ b/src/main/ipc-domain-handlers.ts @@ -21,6 +21,7 @@ export function registerDomainHandlers(dependencies: RegisterDomainHandlersDepen publishDomainChanged: dependencies.publishDomainChanged, skillService: dependencies.skillService, db: dependencies.db, + logger: dependencies.logger, }); registerBudgetHandlers({ registerHandle: dependencies.registerHandle, @@ -28,5 +29,6 @@ export function registerDomainHandlers(dependencies: RegisterDomainHandlersDepen publishDomainChanged: dependencies.publishDomainChanged, budgetService: dependencies.budgetService, db: dependencies.db, + logger: dependencies.logger, }); } diff --git a/src/main/ipc-recovery-handlers.ts b/src/main/ipc-recovery-handlers.ts index 830ce9ed..2c5310e8 100644 --- a/src/main/ipc-recovery-handlers.ts +++ b/src/main/ipc-recovery-handlers.ts @@ -19,6 +19,7 @@ export function registerRecoveryHandlers({ ensureCompanyExists, publishDomainChanged, wakeAgentIfPossible, + logger, }: Pick< RegisterDomainHandlersDependencies, | "registerHandle" @@ -26,6 +27,7 @@ export function registerRecoveryHandlers({ | "ensureCompanyExists" | "publishDomainChanged" | "wakeAgentIfPossible" + | "logger" >) { registerHandle(IPC_CHANNELS.recoveryRetry, async (_event, payload) => { const parsed = recoveryRetrySchema.parse(payload); @@ -37,8 +39,8 @@ export function registerRecoveryHandlers({ db.retryInterruptedTask(parsed.taskId, parsed.agentId, parsed.companyId); try { wakeAgentIfPossible(parsed.agentId, parsed.companyId, "retry_interrupted"); - } catch { - // Best effort -- agent may have been deleted + } catch (err) { + logger.warn(`[ipc/recovery] wakeAgentIfPossible failed for agent ${parsed.agentId}:`, err instanceof Error ? err.message : String(err)); } publishDomainChanged(); return ok(null); diff --git a/src/main/ipc-skill-handlers.ts b/src/main/ipc-skill-handlers.ts index 606f3b94..151a0e0b 100644 --- a/src/main/ipc-skill-handlers.ts +++ b/src/main/ipc-skill-handlers.ts @@ -16,7 +16,7 @@ import type { AppDatabase } from "./database"; interface SkillHandlerDependencies extends Pick< RegisterDomainHandlersDependencies, - "registerHandle" | "ensureCompanyExists" | "publishDomainChanged" + "registerHandle" | "ensureCompanyExists" | "publishDomainChanged" | "logger" > { skillService: ReturnType | null; db: AppDatabase; @@ -28,6 +28,7 @@ export function registerSkillHandlers({ publishDomainChanged, skillService, db, + logger, }: SkillHandlerDependencies) { function requireSkillService(): | { service: NonNullable; error?: undefined } @@ -64,8 +65,8 @@ export function registerSkillHandlers({ // Scan workspace .agents/skills/ directories for new skills before listing try { await svc.scanWorkspaceSkills(parsed.companyId); - } catch { - // Non-fatal: scan failure should not block listing existing skills + } catch (err) { + logger.warn(`[ipc/skills] scanWorkspaceSkills failed for company ${parsed.companyId}:`, err instanceof Error ? err.message : String(err)); } return ok(svc.listForCompany(parsed.companyId)); }); diff --git a/src/main/model-resolver.ts b/src/main/model-resolver.ts index 1c90fa5c..3aea7494 100644 --- a/src/main/model-resolver.ts +++ b/src/main/model-resolver.ts @@ -8,6 +8,7 @@ * other provider we delegate to pi-ai's `getModel` / `getModels`. */ +import log from "electron-log/main.js"; import { getModel, getModels, type Api, type Model } from "@mariozechner/pi-ai"; const FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference"; @@ -54,7 +55,8 @@ export function resolveModel(provider: string, modelId: string): Model | nu } try { return getModel(provider as never, modelId as never) ?? null; - } catch { + } catch (err) { + log.warn(`[model-resolver] getModel failed for ${provider}/${modelId}:`, err instanceof Error ? err.message : String(err)); return null; } } @@ -69,7 +71,8 @@ export function resolveModels(provider: string): Array<{ id: string; name: strin try { const models = getModels(provider as never) ?? []; return models.map((model: { id: string; name?: string }) => ({ id: model.id, name: model.name ?? model.id })); - } catch { + } catch (err) { + log.warn(`[model-resolver] getModels failed for ${provider}:`, err instanceof Error ? err.message : String(err)); return []; } } diff --git a/src/main/service-graph/create-lifecycle.ts b/src/main/service-graph/create-lifecycle.ts index fd0d60b0..cde1c849 100644 --- a/src/main/service-graph/create-lifecycle.ts +++ b/src/main/service-graph/create-lifecycle.ts @@ -72,7 +72,8 @@ export function createLifecycle(deps: LifecycleDeps) { return db.listSnapshot().runs .filter((r) => r.status === "running" || r.status === "queued") .map((r) => r.id); - } catch { + } catch (err) { + logger.warn("[lifecycle] listActiveRunIds failed:", err instanceof Error ? err.message : String(err)); return []; } } diff --git a/src/main/session-management-service.ts b/src/main/session-management-service.ts index fe80ddb5..442261d0 100644 --- a/src/main/session-management-service.ts +++ b/src/main/session-management-service.ts @@ -68,7 +68,8 @@ export function createSessionManagementService({ db, logger }: SessionManagement let task: { title: string; description: string } | null = null; try { task = db.getTask(taskId); - } catch { + } catch (err) { + logger.warn(`[session-management] getTask failed for ${taskId} during handoff generation:`, err instanceof Error ? err.message : String(err)); task = null; } diff --git a/src/main/skills/import.ts b/src/main/skills/import.ts index 5d33a84b..d6036ace 100644 --- a/src/main/skills/import.ts +++ b/src/main/skills/import.ts @@ -272,8 +272,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill const pathParts = urlObj.pathname.split("/").filter(Boolean); const lastPart = pathParts[pathParts.length - 1] ?? ""; urlSlug = normalizeSkillSlug(lastPart.replace(/\.md$/i, "")); - } catch { - // ignore URL parsing errors + } catch (err) { + logger.warn(`[skill-import] Unable to parse skill URL for slug derivation (${url}):`, err instanceof Error ? err.message : String(err)); } const rawSlug = normalizeSkillSlug(parsed.frontmatter.slug as string | undefined) ?? @@ -340,7 +340,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill let realDir: string; try { realDir = realpathSync(dir); - } catch { + } catch (err) { + logger.warn(`[skill-import] realpathSync failed for ${dir}:`, err instanceof Error ? err.message : String(err)); return; } if (visitedRealPaths.has(realDir)) { @@ -351,7 +352,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill let entries: Dirent[]; try { entries = readdirSync(dir, { withFileTypes: true }); - } catch { + } catch (err) { + logger.warn(`[skill-import] readdirSync failed for ${dir}:`, err instanceof Error ? err.message : String(err)); return; } for (const entry of entries) { @@ -395,7 +397,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill let realDir: string; try { realDir = realpathSync(dir); - } catch { + } catch (err) { + logger.warn(`[skill-import] realpathSync failed for ${dir}:`, err instanceof Error ? err.message : String(err)); return; } if (visitedRealPaths.has(realDir)) { @@ -406,7 +409,8 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill let entries: Dirent[]; try { entries = readdirSync(dir, { withFileTypes: true }); - } catch { + } catch (err) { + logger.warn(`[skill-import] readdirSync failed for ${dir}:`, err instanceof Error ? err.message : String(err)); return; } for (const entry of entries) { diff --git a/src/main/task-quality-gates.ts b/src/main/task-quality-gates.ts index feb09a8a..cf2e9eca 100644 --- a/src/main/task-quality-gates.ts +++ b/src/main/task-quality-gates.ts @@ -5,6 +5,7 @@ * minimum description quality, acceptance criteria, and capability matching. */ +import log from "electron-log/main.js"; import type { DesktopEvent } from "@shared/types"; import type { AppDatabase } from "@main/database"; import type { NotificationOptions } from "@main/notification-service"; @@ -279,8 +280,8 @@ export function sweepStaleReviews( detail: "Auto-approved by review timeout escalation", actor: "system", }); - } catch { - // Already decided (race condition with manual approval) -- skip silently (Pitfall 3) + } catch (err) { + log.warn(`[task-quality-gates] resolveApprovalDecision failed for approval ${pendingApproval.id} (likely already decided):`, err instanceof Error ? err.message : String(err)); } } @@ -288,8 +289,8 @@ export function sweepStaleReviews( try { deps.db.advanceTaskStatus(task.id, "done", "Auto-advanced: review timeout exceeded 30 minutes."); deps.handleTaskStatusChange(task.id, companyId, "in_review", "done"); - } catch { - // Task may have been updated by manual action + } catch (err) { + log.warn(`[task-quality-gates] advanceTaskStatus failed for task ${task.id}:`, err instanceof Error ? err.message : String(err)); } escalatedCount++; diff --git a/src/main/thread-integration/index.ts b/src/main/thread-integration/index.ts index 91434c5a..6bffc202 100644 --- a/src/main/thread-integration/index.ts +++ b/src/main/thread-integration/index.ts @@ -139,7 +139,7 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh }); notifyThreadSubscribers(db, thread.id, thread.title, approvalReason, notify); } - announceApprovalToBoard(db, approval, { emitEvent, publishDomainChanged }); + announceApprovalToBoard(db, approval, { emitEvent, publishDomainChanged, logger }); } catch (err) { logger.error(`[thread-integration] onApprovalCreated failed: ${err instanceof Error ? err.message : String(err)}`); } @@ -305,7 +305,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh try { const task = db.getTask(thread.relatedEntityId); if (task.assigneeAgentId !== toAgentId) continue; - } catch { + } catch (err) { + logger.warn(`[thread-integration] getTask failed for thread ${ws.threadId}:`, err instanceof Error ? err.message : String(err)); continue; } } @@ -321,7 +322,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh try { const waitStates = db.listThreadWaitStates(companyId); return waitStates.length > 0; - } catch { + } catch (err) { + logger.warn(`[thread-integration] hasUnresolvedWaitStates query failed for company ${companyId}:`, err instanceof Error ? err.message : String(err)); return false; } }, @@ -343,7 +345,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh companyId, agentId, ); return rows.length > 0; - } catch { + } catch (err) { + logger.warn(`[thread-integration] hasUnresolvedWaitStatesForAgent query failed (company=${companyId} agent=${agentId}):`, err instanceof Error ? err.message : String(err)); return false; } }, @@ -355,7 +358,8 @@ export function createThreadIntegration({ db, logger, emitEvent, publishDomainCh countUnresolvedWaitStates(companyId: string): number { try { return db.listThreadWaitStates(companyId).length; - } catch { + } catch (err) { + logger.warn(`[thread-integration] countUnresolvedWaitStates failed for company ${companyId}:`, err instanceof Error ? err.message : String(err)); return 0; } }, diff --git a/src/main/thread-integration/thread-helpers.ts b/src/main/thread-integration/thread-helpers.ts index e1d0eb22..d107d5f7 100644 --- a/src/main/thread-integration/thread-helpers.ts +++ b/src/main/thread-integration/thread-helpers.ts @@ -5,7 +5,7 @@ import type { ThreadType } from "@shared/types"; export interface ThreadIntegrationDeps { db: AppDatabase; - logger: { info: (...args: unknown[]) => void; error: (...args: unknown[]) => void }; + logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void }; emitEvent?: (event: import("@shared/types").DesktopEvent) => void; publishDomainChanged?: () => void; notify?: (options: { title: string; body: string }) => void; @@ -64,6 +64,7 @@ function resolveApprovalAnnouncementChannel( companyId: string, taskId: string | null, requestedByAgentId: string | null, + logger: ThreadIntegrationDeps["logger"], ): { channel: "department" | "project" | "company"; channelTargetId: string | null } | null { if (requestedByAgentId) { try { @@ -71,8 +72,8 @@ function resolveApprovalAnnouncementChannel( if (requester.companyId === companyId && requester.department) { return { channel: "department", channelTargetId: requester.department }; } - } catch { - // Fall through to task/project/company targeting. + } catch (err) { + logger.warn(`[thread-helpers] getAgent failed for requester ${requestedByAgentId}; falling through to task targeting:`, err instanceof Error ? err.message : String(err)); } } @@ -82,8 +83,8 @@ function resolveApprovalAnnouncementChannel( if (task.companyId === companyId && task.projectId) { return { channel: "project", channelTargetId: task.projectId }; } - } catch { - // Fall through to company-wide routing. + } catch (err) { + logger.warn(`[thread-helpers] getTask failed for task ${taskId}; falling through to company routing:`, err instanceof Error ? err.message : String(err)); } } @@ -100,7 +101,7 @@ export function announceApprovalToBoard( payloadSummary: string; impactSummary: string; }, - deps: Pick, + deps: Pick, ): void { if (!approval.requestedByAgentId || !deps.emitEvent || !deps.publishDomainChanged) return; @@ -109,6 +110,7 @@ export function announceApprovalToBoard( approval.companyId, approval.relatedTaskId, approval.requestedByAgentId, + deps.logger, ); if (!target) return; diff --git a/tests/unit/company-mode-switch.test.ts b/tests/unit/company-mode-switch.test.ts index 5f957836..11a228c1 100644 --- a/tests/unit/company-mode-switch.test.ts +++ b/tests/unit/company-mode-switch.test.ts @@ -95,7 +95,7 @@ describe("company mode switching", () => { const threadIntegration = createThreadIntegration({ db, - logger: { info: () => {}, error: () => {} }, + logger: { info: () => {}, warn: () => {}, error: () => {} }, }); threadIntegration.onTaskCreated(taskId, companyId); diff --git a/tests/unit/ipc-agent-graph-handlers.test.ts b/tests/unit/ipc-agent-graph-handlers.test.ts index 2a0da632..718a1339 100644 --- a/tests/unit/ipc-agent-graph-handlers.test.ts +++ b/tests/unit/ipc-agent-graph-handlers.test.ts @@ -77,6 +77,7 @@ describe("ipc-core-agent-graph-handlers model enforcement", () => { findInvalidReference: () => null, publishDomainChanged: vi.fn(), handleApprovalResolved: vi.fn(), + logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, }); const saveAgent = handlers.get(IPC_CHANNELS.saveAgent); diff --git a/tests/unit/message-dispatch.test.ts b/tests/unit/message-dispatch.test.ts index 256865e8..096d2d60 100644 --- a/tests/unit/message-dispatch.test.ts +++ b/tests/unit/message-dispatch.test.ts @@ -174,7 +174,7 @@ describe("message dispatch", () => { const { companyId, ctoId, taskId } = createScaffold(db); const threadIntegration = createThreadIntegration({ db, - logger: { info: () => {}, error: () => {} }, + logger: { info: () => {}, warn: () => {}, error: () => {} }, }); threadIntegration.onTaskCreated(taskId, companyId);