Commit Graph
46 Commits
Author SHA1 Message Date
ZacharyZhang-NY 6da0a58769 refactor(db): group database domain modules 2026-04-22 19:24:25 -04:00
ZacharyZhang-NY 5155136937 fix(api): require api server callbacks 2026-04-22 14:27:49 -04:00
ZacharyZhang-NY 7a1dd647aa fix(api): make task updates transactional 2026-04-22 14:00:24 -04:00
ZacharyZhang-NY 19f8494d09 fix(db): make filesystem lock intent explicit 2026-04-22 13:51:41 -04:00
ZacharyZhang-NY 97033dfabd update 2026-04-21 12:44:33 -04:00
ZacharyZhang-NY d2b6f91023 C4 follow-up #3: RFC 8594 Deprecation/Sunset headers + call counter on legacy hire routes 2026-04-20 14:56:34 -04:00
ZacharyZhang-NY 03864f3bd8 Align team-building SOP to tool-based flow; deprecate HTTP hire path (C4 part 5/5)
Rewrite `ceo-modes/team-building.en.md` to describe the `hire_agent`
and `file_approval` pi tools introduced in C4 parts 1-3, replacing the
pre-C4 Node.js `fetch` snippets that showed the CEO how to POST to
`/api/companies/:id/agents` and `/api/companies/:id/approvals`.

The SOP now:
- Lists `hire_agent` args as a table (name / role / connectorId /
  model + optionals) instead of a JSON body with hard-coded headers.
- Lists `file_approval` args the same way, pointing at
  `approve_ceo_strategy` as the canonical use case.
- Preserves the `STRATEGY_BEFORE_HIRES` / `STRATEGY_ALREADY_FILED`
  ordering gate, surfaced through the tool-layer error codes
  (`file_approval.strategy.before_hires` / `.already_filed`).
- Keeps the goal-creation fetch snippet because goal creation is not
  yet exposed as an internal tool; flagged with a "use `bash` for now"
  hint so the CEO does not conflate the two patterns.

HTTP deprecation:
- `POST /api/companies/:id/agents` carries `@deprecated since C4`
  JSDoc and emits `log.warn('[deprecated][C4] ...')` on every
  successful call so residual legacy callers (UI forms, automation
  tests, unmigrated scripts) are auditable in logs.
- `POST /api/companies/:id/approvals` with `type=approve_ceo_strategy`
  emits the same warn; other approval types still use the HTTP route
  and are not flagged (they have no tool equivalent yet).

Neither route is removed — existing clients keep working. Removal is
gated on tool-path eval coverage landing in a follow-up.

tests: pnpm typecheck + pnpm test:unit (529) + pnpm test:eval (8/8) green
2026-04-20 14:29:05 -04:00
ZacharyZhang-NY 118e3d8795 Remove final AppDatabase pass-through methods with ESLint guard (C1 part 3/3)
Migrate all remaining pass-through call sites (social, skills, budget,
entityCrud, documents, knowledge, automation, wakeup, backup, threads,
settings, snapshot, connectors, workspaces, activity) to their owning
domain modules. `AppDatabase` now carries only three methods:

  - createHireRequest (multi-module composite: agents + approvalsMod)
  - rollbackConfigRevision (multi-module composite: documents + entityCrud)
  - queryAll (raw-SQL escape hatch, @internal)

The vault/env-binding infrastructure previously passed argument-by-
argument into `entityCrud.saveSecret`/`resolveSecretEnv` now binds at
module construction, letting callers invoke `db.entityCrud.saveSecret({...})`
without carrying vault plumbing.

Structural types that narrowed AppDatabase to a bag of flat methods
(`ConnectorModelPolicyDeps` callers, `CapabilityMatchDb`, test mocks in
budget-enforcement / budget-incidents / task-quality-gates /
failure-escalation / task-quality-gates-sweep) are reshaped to the new
namespace. `connectorPolicyDepsFromDb` replaces the old pattern of
handing the whole AppDatabase to `validateConnectorModelSelection`.

