Commit Graph
264 Commits
Author SHA1 Message Date
ZacharyZhang-NY 62050c9c05 refactor(automation): use JSON-stringified execution keys
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.
2026-04-16 22:50:38 -04:00
ZacharyZhang-NY 2050c0e8fd fix(notification-service): track latest section for batch summary 2026-04-16 22:50:09 -04:00
ZacharyZhang-NY 4ed1e47c32 refactor(desktop-shell-menu): drop dead '?? ""' defense in openPath 2026-04-16 22:49:41 -04:00
ZacharyZhang-NY 1ccf31f6d7 refactor(app-lifecycle): invert window-all-closed for clearer macOS intent 2026-04-16 22:49:30 -04:00
ZacharyZhang-NY 2eb6cb2c06 fix(db): move active runs query into deleteTask transaction (P1-12 TOCTOU) 2026-04-16 22:49:22 -04:00
ZacharyZhang-NY 9874d825b6 refactor(api-server): move agents-standup SQL into db domain methods
Replace the six inline queryAll calls in routes-agents-standup.ts with
typed domain methods: db.agents gains listDirectReportsForStandup and
listReportStatusesForStandup (LEFT JOIN aggregation), db.tasks gains
listAssigneeTasksForStandup and listTasksForAssignees (safe on empty
assignee list), db.approvals gains listAgentPendingApprovals and
listAgentResolvedApprovals. Shared listRecentTaskComments is reused.
2026-04-16 22:39:43 -04:00
ZacharyZhang-NY 4342cf3e2f refactor(api-server): move approvals SQL into db.approvals domain methods
Replace the ad-hoc getApproval helper and the filterable list query
in routes-approvals.ts with db.approvals methods. Add findApprovalById
(non-throwing lookup returning null) and listApprovalsForApi (accepts
requestedByAgentId / relatedTaskId / statuses filters and builds the
prepared statement inside the domain layer).
2026-04-16 22:38:05 -04:00
ZacharyZhang-NY 28296b3d5f refactor(api-server): move agents SQL into db.agents/db.activity/db.approvals methods
Replace seven inline queryAll calls in routes-agents.ts with typed
domain methods. db.agents gains listAgentsForApi, listAgentsForStandup,
listExistingAgentsForHireCheck. db.tasks gains listTasksForStandup.
db.approvals gains listApprovalsForStandup and
listPendingHireApprovalPayloads. db.activity gains listRecentTaskComments
(safe on empty taskIds). All HTTP response projections stay identical.
2026-04-16 22:37:10 -04:00
ZacharyZhang-NY efb9fdb8cc refactor(api-server): move work-graph SQL into db domain methods
Replace six inline queryAll calls in routes-work-graph.ts with domain
methods. db.entityCrud gains listGoals, getGoalForUpdate, listProjects,
getProjectForUpdate. db.knowledge gains listSprints. db.company gains
getDashboardSnapshot, which returns the raw agent/task/activity rows
the dashboard handler aggregates. The route-level row shapes are
preserved in the new GoalListRow / ProjectListRow / SprintListRow
types so HTTP responses remain identical.
2026-04-16 22:34:46 -04:00
ZacharyZhang-NY 2be1bd1612 refactor(api-server): move misc SQL into db.knowledge domain method
Replace the inline knowledge_base queryAll in routes-misc.ts with
db.listKnowledgeEntries (added in the previous commit). Routes-misc
now holds no raw SQL.
2026-04-16 22:32:25 -04:00
ZacharyZhang-NY de02351168 refactor(api-server): move meetings SQL into db.knowledge domain methods
Replace inline queryAll calls in routes-meetings.ts with two new
domain methods on db.knowledge: listMeetings (powers
GET /api/companies/:companyId/meetings) and getMeeting (used by the
PATCH meeting handler). The MeetingListRow shape mirrors the historical
inline projection so HTTP responses stay byte-stable. Also adds
listKnowledgeEntries here for use by routes-misc.
2026-04-16 22:32:01 -04:00
ZacharyZhang-NY 79e57567b2 refactor(api-server): move files SQL into db.documents domain methods
Replace raw queryAll calls in routes-files.ts with two new domain
methods on db.documents: listDocuments (powers
GET /api/companies/:companyId/documents) and getDocumentForUpdate (used
by the PATCH document handler). The DocumentListRow / DocumentLookupRow
shapes preserve the historical inline-SQL projections so HTTP responses
remain identical.
2026-04-16 22:30:39 -04:00
ZacharyZhang-NY 7f097e3319 refactor(api-server): move tasks SQL into db.tasks/db.documents domain methods
Replace inline queryAll calls in routes-tasks.ts with two new domain
methods: tasks.listTasksByStatuses (used by GET /api/companies/:id/issues)
and documents.listImportantDocsForReview (used by the deliverable-review
gate). Routes now hold no SQL or column-mapping logic; the
TaskListByStatusesRow shape preserves the existing HTTP response fields
including requiresUserReview as a boolean.
2026-04-16 22:29:36 -04:00
ZacharyZhang-NY 555d8e59eb chore(release): remove release-cloudflare.mjs wrapper, replace with package.json chain 2026-04-16 22:29:09 -04:00
ZacharyZhang-NY f723d3cc80 refactor(release): use shared lib in release-local + landing-release-links 2026-04-16 22:28:43 -04:00
ZacharyZhang-NY e54aea5942 refactor(release): use shared lib in release-boss + load env at startup
Boss script previously did not source .env.release / .env.release.local,
which silently dropped any release-only environment variables that the
remote workflow dispatch and asset download paths may rely on. Match
release-public-cloudflare's behavior by calling loadEnvFile for both
files before any gh / wrangler invocation.
2026-04-16 22:28:15 -04:00
ZacharyZhang-NY 18bcd0abfc refactor(api-server): extract IMPORTANT_DOC_TYPES constant
Move the duplicated important-document-type list out of routes-tasks.ts
and routes-files.ts into src/shared/document-categories.ts so the array
and lookup set live in one place.
2026-04-16 22:27:56 -04:00
ZacharyZhang-NY 12027562e3 refactor(release): use shared lib in release-public-cloudflare 2026-04-16 22:27:24 -04:00
ZacharyZhang-NY d2078446b4 refactor(release): extract dotenv/snapshot/workflow shared lib 2026-04-16 22:26:11 -04:00
ZacharyZhang-NY 466dac117d perf(api-server): scope agent standup route to caller's tasks and reports
Replace full-company SELECT * + JS filtering with targeted scoped queries:

  - direct reports = agents WHERE reports_to = me
  - my tasks = tasks WHERE assignee_agent_id = me
  - report tasks = tasks WHERE assignee_agent_id IN (...directReports)
  - report status counts = LEFT JOIN tasks GROUP BY report agent
  - approvals scoped to requested_by = me with separate pending /
    resolved queries (resolved capped at 5 in SQL)

