Commit Graph
650 Commits
Author SHA1 Message Date
ZacharyZhang-NY 69d03312e6 Cleanup residual dead code and stale annotations (N-scan residual)
- Drop the "pass-through remains for back-compat" note on
  AppDatabaseBase's domain modules; the pass-through layer is gone, so
  the comment was out of date. Replace with a pointer to the ESLint
  guard that blocks regression.

- Flag the two remaining >500-line files (`agent-runtime/runtime-bridge.ts`
  648 LOC, `agent-runtime/artifact-projector.ts` 637 LOC) with an
  inline `TODO: decompose — M-followup` note. No structural change;
  the decomposition is tracked for a later milestone to keep this pass
  scope-safe.
2026-04-20 13:39:56 -04:00
ZacharyZhang-NY 827f06f358 Migrate 26 high-value catch sites to AppError taxonomy (C6 wave 1)
Classify scheduler, orchestration, IPC read, and automation catch
blocks via logAppError + classifyUnknownError so each failure surfaces
a stable `module.subdomain.condition` code plus a kind. No behaviour
change — returns, throws, and fall-through are preserved; only log
prefixes and the taxonomy metadata are new.

Wave 1 coverage (26 sites, all non-database):

  - orchestration/scheduler-lock-recovery (3)
  - orchestration/scheduler-housekeeping (5)
  - orchestration/scheduler-idle-automation (2)
  - orchestration/scheduler-heartbeat (4)
  - orchestration/turn-context (2)
  - ipc-core-read-handlers (6)
  - automation/automation-handlers (4)

Relax the Logger interface so `error` is optional, letting callers
whose deps expose only `{info, warn}` (scheduler contexts) pass in
directly without wrapping. The helper falls back to warn when error
is absent.
2026-04-20 13:37:40 -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 411294afa6 Lint rule: block new UPDATE tasks SET status outside advanceTaskPhase (B2 Phase 1 step 5/5) 2026-04-20 13:00:53 -04:00
ZacharyZhang-NY ab4afba9c7 Add phase/status consistency check observability (B2 Phase 1 step 4/5) 2026-04-20 12:57:24 -04:00
ZacharyZhang-NY 2656684392 Dual-write phase and lease in task status transitions (B2 Phase 1 step 3/5) 2026-04-20 12:55:53 -04:00
ZacharyZhang-NY 8cb227f455 Add advanceTaskPhase and task lease helpers with tests (B2 Phase 1 step 2/5) 2026-04-20 12:53:28 -04:00
ZacharyZhang-NY b9040adad5 Add phase column and task_leases table with backfill (B2 Phase 1 step 1/5) 2026-04-20 12:53:20 -04:00
ZacharyZhang-NY 92e6eff5da Introduce pure resolveCeoMode function with unit tests (M7 phase 1) 2026-04-20 12:48:33 -04:00
ZacharyZhang-NY da517b6e12 Aggregate agent-runtime hooks into AgentHooks factory (M10) 2026-04-20 12:46:20 -04:00
ZacharyZhang-NY e530849b0f Review iteration: warn on gitSha read failure, WAL invariant comment, C6.2 wave-3 coordination gate
- backup.ts: log.warn when `git rev-parse HEAD` fails so sandbox/CI runs
  with no git show diagnosably instead of silently emitting gitSha:null
  (Karpathy review ask).
- backup.ts: expand comment on VACUUM INTO to pin the WAL-mode invariant
  — future readers must not "helpfully" add a wal_checkpoint call
  (Karpathy review ask).
- C6.2 proposal: add hard coordination gate before wave 3 because
  commit 0db6e42 already demonstrated the race hazard when two agents
  touch scripts/ / connector-definitions/ in the same window
  (Linus review ask).
2026-04-20 12:33:45 -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 ec0c81b4b3 Harden record-fixture CLI to emit real LLM trace JSONL (B5 step 2)
Replaces the stub recorder with a real-provider CLI. The command now:

  1. Reads system prompt + user message from files on disk (empty files
     are rejected — a fixture with no prompt captures no behaviour).
  2. Resolves the provider spec from `--provider anthropic|openai|google`
     and pulls the API key from the matching environment variable.
     Missing env → hard error with a copy-pasteable export hint; no
     fallback, no mock.
  3. Calls `streamSimple` from @mariozechner/pi-ai with the user-picked
     model. On provider error (stopReason=error|aborted) the CLI exits
     non-zero and writes no JSONL. Users re-run if they think the
     failure was transient.
  4. Serialises one user turn + one assistant turn + an empty
     assertions block into the JSONL shape documented in
     tests/eval/README.md. Token ledger comes straight from the
     assistant message's usage field (renamed to snake_case for
     provider-neutral on-disk format).