A new ESLint rule in eslint.config.mjs blocks new top-level methods on
AppDatabase so the pass-through layer cannot silently grow back.
`AppDatabase.ts` dropped from 193 lines to 58; `AppDatabaseBase` lost
the 270-line trailing pass-through block.
2026-04-20 13:31:39 -04:00
ZacharyZhang-NY a69fbb053b Codemod db pass-through calls: messages + runs + approvals + company (C1 part 2/3)
Rewrite ~376 call sites for messages, runs, approvalsMod, and company
domains and delete the corresponding pass-through methods on
AppDatabase / AppDatabaseBase. Structural type references
(ReturnType<HandlerDeps["db"]["getRun"]>) and SweepStaleReviewsDeps-style
mocks are updated to route through the owning sub-module.
2026-04-20 13:20:15 -04:00
ZacharyZhang-NY 5e9b7acbc6 Codemod db pass-through calls to domain modules: tasks + agents (C1 part 1/3)
Rewrite ~525 call sites from `db.<method>()` to `db.<module>.<method>()`
for tasks and agents domains (including `getAgent`/`findAgent`/`getTask`
which live on the `entityCrud` field). Delete the corresponding
pass-through methods on AppDatabaseBase. Structural types that narrowed
`db` to a bag of flat methods (CapabilityMatchDb, EscalationDeps,
SweepStaleReviewsDeps) are reshaped to match the new namespace. Internal
callback references inside database.ts/database-base.ts now invoke the
owning module directly (e.g. `this.agents.saveAgent`) rather than the
deleted pass-through.

The codemod lives at scripts/codemod/remove-appdatabase-passthrough.mjs
and filters by owning module + optional explicit method list. It is
idempotent and does not re-rewrite already-namespaced calls.
2026-04-20 13:17:01 -04:00
ZacharyZhang-NY 557090a6a8 Move message-*.ts into messaging/ subdir (N10)
Consolidate the two runtime messaging modules under src/main/messaging/. Storage stays in database/messages.ts — that is the SQL layer and belongs with the other DB modules; messaging/ covers the dispatch/normalize runtime around it.

- message-dispatch.ts -> messaging/dispatch.ts
- message-normalize.ts -> messaging/normalize.ts
- add messaging/index.ts barrel for future consumers (existing call sites import the concrete module path directly)
- 10 call sites updated across src/main and tests
2026-04-20 12:29:15 -04:00
ZacharyZhang-NY 2e60ecc0e0 Distinguish top-level review wakes from subtask completion
Top-level tasks submitted to in_review fired the same
subtask_completed wake as the subtree leaves, so the reviewer's
prompt framed the work as "a subtask you delegated". Add a dedicated
task_review_requested wake reason with a reviewer-specific title and
step list, and route the parent-less branch of the task PATCH handler
through it.
2026-04-19 19:26:05 -04:00
ZacharyZhang-NY dd8485b7e2 Surface the CEO self-review guard as a proper 422
The database layer already refuses to advance a top-level assignee's
task to in_review without requires_user_review=1 — the right
invariant. But the HTTP route caught nothing specific and returned a
bare 500, so the CEO prompt had to carry a paragraph of "the system
will demote any such task back to todo, but the cycle wastes a
heartbeat slot" prose to keep the model away from the error path.

Translate the guard to a 422 NO_REVIEWER_ON_TOP_LEVEL_TASK response
with an actionable hint and rewrite the prompt section to describe
the contract instead of begging the model to avoid the failure.
Also drop the residual skill-looping paragraph now that hires get
their default kit on approval.
2026-04-19 19:23:37 -04:00
ZacharyZhang-NY 0443b4a197 Block approve_ceo_strategy until hires exist
The CEO prompt carried a plea — "do not submit strategy before hires,
we have observed a loop" — in place of an actual gate. The model
could (and did) ignore it, land back in team_building mode, and
resubmit. Move the invariant into the /approvals POST handler:
reject approve_ceo_strategy with 409 STRATEGY_BEFORE_HIRES when no
hire_agent approval exists for the company, and 409 STRATEGY_ALREADY_FILED
when one is already pending or approved.