JSON response shape unchanged.
2026-04-16 22:22:36 -04:00
ZacharyZhang-NY 94a9e457ce perf(database): replace listSnapshot+JS filter with scoped SQL in getInbox
Replace 17-table snapshot fetch + 6 JS filter passes with 5 scoped
SELECTs targeting only inbox-relevant rows:

  - approvals where state = 'pending'
  - runs where status in ('failed', 'timed_out') and created_at > 24h
  - agents where budget breach > 80%
  - tasks where status in ('blocked', 'in_review')
  - agent_messages with __board__ read-receipt left join filtered to
    company/incident channel or urgent priority

Agent name lookup is a single id->name map built from one scoped agents
read. Return shape unchanged.
2026-04-16 22:21:30 -04:00
ZacharyZhang-NY 19c575933d perf(database): replace listSnapshot+JS filter with scoped SQL in getStandupReport
Replace 17-table snapshot fetch + per-agent JS filters with:

  - scoped agents row read (excluding terminated/pending_approval)
  - tasks GROUP BY assignee_agent_id with conditional sums for
    in_progress / blocked / done-in-24h counts
  - runs GROUP BY agent_id over 24h window with correlated subquery
    for last status, plus max(created_at) for last run timestamp
  - cost_entries GROUP BY agent_id over 24h window for cost
  - activity GROUP BY actor for review-cycle counts
  - single SELECTs for company-level counters (completed tasks, run
    aggregates, blocked tasks, pending approvals, urgent unread, active
    sprint)