Scope is deliberately single-turn: tool use / multi-turn agent loops
are B5.1's job. Importing the AgentCompany worker's system prompt
builder would require the Electron runtime; the CLI stays node-only
so it can run standalone.

`--help` works and prints the argument matrix. All failure modes exit
non-zero with a specific, actionable message.

Also document the new recording workflow in tests/eval/README.md —
four-step procedure, example scenario files, assertion templates, and
explicit list of what the recorder still does not do.
2026-04-20 12:28:23 -04:00
ZacharyZhang-NY 1c7bc71931 Wire connector-definitions/ subdir with index barrel (N9)
32dfa89 renamed the three catalog data files into src/main/connector-definitions/ but left src/main/connector-definitions.ts pointing at the old connector-definitions-data-* paths, which no longer resolve. Fix the barrel to import from ./connector-definitions/index and correct each moved file's ConnectorDefinition import to walk up one level (../connector-definitions).
2026-04-20 12:26:36 -04:00
ZacharyZhang-NY 32dfa89b7e Add C6.2 proposal for remaining 228 catch site migrations
Partitions the remaining catch sites into three waves (worker +
orchestration, IPC surfaces, database + credentials) so each wave is
one coherent review and can land in parallel with the B2 migration
precondition work that depends on classified DB catches.

Non-goals are explicit: no observable behaviour change, no renderer-
side coverage, no Result<T, AppError> envelope. The proposal exists
so the error taxonomy reaches the 95%+ coverage a lint rule needs.
2026-04-20 12:24:50 -04:00
ZacharyZhang-NY 68ec54ca0c Trim CLAUDE.md lessons.md reference; N5 already satisfied (N6)
lessons.md does not exist in this repo and is user-private in other workspaces. CLAUDE.md's self-evolution bullet now points at the project-level AGENTS.md, which is actually checked in and maintained. Build-artifact gitignore coverage (out/, build/, dist/, boss-build/, local-release/) was already in place — no change needed for N5.
2026-04-20 12:24:14 -04:00
ZacharyZhang-NY 0db6e4261f Migrate top-20 high-frequency catch sites to AppError taxonomy (C6.1 part 2/2)
Each of these catches already logged via an accessible `logger` or `log`.
The migration wraps the thrown value through `classifyUnknownError(raw,
fallback)` and emits the log line with the structured `[code]` prefix.
Observable behaviour — return values, fail strings, DB writes — is
unchanged; the only addition is a machine-readable `code` on every log
line so future telemetry can bucket by category.

Sites migrated (11 files, 20 catch sites):

  src/main/browser-action-service.ts             (2) approval lookup, payload JSON
  src/main/ipc-credentials-handlers.ts           (4) save, remove, test lookup, test resolve
  src/main/hire-hook.ts                          (3) getAgent, connector def, outer exception
  src/main/checkpoint-engine.ts                  (2) onTaskStarted, onDocumentDrafted
  src/main/document-lifecycle.ts                 (1) getRun while deriving taskId
  src/main/skills/runtime.ts                     (2) builtin statSync, builtin readdir
  src/main/worker/handlers/run-status-handler.ts (1) run not found during status
  src/main/worker/handlers/run-log-handler.ts    (1) lease renewal lookup
  src/main/notification-service.ts               (1) notification.close during destroy
  src/main/updater-service.ts                    (1) autoUpdater.checkForUpdates
  src/main/ipc-support.ts                        (2) ipc handler throw, emitEvent send