Introduce listCompanyApprovalsByType on the database facade to keep
the route stateless. Trim the prompt to describe the gate instead of
begging the model to obey.
2026-04-19 19:22:34 -04:00
ZacharyZhang-NY 9c65291973 Keep document bodies out of the cacheable system prompt
buildDocumentsSection was inlining up to 5 document bodies at 2000
chars each into the system prompt. Any edit to any of the listed
documents between wakes invalidated the Anthropic prompt cache — the
prefix that buildTaskPrompt spent real effort to keep stable.

Emit metadata only (id, type, title, author, status, version,
updatedAt) and teach agents to GET the single-document endpoint when
they actually need the body. The single-doc route did not exist, so
add it; update the API doc block in the system prompt accordingly.
2026-04-19 19:14:14 -04:00
ZacharyZhang-NY d51682b06d Surface the agent's own outputs in standup
The standup briefing knew about tasks, approvals, comments, and
report statuses but had no view of what the agent itself recently
produced. Without that view the model could narrate "I wrote the
PRD" in its assistant text without ever calling POST /documents,
and we'd have no automated way to catch the drift — the user just
saw an empty document page and a confused agent.

Standup now returns:

  - `recentDocuments` — documents the agent authored (capped at 20),
    with id / type / title / status / version / updatedAt;
  - `recentWorkProducts` — filesystem artifacts produced on tasks
    the agent owns (capped at 20), with title / externalId / url /
    status / task title / updatedAt.

The CEO operating prompt was already updated in the previous
commit to reference these fields and re-do a missing POST when a
task claims completion but the persistence is absent. The trust-
but-verify loop is now closed: the agent can compare its own
narration against its own actual outputs every wake.
2026-04-19 18:38:19 -04:00
ZacharyZhang-NY 4eaad1f91a Surface unread inbox in the standup briefing
Pre-fix the agent had to make a second `?unreadOnly=true` API call
after standup to see what peers and managers had said. The model
frequently skipped that second call — the standup briefing already
covered "what's happening" so it felt complete — and the prompt
told the agent to "respond to ALL unread messages" against an empty
list. Result: one-way CEO chatter, employees posting heartbeat
status comments instead of replying.

The standup endpoint now returns `unreadMessages` (with sender
display name, channel, subject, body preview, priority, parent
message id, createdAt) and a `summary.unreadMessages` count. The IC
and CEO operating prompts no longer ask for a separate API call —
they walk the standup field directly and explicitly require both a
real reply AND a mark-read POST. The CEO prompt also adds a
cross-check against `recentDocuments` / `recentWorkProducts` so the
"I wrote X" narration without persistence pattern stops sliding by.
2026-04-19 18:36:50 -04:00
ZacharyZhang-NY d9b35d73ab Pin work_products identity to (company, workspace, path)
The natural key used to be (companyId, provider, externalId), so two
workspaces with the same workspace-relative path collided onto one
row. Provenance was also frozen at the first write: `created_by_run_id`
never refreshed, so a run that edited a pre-existing file never
qualified as a deliverable and the DocumentCenter "open" link could
still jump into the first workspace after a repo move.

Migration v45 adds `workspace_id` + `last_touched_by_run_id`, backfills
`workspace_id` via the task/run join, seeds `last_touched_by_run_id =
created_by_run_id`, and installs a partial unique index on
`(company_id, workspace_id, provider, external_id)` scoped to rows
that have a workspace. The artifact projector now records
`workspace_id` on insert and refreshes `last_touched_by_run_id` on
every upsert. The deliverable signal query reads either provenance
column so a run that only edited a pre-existing file still counts.
`resolveWorkProductTarget` prefers the stored `workspace_id` and falls
back to the task/run join for legacy NULL rows.
2026-04-19 15:43:48 -04:00
ZacharyZhang-NY 686140ae55 Route every document save through one lifecycle pipeline
Desktop IPC saves used to fire only automation and the reviewer wake,
while HTTP API saves fired automation, thread integration, checkpoint
engine, deliverable-review approval, and the reviewer wake. Two
divergent paths produced two different lifecycle states for the same
document, and a future fix to either branch would miss the other.