Return shape unchanged.
2026-04-16 22:20:09 -04:00
ZacharyZhang-NY 8f7054630b fix(heartbeat): atomic find-or-create + checkout to eliminate duplicate task race
Replace the racy three-step heartbeat path (listAgentTasks ->
findReusableHeartbeatTask -> saveTask -> checkoutTask) with a single
transactional acquireHeartbeatTask method. The prior flow allowed two
concurrent scheduler ticks to both observe "no reusable heartbeat" and
both INSERT a fresh [Heartbeat] task before either CAS-checked out,
producing duplicates that cleanupDuplicateHeartbeatTasks had to mop up.

acquireHeartbeatTask runs inside BEGIN IMMEDIATE so concurrent ticks
serialize on the writer lock. Inside the transaction it first attempts
to claim any existing claimable [Heartbeat] task (todo/backlog/done/
in_review with checkout_run_id IS NULL), reviving done/in_review by
resetting status. If nothing is claimable but an in-flight heartbeat
exists (status NOT IN done/cancelled), it returns null instead of
creating a duplicate. Only when no heartbeat task exists at all does it
INSERT a new one, already in 'in_progress' with checkout_run_id set.

cleanupDuplicateHeartbeatTasks remains as deletion-side defense.
2026-04-16 22:19:42 -04:00
ZacharyZhang-NY 659a2026de perf(database): replace listSnapshot+JS filter with scoped SQL in getCompanyMetrics
Replace 17-table snapshot fetch + 50+ JS filters per agent with five
scoped aggregation queries:

  - tasks GROUP BY assignee_agent_id with conditional sums for done /
    assigned / cancelled counts
  - runs (within cutoff) GROUP BY agent_id with conditional sums for
    succeeded / failed counts
  - runs GROUP BY agent_id over started_at/finished_at for avg duration
    + max(created_at) as last active
  - cost_entries GROUP BY agent_id for total cost
  - activity GROUP BY actor restricted to status-change actions for
    review cycle count

Company-level totals also collapse to single COUNT/SUM queries instead
of in-memory reductions over the full snapshot. Return shape unchanged.
2026-04-16 22:18:52 -04:00
ZacharyZhang-NY 47731f61a0 perf(database): scope listSnapshot table reads to current company
Add explicit LIMIT to all hot-path tables in listSnapshot regardless of
companyId, raise cross-company caps from 500/250 to 2000/1000 so dashboard
no longer truncates silently at 500.

Add migration 28 with composite indexes covering snapshot WHERE/ORDER BY
columns: tasks(company_id, created_at|updated_at), approvals(company_id,
created_at), task_comments(company_id, created_at), documents(company_id,
updated_at), meetings(company_id, scheduled_at), sprints(company_id,
start_date), runs(agent_id, created_at), cost_entries(agent_id,
created_at), activity(company_id, actor, action), tasks(company_id,
assignee_agent_id, status). Indexes match exact ORDER BY direction so
SQLite can satisfy reads via index scan without temp sort.
2026-04-16 22:17:46 -04:00
ZacharyZhang-NY e2253b92fa refactor(renderer): dismantle useAppController 47-prop god-hook
Sections now subscribe directly to the Zustand store via useSectionContext
(shared data derivation) and useAppStore fine-grained selectors. Delete
useAppController, useExecutionController, useOrganizationController, and
useCommunicationController; the three domain forwarders held no logic
beyond re-exporting store slices.