Why only 20 tonight: 248 catches exist in src/main/. Rewriting them all
in one commit is high-diff-risk (collides with Linus's in-flight
database/backup/migration work on database/*, connector-definitions-*,
message-* and scripts/*) and low-value (pure-library catches with no
accessible logger produce dead `void classified` constructs). The
taxonomy module is committed separately so the remaining 228 sites can
land incrementally via the C6.2 proposal.

All 11 files selected sit outside Linus's working set. Verified:
  - pnpm typecheck   clean
  - pnpm test:unit   498 passed
2026-04-20 12:22:52 -04:00
ZacharyZhang-NY d7e267031f Drop unused agent_messages.read_at column via migration v49 (M1)
The `agent_messages.read_at` column has had no production writer since
v12 moved read tracking into the per-viewer `agent_message_reads`
receipts table. It survived anyway, carrying a NULL through every SELECT
list and begging the next maintainer to accidentally read stale data
from it.

- v49 migration: `ALTER TABLE agent_messages DROP COLUMN read_at`
  (idempotent: skips if absent).
- messages.ts: SELECTs no longer fetch the column; per-viewer `readAt`
  is computed from `agent_message_reads` as before, with `null` for
  call-sites that have no viewer context (getAgentMessage /
  searchMessages / listAgentInboxUnread).
- runtime-bridge.ts / runtime-message-injector.ts: comments referencing
  the dropped column updated to name the read-receipts table instead.
2026-04-20 12:20:32 -04:00
ZacharyZhang-NY dade5eb333 Wire pre-migration auto-backup with named API and manifest (B2/backup precursor)
Before running any pending migration, take a synchronous snapshot of the
profile to backups/pre-migration-v<target>-<ts>/ and write a
backup-manifest.json recording reason, schema version, app version, and
git SHA. A failed migration now has a well-labelled recovery point
instead of a half-upgraded profile.

- createAutomaticBackup(reason): synchronous snapshot via VACUUM INTO +
  recursive cpSync of logs/artifacts, plus JSON manifest. No fallback —
  failure throws so the caller refuses to touch the schema.
- runMigrations accepts createAutomaticBackup and invokes it when any
  migration is pending. No optional chaining; callers must supply it.
- AppDatabase constructor takes appVersion so main-runtime stamps the
  manifest with app.getVersion().
2026-04-20 12:16:39 -04:00
ZacharyZhang-NY 08427931b6 Introduce AppError taxonomy with 4 kinds (C6.1 part 1/2)
Establishes `src/main/errors/` with:

- `AppError` base + `Recoverable|UserInput|Upstream|Fatal` subclasses
  keyed on a narrow `ErrorKind = "recoverable" | "user_input" |
  "upstream" | "fatal"` union. Four kinds is deliberate: adding a fifth
  is a conscious proposal, not an inline decision at a catch site.
- `classifyUnknownError(e, fallback)` — required-fallback-meta wrapper
  that refuses to invent classification. If `e` is already an
  AppError it is returned untouched; otherwise the call site's
  intent wins and the raw value is preserved via `cause` (for Error
  instances) or `context.raw` (for anything else).
- `logAppError(logger, raw, fallback, level?)` helper emits one
  structured log line and returns the classified AppError so the
  caller can still branch on `err.kind`.

Why this module exists: 248 catch blocks in src/main/ today are
overwhelmingly `} catch {}` or `log.warn(); return default`. Behaviour
is correct case-by-case but the codebase has no machine-readable signal
for "is this recoverable vs fatal", making it impossible to write
graceful-shutdown logic, telemetry buckets, or catch-site audits.

Part 2 migrates the top-20 high-frequency catch sites. Part 3 (tracked
as C6.2 proposal) covers the remaining 228.

No behavioural change in this commit — it only adds the classifier and
exports.
2026-04-20 12:14:55 -04:00
ZacharyZhang-NY f849efadf5 B5 harness iteration: self-test + assertion-judge cleanup + dead branch fix + _BLOCKED timeline correction
- harness.test.ts: add in-memory self-test layer (pass + fail + purity), guard
  fixtures dir with existsSync so absent dir yields empty list without
  crashing the runner. Empty-fixture pnpm test:eval now carries real signal.
- replay-client.ts: remove dead if (!turn) branch; cursor bounds-check above
  already guarantees the element. Use non-null assertion with a comment.
- assertion-judge.ts: drop unnecessary (turn as AssistantTurn | { content:
  unknown[] }) cast. turn.role === assertion.role already narrows to
  UserTurn | AssistantTurn and both expose .content.
- _BLOCKED-BY-CONSTRAINT.md: correct electron-builder note. files: is a
  whitelist, so tests/eval is automatically excluded and no new rule is
  required.
2026-04-20 11:48:55 -04:00
ZacharyZhang-NY cb2a0a0693 Mark B5 unblocked: minimal harness scaffold landed
Annotate B5 / C5 / M3 / M6 subsections with 2026-04-20 update pointers:
B5 is unblocked and scaffolded today (tests/eval/); C5 / M3 / M6
execution unblocks progressively as fixtures accumulate against the B5
harness.
2026-04-20 11:42:05 -04:00
ZacharyZhang-NY bf79a9a22e Add B5.1 proposal for LLM-as-judge tier above assertion judge
Document the follow-up tier on top of the B5 assertion harness: thin
single-call rubric judge, additive RubricDimension/RubricVerdict types,
cost cap ($2/run) with loud-skip behavior, per-fixture rubric cap (8
dimensions), baseline acceptance workflow, and explicit trigger
conditions (10+ fixtures / 2 incidents / structural-miss / 50-run zero
catch) — do not build speculatively.

Roundtable outcome: 2-1 to defer tonight. Linus — don't bolt a second
LLM to an empty harness. Jobs — single signal first. Karpathy — fixture
accumulation is what will tell us which rubric dimensions matter; the
schema stays extensible so we can add rubric-per-fixture without
breaking B5.
2026-04-20 11:41:25 -04:00
ZacharyZhang-NY 2c6dd8ed08 Reorganize worker observability layer into worker/observability/ (M8 part 3/3)
Collects the five post-run diagnostic + escalation modules into src/main/worker/observability/: run_events persistence, stdout/stderr noise filter, credential-shape log redactor, self-wake circuit breaker, and retry-aware failure escalation pipeline. circuit-breaker.ts and failure-escalation.ts remain as standalone files inside the directory — each owns a distinct state machine that deserves its own boundary — rather than collapsing into a single index.ts. handlers/*.ts and tests/unit/*.test.ts updated to point at the new paths.
2026-04-20 11:41:07 -04:00
ZacharyZhang-NY da3543fcd9 Reorganize worker runtime layer into worker/runtime/ (M8 part 2/3)
Groups the pi Agent lifecycle, factory, signature contract, and env/redaction helpers into src/main/worker/runtime/. WorkerRunner is the module's public entry and lives in runtime/index.ts; pi-agent-factory and pi-hook-contract stay named for their role as visible runtime boundaries; helpers.ts carries ParentMessage/ChildMessage/RunJob types plus redactor + env override utilities. entry/index.ts and agent-runtime/runtime-message-injector.ts updated to import from the new paths.
2026-04-20 11:40:01 -04:00
ZacharyZhang-NY e7f879bed0 Build minimal LLM eval harness scaffold (B5) — fixtures recorded separately
Land the B5 harness skeleton: fixture loader (strict JSONL contract with
meta header + ordered turn records + assertions tail), replay-client that
swaps in for pi-ai's streamSimple by replaying recorded assistant turns,
structural assertion judge (tool_called / no_tool_called /
message_contains_substring / tool_call_arg_matches / trace_max_turns), an
eval-runner that sums the token ledger and produces an EvalResult, a
vitest driver (tests/eval/harness.test.ts) that discovers *.jsonl
fixtures, and a record-fixture CLI that writes fixture meta headers.

Hard contracts:
- No fallback: replay-client and fixture-loader throw on exhaustion /
  malformed input rather than synthesizing defaults.
- No mock: types reuse @mariozechner/pi-ai AgentMessage / ToolCall
  verbatim; shadow types are forbidden.
- No fake fixture data: tests/eval/fixtures/ ships only README +
  .gitkeep. Real traces are recorded by the user against live providers.

package.json adds test:eval (vitest --passWithNoTests so an empty
fixture dir is green) and eval:record (CLI entrypoint). electron-builder
already excludes tests/ via its files: glob — fixtures never enter the
shipped .asar.
2026-04-20 11:39:53 -04:00
ZacharyZhang-NY 76d9031a41 Reorganize worker entry layer into worker/entry/ (M8 part 1/3)
Pulls the worker process entry point, parent<->worker IPC plumbing, message validation schema, and crash-recovery handler into a single src/main/worker/entry/ module. No runtime behavior changes — only directory + file-name renames plus import-path updates: worker-entry.ts -> entry/index.ts, worker-message-handler.ts -> entry/message-handler.ts, worker-rpc.ts -> entry/rpc.ts, worker-crash-recovery.ts -> entry/crash-recovery.ts, worker-types.ts -> entry/types.ts. electron.vite.config.ts, worker-service.ts, handlers/*.ts, pi-agent-factory.ts, and worker-runner.ts updated to point at the new locations.
2026-04-20 11:38:46 -04:00
ZacharyZhang-NY 0e6cad7f4c _BLOCKED: Add constraint timeline showing tests-allowed update 2026-04-20 11:38:01 -04:00
ZacharyZhang-NY cfc06e1e53 README: Document standard package.json scripts conventions (N8) 2026-04-20 11:37:14 -04:00
ZacharyZhang-NY 35a38631ef Clarify AGENTS.md vs CLAUDE.md split (N7) 2026-04-20 11:36:54 -04:00
ZacharyZhang-NY ec2a5f971a Restructure top-level directories (screenshots/builtin-skills/assets) (N4) 2026-04-20 11:36:00 -04:00
ZacharyZhang-NY 38e02c969c tsconfig: Comment explains base/check/default config split (N3) 2026-04-20 11:35:05 -04:00
ZacharyZhang-NY 5a41489bc4 Pin local hook signatures to pi-agent-core compile-time contracts (C5)
Adds src/main/worker/pi-hook-contract.ts, a compile-only contract that re-states every pi-agent-core hook callback shape (beforeToolCall / afterToolCall / transformContext) as a named alias and pins the local worker implementations against them via type-level ExpectHookType assertions. pi-agent-factory.ts imports the contract for side effects so tsc drags it into the build graph. A future pi upgrade that renames a field or changes an argument now fails typecheck here instead of at first destructive tool invocation.
2026-04-20 11:35:04 -04:00
ZacharyZhang-NY f6a58f6a37 README: Document AgentCompany-Rust as exploratory parallel implementation (N2) 2026-04-20 11:34:21 -04:00
ZacharyZhang-NY b3b9eb60e4 Document pi-agent-core 0.67.6→0.67.68 upgrade trace and rollback plan (B1.1) 2026-04-20 11:04:13 -04:00
ZacharyZhang-NY b100bdcc10 追加 C3 真实 tokenizer 迁移 proposal
C3 首轮落地的 2 chars/token 是中间态,根治方案是换 tiktoken 或等 pi-ai
暴露 tokenizer。proposal 记录了备选依赖(tiktoken / anthropic-tokenizer /
count_tokens API)的 trade-off,以及保留 estimateTokens 签名不变的迁移契约。
2026-04-20 10:59:55 -04:00
ZacharyZhang-NY c59f62ba84 收紧 token 估算到 2 chars/token (C3)
原先 4 chars/token 对中文 BPE 严重低估(汉字实际约 1.5-2 tokens/char),
120k 阈值只有在真实上下文已经爆窗之后才触发。2 chars/token 对拉丁文保守
一些,换来中文 agent 不再漏触发压缩。pi-ai 0.67.68 没暴露真 tokenizer,
换 tiktoken/anthropic-tokenizer 的跟进方案见 docs/proposals/C3-real-tokenizer-followup.md。
2026-04-20 10:59:12 -04:00
ZacharyZhang-NY 4ff751b3af 让 compactor 尊重 tool_use/tool_result 配对边界 (B3)
cutoff 只允许落在 role==user 的消息上,避免把 assistant 的 toolCall 和下一条
toolResult 切开导致 provider 拿到悬挂 id 崩溃。找不到安全边界就直接不压缩,
let it crash 给上游 token 窗口兜底,不伪造假 tool_result。
2026-04-20 10:58:49 -04:00
ZacharyZhang-NY 3514ede639 Document constraint-blocked items (B5/C5/M3/M6) and execution matrix 2026-04-20 10:54:49 -04:00
ZacharyZhang-NY 06d58d9fd6 Add proposals for deferred root-cause refactors (B2/B4/C1/C2/C4/C7/M2/M5/M7-M12) 2026-04-20 10:53:16 -04:00
ZacharyZhang-NY 77076e7b29 Drop redundant prompt-builder.ts re-export shim 2026-04-20 10:30:04 -04:00
ZacharyZhang-NY f01fc2cd42 Pin pi-agent-core/pi-ai/pi-coding-agent to exact 0.67.68 (align with in-tree reference) 2026-04-20 10:27:54 -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 e74dafcd36 Silence render-disposed spam, unbreak bundled skills, drop board wake
Three independently ugly bugs that all fed the same log flood:

* Electron's `WebFrameMain.send` writes every "Render frame was disposed"
  to stderr via `console.error`. When the worker fans a second
  agent-event past a reloading renderer, we produced thousands of
  identical lines and stalled the main loop on stderr I/O. Added a
  `mainFrame.detached` guard so the send is short-circuited before
  Electron's own catch block fires, and installed a targeted filter in
  `suppress-warnings.ts` (now actually imported from the entry point)
  so the specific `console.error("Error sending from webFrameMain:"…)`
  noise never reaches the terminal. Any other payload still surfaces.

* `dispatchAgentMessage` accepted the human-reader sentinel as a wake
  target when an agent replied to `__board__` — `wakeAgentIfPossible`
  then called `db.getAgent("__board__")`, threw, and the dispatcher
  kept repeating on every send. Strip `HUMAN_SENDER_ID` at
  `resolveMessageWakeTargets`, and keep a defense-in-depth guard in the
  heartbeat so a stray caller (future automation fan-out) cannot
  reintroduce the crash.

* Pi's skills loader rejects any SKILL.md whose frontmatter `name`
  diverges from its parent directory. The four bundled `my-writing-style`
  copies declared `name: writing-style-skill`, so every install emitted
  a warning and dropped the skill. They also lived as orphan gitlinks
  (mode 160000 without .gitmodules), and the global `*.md` gitignore
  hid every other SKILL.md from version control. Fixed the frontmatter
  to match the dir, converted the dangling submodules to plain trees,
  and allowlisted `builtin-skills/**/*.md` so a fresh clone carries the
  catalogue instead of silently losing it.
2026-04-20 08:13:48 -04:00
ZacharyZhang-NY 7a9a6779b0 uodate 2026-04-20 08:02:40 -04:00
ZacharyZhang-NY e5e293085b Revert Fire Pass model id to the router path
The previous pass "aligned" model-resolver.ts to
accounts/fireworks/models/kimi-k2.5-turbo — the per-token model path —
to match the connector catalog's default. That was the wrong direction:
Fire Pass subscription traffic routes exclusively through
accounts/fireworks/routers/kimi-k2p5-turbo (see docs.fireworks.ai/firepass),
and the entitlement does not attach to the per-token id. Existing agent
rows already pointed at the router id, so validateConnectorModelSelection
rejected every dispatch with "does not allow model
'accounts/fireworks/routers/kimi-k2p5-turbo'" and the CEO stopped running
entirely.

Put the router id back in model-resolver.ts (the enabled-pool source of
truth), and bring the two catalog defaults in line so a freshly-seeded
Fire Pass connector also ends up on the router id. The per-token
`fireworks` connector keeps the `models/...` default since that path
bills per token and genuinely targets the per-model endpoint.
2026-04-19 22:14:42 -04:00
ZacharyZhang-NY 834ac913ca Chain CEO runs through self-wake during team building
triggerSelfWake fired only when the completing agent had at least one
actionable task (todo / in_progress). A CEO in team_building mode has
zero tasks by definition — each heartbeat hired one or two agents,
returned, and then waited for the next 2-minute timer tick. Operators
watched bootstrap crawl: in the captured field log, six heartbeats
burned 12 minutes before the first hire landed.

Teach triggerSelfWake to also chain when the agent is a top-level CEO
with active goals, zero active direct reports, and no already-pending
hire_agent approvals — the exact state team_building.en.md is telling
the model to resolve. The existing circuit breaker (MAX_CONSECUTIVE_WAKES
without progress) still applies, so a CEO that keeps returning without
filing hires gets paused and its manager wake — the board / human —
is raised instead.

Once reports activate or all intended hires are filed, the predicate
flips to false and runs resume the normal heartbeat cadence.
2026-04-19 21:10:28 -04:00