Introduce `document-lifecycle.ts` as the single owner of document
creation side effects: save, derive task binding from the originating
run, fan out to automation/thread/checkpoint, wake the reviewer, then
raise a deliverable-review approval when the document qualifies. The
HTTP route and the IPC handler both call the pipeline — the API
server's `onDocumentCreated` callback is replaced with a
`createDocument` hook that delegates to the pipeline.

Wire `checkpointEngine` through `bootstrap-application` →
`main-runtime` → `main-runtime-support` so the IPC handler bag carries
both thread integration and the checkpoint engine required by the
pipeline. The document-run-provenance integration test harness is
updated to pass a `createDocument` implementation backed by the same
pipeline the production path uses.
2026-04-19 15:13:53 -04:00
ZacharyZhang-NY 1a0fdc6f31 Collapse the GET /api/agents/me/standup duplicate to one module
Two registrations of the same route lived in the codebase: one inline
in routes-agents.ts that filtered three full company-wide SELECTs in
memory, and the dedicated routes-agents-standup.ts that uses targeted
standup-shaped DB queries. The router keeps the first match so the
inline copy was serving every request and the focused module was dead
code — any future fix to standup would have silently missed
production.

Remove the inline standup handler and its three companion helpers
(`listAgentsForStandup`, `listTasksForStandup`, `listApprovalsForStandup`)
plus the unused `AgentStandupRow` type. The dedicated standup module is
now the single source of truth.
2026-04-19 15:01:41 -04:00
ZacharyZhang-NY 5ee3d1394a feat(messaging): route messages to live runs via pi steer
Incoming messages (human → agent, agent → agent, channel broadcast)
persisted to the inbox and were only projected into the recipient's
transcript on the next `prepareRun`. When the recipient was already
running, every message waited for the next wake — a Slack-like
system operating at heartbeat cadence, not in real time.

`RuntimeMessageInjector` closes the gap: it looks up the recipient's
active run through `RuntimeBridge.findActiveRunForAgent`, projects
the persisted `AgentMessageRecord` via the existing inbox-projection
helper, and posts a `steer-run` ParentMessage into the worker. The
worker's runner enqueues the projection on the Agent's steering
queue; pi drains it at the next turn boundary and emits the usual
`message_end` → `persistMessage` sequence, so the session store
shadow is preserved without double-writing.

`dispatchAgentMessage` gains an optional `injectRuntimeMessage` hook
used by every API-side dispatch. Injection succeeds → we mark the
inbox row delivered and skip `wakeAgentIfPossible`. Injection fails
(no active run) → the legacy inbox + wake path still fires, so
sleeping agents continue to receive their messages on the next turn.

The injector is owned by the service graph alongside `workerService`
and `piRuntimeRef`, which gives it both the worker postMessage
channel and the RuntimeBridge active-run map without additional
coupling. Internal dispatch call sites (sweepStaleReviews,
automation actions) leave the hook unset; those system messages
target idle agents by design and do not need the runtime path.
2026-04-19 00:58:39 -04:00
ZacharyZhang-NY f041336721 refactor: nuke dead exports, dead files, dead DB column, dead zero-value wrappers
Full-codebase ts-prune + manual audit sweep. Every change below is a pure
deletion (or a 2-line re-wire where a dead function was supposed to be
alive). No scope additions.