SectionContentView becomes a 50-line layout dispatcher taking only
{section, error, pageTransition}; cross-domain run actions (startRun /
cancelRun) move into App.tsx and are exposed to sections through a new
AppActionsContext alongside refresh / setError / openPathWithFeedback.
App.tsx now calls useUIController + useCompanyController directly and
composes palette + navigation plumbing itself.
2026-04-16 22:12:51 -04:00
ZacharyZhang-NY de77f5f38e refactor(onboarding): replace 50-prop drilling with OnboardingContext
OnboardingStepPanel previously forwarded ~50 props to seven step
components, so adding a single wizard field meant touching four layers.
Introduce a renderer-local React Context (OnboardingProvider /
useOnboarding) that carries all wizard state and actions (connectors,
node status, company inputs, model selection, execution mode, mission,
launch summary, locale, t, error/loading). Step panels now receive only
their navigation callbacks (onContinue / onBack / onLaunch / onFinish)
and read everything else via useOnboarding(). CompanyOnboarding wraps
the step panel tree in OnboardingProvider with a memoised value.

Business logic is unchanged: each onContinueFrom* transition, the
bootstrap call, and the welcome-step branch remain exactly as before;
this refactor only moves the data-passing mechanism.
2026-04-16 22:11:45 -04:00
ZacharyZhang-NY f3816c6115 refactor(ui): unify success/danger/warning color tokens across Toast/StatusPill/InlineNotice 2026-04-16 22:10:43 -04:00
ZacharyZhang-NY a443312f1a refactor(automation): replace ACCENT/LINE constants with design tokens 2026-04-16 22:10:04 -04:00
ZacharyZhang-NY f177e6c877 refactor(settings): use Modal for OAuthPromptDialog 2026-04-16 22:06:59 -04:00
ZacharyZhang-NY 0f1b313272 refactor(execution): split ApprovalInspector 346-line god-component
Decompose monolithic src/renderer/components/execution/ApprovalInspector.tsx
into src/renderer/components/execution/approval-inspector/:
- ApprovalInspector.tsx (168 lines, orchestrator only)
- ApprovalSummary.tsx, ProposedHireCard.tsx, ApprovalDocumentsPanel.tsx
- DeliverableReviewPanel.tsx, SchedulePicker.tsx, DecisionForm.tsx
- TypeBadge.tsx, useApprovalPayload.ts, index.ts (barrel)

Hoist JSON.parse out of JSX IIFE into useApprovalPayload hook with typed
return. Replace roll-your-own role=radio spans with native
input[type=radio]. Extract getTypeBadgeClass switch into TypeBadge
component. Update execution.tsx barrel to re-export from new path.

Business logic preserved: approval decision flow, schedule validation,
deliverable review, document editing, delete confirmation.
2026-04-16 22:06:39 -04:00
ZacharyZhang-NY cc02b45864 refactor(worker): split god-handler into per-message type files
Extract handleWorkerMessage (380 lines, 12 positional args) into dispatcher
+ focused handler modules. Dispatcher now routes by message.type to
run-log / run-status / run-finished handlers, each receiving (event, deps,
context) triple. run-finished is further decomposed into 5 steps:
finalizeRun, accumulateAndRotateSession, emitAndNotifyCompletion,
runPostCompletionAutomation, triggerSelfWake.

