Commit Graph
11 Commits
Author SHA1 Message Date
ZacharyZhang-NY 6da0a58769 refactor(db): group database domain modules 2026-04-22 19:24:25 -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 af72f1b4d9 Make @mentions in a DM redirect the turn (group-chat semantics)
The operator's mental model: a chat is a room, @X means "X takes the
turn", unmentioned participants keep working. Two separate gaps were
letting 张工程师 answer when the user wrote "@CEO 更新一下进度" inside
their Board↔张工程师 chat:

* `resolveMessageWakeTargets` always woke the DM recipient, regardless
  of who the body @-mentioned. CEO woke too, but 张工程师 — the
  addressed party — replied first and CEO's busy-queued wake arrived
  after the operator had already taken the earlier reply as "the
  answer". Explicit mentions now shadow the implicit DM recipient:
  with mentions, only the named agents wake; the recipient still sees
  the message in their inbox, they just aren't obliged to answer.
  No mentions → fall back to the historical "DM wakes the recipient"
  contract so every existing flow is untouched.

* `channel-utils` keyed each chat by `sort(fromAgent, toAgent)`. CEO's
  reply with `toAgentId=__board__, parentMessageId=<original>` landed
  in a fresh Board↔CEO pane — the exact "new session" the operator
  complained about. Walk `parentMessageId` back to the root of the
  reply chain and use the root's participant pair: every reply in a
  thread now collapses into the chat the sender originally opened,
  cycle-guarded against malformed parent pointers.

Pinned by a new unit test: DM to CTO with "@CEO 更新一下进度。" wakes
only CEO while keeping the message visible to both CTO's and CEO's
inbox.
2026-04-20 08:59:14 -04:00
ZacharyZhang-NY 7833ded4a1 Make @mentions visible to the agents they name
A DM addressed to one agent that @-mentions another was silently
dropping the second agent out of the conversation:

* `dispatchAgentMessage` added the mentioned agent to the wake set,
  so CEO's heartbeat fired — but
* `listAgentInboxUnread` only surfaced messages where the current
  agent was `to_agent_id` or sat in a matching channel/project/
  department. Mentions were invisible, so CEO's next turn saw nothing
  new and returned silently. From the operator's chair this looked
  exactly like "CEO ignored me; 张工程师 answered instead".
* `findMentionedAgentIds` had a latent bug on top: its unique-alias
  filter used `ids.length === 1`, but a single agent whose name
  matched its role registered itself twice, tripped the filter, and
  every `@CEO` in a scaffold with role === name was a no-op. Once
  inbox visibility was fixed the bug became load-bearing.

Root-cause fixes:

* New migration v48 adds `agent_message_mentions(message_id, agent_id,
  company_id, created_at)` so mention fan-out is a first-class join.
* `sendAgentMessage` now takes the resolved mention set and writes
  the row + mentions in a single transaction — a crash between the
  two writes can no longer leave a message visible to the recipient
  but invisible to the mentioned agent.
* `dispatchAgentMessage` computes the mention set exactly once via
  `resolveMentionedAgentIds` and hands the same list to both the DB
  write and `resolveMessageWakeTargets`, so persistence and wake
  stay in lockstep.
* `listAgentInboxUnread` and `listAgentMessages` gain an `id IN
  (select message_id from agent_message_mentions …)` clause so the
  mentioned agent's inbox actually contains the message that woke it.
* `findMentionedAgentIds` now stores per-alias agent IDs in a `Set`
  and filters on `ids.size === 1`, dropping the self-duplication
  bug while preserving the "refuse ambiguous aliases" semantics.
* Company teardown drops `agent_message_mentions` alongside the
  existing `agent_message_reads` cleanup so deletes stay clean.

Tests pin the new behaviour: a DM with a `@CEO` mention reaches
CEO's inbox and both agents get woken, while a DM whose recipient
is `__board__` never attempts to wake the human sentinel.
2026-04-20 08:21:43 -04:00
ZacharyZhang-NY d47e637a45 fix(messages): company channel fans out to every active agent
"Company" channel fell through to `listTopLevelAgentIds`, so a
broadcast on the most ambient-sounding channel in the product only
reached agents with `reports_to IS NULL`. The label said company;
the semantics said board-only. The rest of the org never knew a
message existed.

Add `listCompanyBroadcastAgentIds` — returns every non-terminated,
non-paused, non-pending agent for the company — and wire it through
the `company` case in `resolveMessageWakeTargets`. Board-only
fan-outs are still reachable: the `incident` channel keeps the
broader pingable set (paused-by-budget included) and direct DMs to
top-level agents work as before.

The prompt doc catches up to the runtime: company is explicitly
framed as all-hands, with board-only guidance redirected to DMs or
incidents. The message-dispatch unit test asserts the new fan-out.
2026-04-19 01:21:26 -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 e94939a562 test(connector-id): migrate fixtures and assertions to pi-mono ids
Replaces legacy adapter ids (codex_local / claude_local / gemini_local /
claude_compatible) across ~42 test files with pi-mono equivalents:
- codex_local → openai-codex
- claude_local → claude-pro
- gemini_local → gemini-cli
- claude_compatible → anthropic

Hand-rewrites where fixture semantics changed:
- connector-matching: TASK_TYPE_PREFERENCE ordering rebased (claude-pro
  first for code/content/management; google first for research)
- connector-model-policy: getModels(provider) from pi-ai instead of the
  deleted adapter.module.models array
- session-management + edge-case-session: adapter exemption removed;
  rotation now uniform under pi-mono
- ipc-core-admin-handlers: CSV compat model replaced with pi-ai anthropic
- connectors.spec: lookup by openai-codex id (catalog now starts with claude-pro)
- onboarding-model-pool: listProviderModels(provider) helper; label text
  changed to "ChatGPT Plus/Pro (Codex)"
- database integration: added vault insecure-fallback env + electron mock
- command-paths: uses new @main/connector-helpers normalizeCommandValue
- pi-mono-migrations: added `as string` casts for 3 negative-existence
  assertions verifying legacy ids were removed

Drops:
- pnpm-workspace.yaml packages: [] (was "packages/*"; directory empty)
- obsolete CONNECTOR_STRENGTHS import; .module.models probing

Verification:
- pnpm typecheck → 0 errors
- pnpm lint → 0 errors / 0 warnings
- pnpm build:verify → 0 errors / 0 warnings
- pnpm test:unit → 508/508 pass
- pnpm test:integration → 96/96 pass
- All 6 adapter hard gates pass (NOT FOUND / 0 matches / empty)
- pi-ai / pi-agent-core / pi-coding-agent all resolved @0.67.6
2026-04-17 02:36:53 -04:00
ZacharyZhang-NY 67c757f481 update 2026-04-04 12:03:50 -04:00