Dead wrappers / phantom features
  - shouldAutoApproveApproval: zero-value wrapper over shouldAutoApprove.
    Four call sites now import shouldAutoApprove from execution-policy
    directly.
  - evaluateRotation(session, connectorId, thresholdsOverride): the
    connectorId arg was never read (underscored), and thresholdsOverride
    was only exercised by the test suite — no production caller. Signature
    collapses to evaluateRotation(session). DEFAULT_THRESHOLDS /
    RotationThresholds exports and their tests go with it.
  - installLogRedactor: defensive console wrapper that was never wired
    from worker-entry. buildRedactor handles the known threat (env-value
    leaks); the pattern-based redactTokens stays for tests and future
    composition.

Dead files
  - resume-interrupted-tasks.ts: helper function never imported.
  - service-graph-orchestration-deps.ts: three factory closures
    superseded by the inline closures in create-orchestration-service.

Dead exports
  - listConnectorsByAuthKind (shared/connector-catalog)
  - getLocalePromptInstruction + whole localization.ts file
  - ImportantDocType
  - main-side ConnectorAuthKind (duplicated the shared one; not referenced)
  - CheckpointEngineService, RunHousekeepingService types
  - WorkspaceResolution type
  - cmdOrCtrlSymbol
  - AgentEventPush, CredentialPayload type aliases
  - parseApprovalPayloadJson, parseEnvValue, ProviderCard
  - getSOP — buildSOPSection reads the SOPS map directly; the individual
    getter had no callers. Deduped to a flat array.
  - renderWakeReason in wake-reasons/en.ts: byte-for-byte duplicate of the
    one in wake-reasons/index.ts.

Forgotten wiring (not cleanup, but adjacent)
  - registerAgentStandupRoute: the GET /api/agents/me/standup endpoint is
    advertised in every system prompt and wake-reason doc, but the route
    handler was never registered in api-server/index.ts. Agents have been
    hitting 404 since Pi Mono. Registered.

Dead DB column
  - v44 migration drops runs.context_snapshot_json. Column was added by
    v16-v25, never written or read, and v43's deliberate preservation
    was an interim measure. Same idempotent table-rebuild pattern as
    v41/v43.

Test tree
  - Phantom-feature threshold override tests removed (3). The rest of
    the rotation suite re-expressed against the real maxRuns=20 floor.

495/495 unit tests pass. typecheck + build clean.
2026-04-18 23:37:37 -04:00
ZacharyZhang-NY 7e1d4199ee refactor(schema): make runs.task_id nullable, delete heartbeat workaround
The NOT NULL constraint forced heartbeat runs to synthesize a fake task
row (`[Heartbeat] X — Check for work`) so the FK could resolve. Three
layers of code — acquireHeartbeatTask, findReusableHeartbeatTask,
cleanupDuplicateHeartbeatTasks, plus the is_heartbeat flag column + UI
filter — existed to paper over one bad constraint. Remove the
constraint and the whole workaround vanishes.

SQLite table rebuild with data preservation. Live production DB, no
backup — migration is atomic, idempotent, and verified before DROP.

## Migration v35 (rebuild runs with nullable task_id)

Uses `fn:` form so it can:
- Snapshot `COUNT(*) FROM runs` before copy
- Walk `PRAGMA table_info(runs)` to detect drift columns (v34 declared
  a column set; any additive ALTER since v14 shows up here)
- Assert `COUNT(*) FROM runs_new == beforeCount` before `DROP TABLE runs`
- `PRAGMA defer_foreign_keys = ON` so run_events(run_id) FK defers to
  commit time, by which runs has the same id set under the new name
- Recreate all 6 indexes on the new table

If the assertion fails, the migration throws inside the existing
`transaction()` wrapper. SQLite rolls back the whole migration, old
`runs` table is untouched, `schema_migrations` never records v35. Next
app start retries cleanly.

## Migration v36 (drop tasks.is_heartbeat + synthetic rows)

Runs now carry `task_id = NULL` for heartbeat dispatches. The
`is_heartbeat` flag is dead data going forward.