No behavior change: all branches, notifications, lease renewals, circuit
breaker, and escalation semantics preserved. Schema validation stays at
dispatcher top level.
2026-04-16 22:04:37 -04:00
ZacharyZhang-NY 69724f0188 refactor(social): use Modal for AddAccountDialog 2026-04-16 22:04:24 -04:00
ZacharyZhang-NY 8541836b26 refactor(automation): use Modal for WorkflowEditor 2026-04-16 22:03:57 -04:00
ZacharyZhang-NY 183f22e922 fix(onboarding): derive step counter from STEP_ORDER
All six step panels previously hard-coded "X / 5" or "X / 6" in their
header captions, which drifted out of sync with the 7-step STEP_ORDER in
OnboardingHero. Export STEP_ORDER / TOTAL_STEPS / getStepNumber from
OnboardingHero and consume them in every step so the counter is always
the single source of truth.
2026-04-16 22:03:57 -04:00
ZacharyZhang-NY 5a5587c2a1 refactor(automation): use Modal for RuleFormModal 2026-04-16 22:03:24 -04:00
ZacharyZhang-NY 8243f58ef7 refactor(automation): use Modal/ConfirmDialog for DeleteConfirmDialog 2026-04-16 22:02:29 -04:00
ZacharyZhang-NY d0c6d1f6d0 perf(db): resolve chain of command in a single recursive CTE to eliminate N+1 queries 2026-04-16 21:59:18 -04:00
ZacharyZhang-NY 2ec77677cb fix(renderer): classify DesktopEvent types to stop full-refresh fallthrough (D-05)
All 14+ non-specially-handled events were pushing into the batcher with
entityKeyFromEvent returning null, which set hasDomainChanged = true and
triggered a full snapshot refresh on every agent-message, run-status, and
heartbeat-status -- defeating the granular patch machinery.

Per-type classification:
- run-status         -> granular run-updated (patchRun by runId)
- heartbeat-status   -> granular agent-updated (patchAgent by agentId)
- agent-message      -> granular task-updated (patchTask refetches comments)
- new-message, thread-wait-state-updated, domain-changed
                     -> full refresh, coalesced via 500ms batcher window
- automation-fired, recovery-needed
                     -> snapshot-irrelevant, skipped at event source

Widen batcher window from 50ms to 500ms so bursts coalesce into one reload.
Granular batches still flush at the same window cadence.
2026-04-16 21:58:32 -04:00
ZacharyZhang-NY 8cf6be05bb fix(skills): enforce per-file and aggregate size caps for imported SKILL.md content 2026-04-16 21:58:32 -04:00
ZacharyZhang-NY c81d72fb9c fix(api-server): SSE stream pulls incremental events via seq cursor
Replace per-poll 5000-row scan + JS filter with indexed WHERE seq > ?
query (uses idx_run_events_run). Batch up to 500 per call, drain loop
on reconnect. Poll interval raised 500ms -> 1000ms.
2026-04-16 21:57:35 -04:00
ZacharyZhang-NY c5c3919c78 fix(skills): bound local skill walk with depth, file count, and symlink loop protection 2026-04-16 21:57:29 -04:00
ZacharyZhang-NY 0123103f58 fix(renderer): subscribe ConsoleView to logRevision to trigger re-render (D-04)
logRevision was bumped on every run-log event but no component subscribed,
so ref mutations to logBufferRef never triggered a re-render -- the console
pre stayed stale until something else forced a render.

Subscribe to logRevision via useAppStore and depend on it in the useMemo
that reads logBufferRef, keeping the ref-based buffer (cheap writes) while
restoring re-renders.
2026-04-16 21:57:08 -04:00
ZacharyZhang-NY 0119872db6 fix(renderer): use snapshot.currentCompanyId as single source of truth (D-03)
CompanySlice's standalone currentCompanyId field + setCurrentCompanyId action
were never written by any caller, so useDesktopEvents.ts read null and fell
through to full refresh for every entity event -- the granular patch
machinery was dead code.

