The earlier sweep cleaned the hot path (worker/runtime/automation/api-
server/ipc core — 59 sites). This pass finishes the deferred long tail:
main/* services, database/* CRUD modules, api-server/* routes, ipc-*
handlers, thread-integration, automation helpers, browser, skills.
Default strategy: catch (error) { logger.warn("[module] context:", ...) }
— preserve current behavior, make the failure visible instead of
silently hidden. Widened 3 logger interface signatures to include warn
where callers only passed info/error previously (thread-helpers,
host-environment-service, ipc-domain-common).
Legitimate boundaries left intact: JSON.parse on untrusted input, DNS
resolver no-records paths, explicit safe* helpers (safeJson, parseJson,
parseTriggerDetail), URL-parse predicates, and `<unserialisable>`
fallbacks inside the prompt-builder summarisers. Each kept catch now
either has a reason in context or is a documented safe* helper.
Tests adjusted only where test-constructed no-op loggers needed a warn
stub (message-dispatch, company-mode-switch, ipc-agent-graph-handlers).
With the earlier pass, AgentCompany no longer has silently-swallowing
catch blocks in main-process or renderer business code.
Cost was written through three independent paths: worker-runner shipped
`finalUsage.costUsd` in run-finished -> finishRun wrote `runs.cost_usd`,
`tasks.cost_usd += costUsd`, `agents.spent_monthly_usd += costUsd`, and
inserted a per-run aggregate row in cost_entries. CostWriter
additionally inserted per-message rows in cost_entries from main. Any
CostWriter failure created silent drift between `SUM(cost_entries)`
(read by budget enforcement) and `runs.cost_usd` (read by the UI).
Make CostWriter the single writer, finishRun the single reader.
- Add `db.recordCostEntry(...)` as the first-class insert path for
per-message cost rows. CostWriter calls it; tests call it directly.
- Remove the aggregate INSERT from finishRun. finishRun now reads
`SELECT COALESCE(SUM(amount_usd), 0) FROM cost_entries WHERE run_id=?
AND unattributed=0` and writes that as the authoritative run total to
runs.cost_usd, tasks.cost_usd, and agents.spent_monthly_usd.
- Drop `costUsd` from `finishRun` input, the worker `run-finished`
ChildMessage shape, the automation `RunFinishedEvent` type, and every
caller + test fixture. Cost is owned by the per-message path.
- worker-runner stops extracting `usage.cost.total` — token counts stay
for session accumulation, cost comes from pi-ai via CostWriter.
- run-finished-helpers now gates budget incident checks on
`event.status === "succeeded"` instead of a cost field that no longer
exists on the event.
- automation-run-completion fires `budget_threshold` on every succeeded
run (post-run spent_monthly_usd already reflects the SUM path).
- Update budget tests to write a `recordCostEntry` before `finishRun`
to simulate the per-message cost path; drop `costUsd` from cross-
subsystem, edge-case-budget, database, and related fixtures.
One writer, one reader, one source of truth. No drift.
Replaces Record<string, string[]> with Record<DepartmentId, string[]>
so future edits to the department taxonomy trigger TS errors for any
keyword map that drifts. Cast at the lookup site reflects the fact
that agent.department is a free-form string column that may hold
legacy or custom values.
- P3-52: replace 5-deep if-chain in deriveEscalationWakeTrigger with two
lookup maps (status takes precedence over rule trigger), which removes
the cognitive overhead of reading the chain and makes the two inputs'
roles explicit.
- P3-53: rewrite the Windows-only curl warning as platform-neutral
guidance that matches the surrounding examples. The old text said
'prefer fetch on Windows' while the doc showed curl POSTs everywhere;
the new text recommends fetch universally and explains when the curl
temp-file pattern is needed.
P3-58: Automation rule's schedule_meeting action hardcoded +1hr from
now, producing meetings that could land in the past or outside working
hours. Accept scheduledAt (ISO-8601) first, fall back to
scheduleOffsetMinutes, and only default to +60min when the rule
specifies no time at all.
P2-37: automation-run-completion.checkRunProducedDeliverable issued 5
raw SQLs via db.queryAll. Consolidate into db.runProducedDeliverable
so schema changes to knowledge_base / documents / task_comments
surface in the knowledge domain module rather than silently breaking
at the caller.
P1-22: The opportunistic JSON.parse of payloadJson ran for every
approval, hoping to find workflowId/stepId. Gate on the actual
approval type that carries workflow metadata (dangerous_command, as
emitted by runWorkflowPipeline), and validate payload shape via zod.
P1-20: normalizeWorkflowSteps used ad-hoc typeof checks that silently
accepted malformed entries (e.g. number where string was expected).
Replace with workflowStepsInputSchema zod validation; semantics for
defaults/action whitelist are preserved.
P2-23: 'lead' in system-prompt.ts matched 'cleaner' / 'leaderboard' via
substring .includes(). Hoist LEADERSHIP_ROLE_KEYWORDS into a shared
agent-role-helpers.ts module; tokenize input on non-alphanum to require
whole-word match.
P3-54: Same keyword list was duplicated in automation-skills with drift
(missing 'ceo', different members). Both sites now call through the
shared helper.
P2-36: Multiple subsystems were string-coupled via the magic
'[Heartbeat]' prefix. Introduce src/main/orchestration/heartbeat-task.ts
as the single source of truth and replace scattered literals in
heartbeat dispatch, automation actions, and run completion.
P3-52: Collapse 5-deep ternary in automation-actions' wake-trigger
selection into lookup helpers (deriveHeartbeatWakeTrigger,
deriveEscalationWakeTrigger).
P2-46: Previous bare try/catch hid parse errors, making rules silently
non-firing. Let JSON.parse throw; the caller (evaluateAutomationRules)
already logs and records the failure.
P1-19: Previous string-join with ':' separator could collide when
field values contain colons (e.g. runId='a:b' vs runId='a' + previousStatus='b').
Use JSON.stringify for unambiguous serialization.