1. `UPDATE runs SET task_id = NULL WHERE task_id IN (SELECT id FROM tasks WHERE is_heartbeat = 1)`
2. `DELETE FROM tasks WHERE is_heartbeat = 1` (synthetic rows, not real work)
3. Rebuild `tasks` without the `is_heartbeat` column (same defer-FK +
   row-count-assert pattern as v35)

## Code cleanup

- `RunRecord.taskId: string | null`; `createRun` / `cancelRunForDeletion`
  / `finishRun` guard every `UPDATE tasks` path with a null-check.
- `TaskRecord.isHeartbeat` removed; every SELECT / filter / UI reference
  dropped.
- Delete `acquireHeartbeatTask` / `findReusableHeartbeatTask` /
  `cleanupDuplicateHeartbeatTasks` — dead after the constraint lifts.
- Delete `src/main/orchestration/heartbeat-task.ts` (pure workaround
  constant file).
- `heartbeat.ts` creates runs with `taskId: null` when the agent has no
  assigned work. Turn context already supports nullable task
  (heartbeatFallbackSection); prompt surface unchanged.
- `validateTaskCreation` drops the `isHeartbeat` parameter — user-
  authored + automation-authored tasks are never heartbeat.
- Renderer: `run.taskId ? taskMap.get(...) : undefined` in Dashboard /
  RunCenter / RunDetail / RunQueue.
- Delete `tests/unit/heartbeat-coalescing.test.ts` (12 tests that
  exercised the deleted DB methods).
- Drop 1 isHeartbeat-bypass test from `task-quality-gates.test.ts`.

## Grep verification

- `isHeartbeatTaskTitle src/` → 0
- `acquireHeartbeatTask src/` → migration comment only
- `is_heartbeat src/` → schema.ts migration history only
- `isHeartbeat src/` → 0

Tests: 578/578 (was 591 — delta is 13 tests that tested deleted code).
2026-04-17 16:16:08 -04:00
ZacharyZhang-NY 40040b3dd2 refactor(errors): surface 57 deferred catch-and-swallow sites via logger
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.
2026-04-17 15:25:13 -04:00
ZacharyZhang-NY 5547c29491 update 2026-04-17 14:03:56 -04:00
ZacharyZhang-NY 1adfea55b0 chore(meetings): log skipped invalid action items; use requireParam for meetingId (P2-42, P3-48) 2026-04-16 23:08:06 -04:00
ZacharyZhang-NY c47050964a refactor: dedupe heartbeat-title check; JOIN thread visibility; typed skill service guard (P2-43, P2-44, P2-45) 2026-04-16 23:07:01 -04:00
ZacharyZhang-NY 3bdc8b4be1 refactor(api): extract authenticateRequest helper from handleRequest (P1-35) 2026-04-16 23:04:12 -04:00
ZacharyZhang-NY 2df921ca88 refactor(agents): share leadership role detector, use word-boundary match
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.
2026-04-16 22:57:35 -04:00
ZacharyZhang-NY 43a03aa5e9 refactor(db): collapse three markRead variants into markMessageReadForReader (P1-16) 2026-04-16 22:50:40 -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 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 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 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 f803b0b826 fix(api-server): require Origin header when no bearer token
Origin-less requests previously skipped the allowlist entirely, meaning
any process that omitted the Origin header (trivial for a non-browser
attacker) could reach the auth layer. Browsers always send Origin, so
rejecting Origin-less browser-style requests closes the bypass without
breaking agent CLIs, which authenticate with a bearer token instead.

New policy:
 - Requests with a Bearer Authorization header: Origin optional; if
   present it must be in the allowlist.
 - All other requests (including CORS preflight): Origin required and
   must be in the allowlist.
2026-04-16 21:42:04 -04:00
ZacharyZhang-NY 1b578c111a update 2026-04-16 14:48:51 -04:00
ZacharyZhang-NY 9145572422 update 2026-04-05 20:14:48 -04:00
ZacharyZhang-NY b8dda3c8d0 update 2026-04-04 17:01:26 -04:00
ZacharyZhang-NY 67c757f481 update 2026-04-04 12:03:50 -04:00