- Remove currentCompanyId field + setCurrentCompanyId from CompanySlice
- refreshBadges reads from snapshot.currentCompanyId
- useDesktopEvents reads companyId from snapshot.currentCompanyId
- Update store unit test to seed snapshot instead of calling removed action
2026-04-16 21:56:40 -04:00
ZacharyZhang-NY a130dc29f5 fix(tasks): route updateTaskSession/getTaskSession through task_sessions table
Stop writing new session data back to the legacy tasks.session_json
column; updateTaskSession now upserts the active task_sessions row
(updating the session pointed to by tasks.active_session_id, creating
one when absent), and getTaskSession reads from the same row so
session data has a single source of truth. Adds migration 27 to
backfill pre-existing tasks.session_json values into task_sessions
idempotently via INSERT OR IGNORE guarded on task_id. The legacy
column is kept for read-only history and is not dropped.
2026-04-16 21:53:12 -04:00
ZacharyZhang-NY ccf0c2a500 fix(schema): remove duplicate-column fallback; make migrations idempotent
Stop silently swallowing duplicate-column errors in the migration
runner; any real migration failure now throws. Replace try/catch-based
column additions in ensureOptionalColumns and migrations 19-22 with
pragma table_info checks so repeated startup is idempotent without
tolerating error signals. Reorder initializeDatabase so runMigrations
runs before ensureOptionalColumns to avoid migration 13 adding columns
that ensureOptionalColumns already created on fresh databases.
2026-04-16 21:52:43 -04:00
ZacharyZhang-NY 5201b6623b fix(database): enable WAL mode and busy_timeout on every DB connection
Applies WAL journal, 5s busy_timeout, synchronous=NORMAL, and
foreign_keys=ON pragmas immediately after opening each connection so
concurrent main+worker writers share the same locking configuration
and avoid SQLITE_BUSY under contention.
2026-04-16 21:52:25 -04:00
ZacharyZhang-NY a6d0a567a8 fix(ipc-credentials): use shared registerHandle and precise redactSecrets (I-02)
Two tightly-coupled hardening changes to the credential IPC module.

(1) Drop the self-built `DesktopOk/DesktopErr/ok/err/handle` helpers
that called `ipcMain.handle` directly, bypassing the trusted-renderer
check in `ipc-support.registerHandle` and the `ZodError ->
IPC_INVALID_PAYLOAD` sanitizer. The module now takes `registerHandle`
and `fail` as deps (wired from `createIpcSupport`) and imports `ok`
from `ipc-core-common`, mirroring the OAuth handler layout.

(2) Tighten `redactSecrets` to issuer-prefixed patterns only (Bearer,
`sk-`, `sk-ant-`, `sk-proj-`, AKIA access key IDs, three-segment JWTs).
The previous blanket `[A-Fa-f0-9]{40,}` and `[A-Za-z0-9+/]{48,}={0,2}`
rules were shredding legitimate diagnostic text — git SHAs, UUIDs,
Fireworks model identifiers like `accounts/fireworks/models/...`, and
any long opaque ID — into `[REDACTED-HEX]` / `[REDACTED-B64]`, which
made provider errors unreadable in the renderer.
2026-04-16 21:49:53 -04:00
ZacharyZhang-NY ff0c01fe87 fix(worker): claim workspace lock in main before enqueue (D-02)
The lock was previously claimed when the worker reported `running`, which
is after `agent.prompt` had already started streaming. Two concurrent
runs targeting the same workspace could both pass the main-side checks,
both get enqueued, and both read half-written state before one of them
lost the lock race and was cancelled mid-flight.

Move the `claimWorkspaceLock` call into `dispatchRunToWorker` so main
coordinates exclusivity authoritatively. If the lock is unavailable,
the run is finished as failed and never enqueued. The worker-side
lease-renewal path is preserved since it is a lease, not a lock.
2026-04-16 21:49:30 -04:00
ZacharyZhang-NY f86b3ddbac fix(ipc-oauth): use shared registerHandle for trust and error sanitization (I-01)
The self-built `ok/err/handle` helpers called `ipcMain.handle` directly,
which meant every OAuth IPC channel skipped the trusted-renderer check
in `ipc-support.registerHandle` and its `ZodError -> IPC_INVALID_PAYLOAD`
sanitizer. Any renderer origin could invoke these channels, and thrown
zod errors would leak the raw validator message.

Take `registerHandle` and `fail` as dependencies so callers wire in the
hardened variants from `createIpcSupport`, drop the local helpers, and
import `ok` from `ipc-core-common`. Zod inputs stay on `safeParse` to
preserve the explicit `INVALID_INPUT` code + message path.
2026-04-16 21:48:47 -04:00