Commit Graph
72 Commits
Author SHA1 Message Date
ZacharyZhang-NY e917c614c9 Gate inbox delivery on worker-side steer ACK
Replace the fire-and-forget steer with a correlated ACK round-trip so main
only marks an inbox row delivered once the worker confirms the message
landed on the live Agent. Steer against a run that is still in the
`preparing` window or has already started `closing` now falls back to the
wake + inbox projection path instead of being silently dropped.

Worker publishes `run-agent-state` transitions at Agent construction and
teardown; main tracks these on `ActiveRunContext.state` and refuses to
steer runs that are not `agent_active`. A rejected or timed-out ACK
triggers `wakeAgentIfPossible` so the next `prepareRun` replays the row.
2026-04-19 14:38:04 -04:00
ZacharyZhang-NY 734a2b04e0 fix(runtime): narrow beforeToolCall gate to operational approvals
Previous Task 2 pass gated every destructive tool on any blocking
approval or any open wait-state for the agent. That was too coarse:
a pending deliverable_review or approve_ceo_strategy (both decision-
type approvals, waiting for a human to respond to completed or
proposed work) froze the agent's entire toolchain — including the
`bash` call the agent uses to POST /api/messages. Result: CEO
talked, employees woke on heartbeat, tried to reply, the message
POST was blocked as a "destructive tool", the run ended silent.
Every reply collapsed to an empty heartbeat.

Two corrections land together:

1. Approval gate narrows to the five approval types that actually
   gate EXECUTION — `dangerous_command`, `secret_access`,
   `production_deploy`, `social_post`, `budget_override`. Review /
   decision types are ignored at the tool layer; they remain visible
   in the board UI and still block via the heartbeat scheduler when
   the execution mode demands it.

2. Wait-state check leaves the hook entirely. The heartbeat scheduler
   (`checkpoint-engine.shouldPauseHeartbeat`) is the single owner of
   "agent should pause because a thread is waiting" — mirroring the
   rule at tool level produced the same company-wide freeze for
   any reply on any thread. Heartbeat is the right scope; tools
   are not.

The destructive-tool set and the operational-approval set are both
named, readonly, and centralised so the next addition reads like a
whitelist instead of a patch.
2026-04-19 13:20:14 -04:00
ZacharyZhang-NY 5e6e929779 refactor: prune session-store and agent-list dead code
The session_entries supersede pipeline was a design that never
shipped: `SqliteSessionStore.replaceMessages` / `appendCompaction`,
`sessionEntries.replace` / `listAll` / `deleteForSession`, and the
`"compaction" | "summary"` kind variants had zero live callers.
Compaction wires through pi's `transformContext` and leaves the
persisted transcript alone, so the shadow-row machinery was pure
cruft with two tests propping it up.

Rip it out: the store exposes only `loadMessages` and
`appendMessage`, `sessionEntries` exposes only `append` and
`listActive`, and `SessionEntryKind` narrows to `"message"` so new
writers can't re-introduce a dead kind by accident.
`is_superseded` stays on the table — the DB column is harmless
with no writer, and dropping it is a separate schema migration
concern.

`listTopLevelAgentIds` loses its only caller in the previous
commit (company channel now fans out through
`listCompanyBroadcastAgentIds`), so it goes too. Three tests that
validated the removed behaviour are deleted; the one remaining
session-store test still covers the live `append + load` path.
2026-04-19 01:34:14 -04:00
ZacharyZhang-NY e53ac36176 feat(session): auto-compact long transcripts via transformContext
pi's `transformContext` hook fired on every LLM call but returned
the transcript unchanged, so a long-running agent paid per-token
for its entire history on every turn. The README promised "sessions
persist + auto-compact" but the main loop had no compaction wired
in at all — only rotation at the long-cycle boundary.

`context-compactor` closes that gap. A pure, provider-free function
estimates tokens (~4 chars/token), short-circuits below a 120K
threshold, and on overflow keeps the last 20 messages verbatim
while collapsing older ones into a single "context summary" user
turn. The summary preserves the original task and the first twelve
assistant highlights so the agent has continuity without forwarding
every historical turn.

Compaction is non-destructive: the Agent's in-memory transcript
and the SQLite session store still hold the full history, feeding
UI timelines and audit. Only the LLM payload shrinks. Rotation
remains the long-cycle boundary (separate commit) — compaction is
what keeps each individual LLM call within budget.
2026-04-19 01:14:48 -04:00
ZacharyZhang-NY 34814b4dfd feat(artifacts): project bash filesystem changes into work_products
The artifact projector only registered write/edit. Everything bash
produced — build artifacts, files written via shell redirection,
git commits, generated code — stayed invisible to the desktop UI
and the audit layer. Filesystem truth and UI truth were
systematically out of sync.

This lifts bash onto the same filesystem artifact protocol as
write/edit. When a bash tool call succeeds in a git workspace, the
projector runs `git status --porcelain=v1 -z --untracked-files=all`
and upserts a work_products row for every modified/untracked entry,
reusing the existing `(companyId, provider='filesystem', externalId)`
identity so repeated edits never duplicate. Non-git workspaces fall
through with an explicit "not_a_git_repo" skip reason — there is no
authoritative baseline there to diff against, and silently
half-registering would be worse than skipping.

A 50-file cap per invocation bounds the scan so a runaway rebuild
cannot flood the table; anything past the cap is logged as a skip.
Rename entries (emitted as `R new\0old`) advance the iterator past
the old-path entry so deletions of the rename source are not
double-registered.

The projector now always returns an array so `write`/`edit` (one
path) and `bash` (many paths) share one shape, and the hook handler
can log uniform outcomes.

.gitignore gains `AgentCompany-Rust/` — the separate Rust rewrite
tree lives in the same repo root but is not part of the desktop app
build and should not drift into feature commits.
2026-04-19 01:01:29 -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 795008f56b fix(runtime): enforce approval + wait-state gate in beforeToolCall
Approval creation already flowed through the API server, but pi's
`bash` / `write` / `edit` tools never traversed that layer, so the
gate was purely advisory: the agent kept mutating the workspace while
the board tried to hold it. The fix: policy runs at the one place
every destructive tool invocation must pass, pi's `beforeToolCall`.

`checkApprovalBlock` fails closed on lookup errors — an unreadable
approvals table is a safety incident, not a licence to execute. It
re-reads `getBlockingApproval` on every call so an approval created
mid-run takes effect on the next tool without any cache.

`checkWaitStateBlock` rides the unified
`hasUnresolvedWaitStateForAgent` query so task, approval, and
freeform thread wait states all gate the same tools. Read-only tools
are untouched; only bash/write/edit are gated because gating
observation would slow investigation without adding safety.

The destructive-tool set is promoted to a named predicate so the
Task 5 artifact projector can share the same definition when it
extends to bash outputs.
2026-04-19 00:51:54 -04:00
ZacharyZhang-NY e1693899a2 feat(runtime): wire pi-agent-core hooks end-to-end
The worker previously constructed `new Agent({systemPrompt, model, tools,
messages, streamFn})` and stopped there — transformContext,
beforeToolCall, afterToolCall, sessionId, thinkingLevel, and steer/
followUp were all dark. Approval enforcement, artifact projection,
auto-compaction, and mid-run steering all had to reach the agent
through other, non-canonical channels (event observers, prompt-block
injection at prepareRun, API-layer gates). That left the whole pi
contract at one-tenth utilisation and forced business logic into the
wrong seams.

This change rebuilds the contract assembly layer:

  - `pi-agent-factory` builds the Agent with the full option surface:
    sessionId (taskSessionId), thinkingLevel (mapped from
    agent.metadataJson), and three RPC-proxied hooks.
  - Each hook shuttles a thin payload over the existing worker RPC
    channel into `RuntimeBridge`, which routes to `hook-handlers`:
      * beforeToolCall → approval-gate policy (blocks destructive
        tools while a pending approval is outstanding).
      * afterToolCall  → filesystem artifact projection, previously
        nested in the tool_execution_end event observer.
      * transformContext → pass-through today; compaction wires in
        next commit.
  - ParentMessage gains `steer-run` / `followup-run`; `WorkerRunner`
    keeps a runId→Agent registry so main can inject mid-run messages
    into the active pi loop.

`worker-types` extends the rpc-request method enum; `runtime-bridge`
loses the per-event artifact projector and gains a `findActiveRunForAgent`
lookup used by the upcoming inbox → steer path. No public main API
changes yet — the wiring is the foundation for the next five commits.
2026-04-19 00:48:13 -04:00
ZacharyZhang-NY ff5e22092a refactor(session): agent-scope rotation + accumulation; auto-register file artifacts
Sessions are already agent-scoped post-v34, but the code kept routing
rotation / accumulation / handoff through the task indirection. Heartbeat
runs (no task) silently skipped all three, letting taskless transcripts
grow past the model's context window — the 7M-token explosion.

- DB adds getSessionById(sessionId) — direct lookup bypassing
  tasks.active_session_id
- session-management-service: generateHandoff(session) is now agent-scoped,
  walking recent runs across tasks + heartbeats; getHandoffForAgent(agentId)
  is the canonical lookup, getHandoffContext(taskId) is a thin shim for
  callers that still hold a task id
- turn-context.ts::resolveSession drops the `if (!task)` early return:
  rotation evaluation runs on the agent session unconditionally
- run-finished-helpers.ts::accumulateAndRotateSession drops the `!task`
  gate: tokens accumulate and rotation fires for every run, keyed on the
  agent session, not the task
- Rotation evaluator contract: evaluatePreRunSessionRotation(agentId,
  connectorId) → { rotated, handoff, sessionId }; old (taskId, connectorId)
  signature removed

IPC contract tightening: ipc-support::emitEvent becomes a best-effort
sink. "Render frame was disposed" during renderer reload is a lifecycle
signal, not a logic error; silently drop it instead of letting it abort
downstream event emissions.

Artifact projection: pi's write/edit tool results now upsert a
`work_products` row via a new agent-runtime/artifact-projector.ts. No
more agents writing 54 files into the workspace that the UI never sees.
Migration v40 relaxes work_products.task_id to nullable so heartbeat
artifacts can register too.

File size: split agent-event-projection.ts out of runtime-bridge.ts to
keep the bridge under the 400-line cap after adding the projector hook.

Tests: 604 unit + integration green; generateHandoff callsites updated
for the single-argument signature; artifact-projector has pure-function
coverage via the projector module's own shape.
2026-04-18 21:37:56 -04:00
ZacharyZhang-NY 02ba887610 fix(runtime): stop gating pi tool calls; Co-Founder approvals live at API layer
The Co-Founder beforeToolCall hook (introduced in 77f81a7) gated every
non-readonly pi tool call as a `dangerous_command` approval. Because
agents call `POST /api/companies/:id/messages`, `/agents` (hire), and
every other company mutation via bash→node/curl, the first bash call in
every Co-Founder run got parked on a 30-minute approval timeout. Users
observed it as "agents never talk to each other" and "every bash command
waits for approval"; both symptoms are the same bug.

The business-level approval flow (`hire_agent`, `document_review`,
`plan_review`, etc. in execution-policy.ts) already gates the events
users actually care about, at the HTTP route layer where the event
originates (routes-agents.ts:368 for hiring, document routes for
deliverables). Stacking a pi-level hook on top was duplicate gating with
the wrong taxonomy.

Removed:
- src/main/agent-runtime/approval-hook.ts (entire file, 137 LOC)
- `beforeToolCall` conditional in worker-runner.ts and the
  corresponding RPC method in worker-types.ts and runtime-bridge.ts
- `approvalHook` field + `resolveApproval` callback + shutdown
  `cancelAll` in runtime-bridge.ts / service-graph-pi-runtime.ts /
  wire-api-server.ts

Unchanged:
- `dangerous_command` approval type itself (still used by automation
  workflows and routes-misc.ts)
- CEO mode (already never attached the hook)
- All business-level approval types and their wiring
2026-04-17 19:11:04 -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 14dc9a4897 refactor(cost): single writer for cost_entries, finishRun reads SUM
Cost was written through three independent paths: worker-runner shipped
`finalUsage.costUsd` in run-finished -> finishRun wrote `runs.cost_usd`,
`tasks.cost_usd += costUsd`, `agents.spent_monthly_usd += costUsd`, and
inserted a per-run aggregate row in cost_entries. CostWriter
additionally inserted per-message rows in cost_entries from main. Any
CostWriter failure created silent drift between `SUM(cost_entries)`
(read by budget enforcement) and `runs.cost_usd` (read by the UI).

Make CostWriter the single writer, finishRun the single reader.

- Add `db.recordCostEntry(...)` as the first-class insert path for
  per-message cost rows. CostWriter calls it; tests call it directly.
- Remove the aggregate INSERT from finishRun. finishRun now reads
  `SELECT COALESCE(SUM(amount_usd), 0) FROM cost_entries WHERE run_id=?
  AND unattributed=0` and writes that as the authoritative run total to
  runs.cost_usd, tasks.cost_usd, and agents.spent_monthly_usd.
- Drop `costUsd` from `finishRun` input, the worker `run-finished`
  ChildMessage shape, the automation `RunFinishedEvent` type, and every
  caller + test fixture. Cost is owned by the per-message path.
- worker-runner stops extracting `usage.cost.total` — token counts stay
  for session accumulation, cost comes from pi-ai via CostWriter.
- run-finished-helpers now gates budget incident checks on
  `event.status === "succeeded"` instead of a cost field that no longer
  exists on the event.
- automation-run-completion fires `budget_threshold` on every succeeded
  run (post-run spent_monthly_usd already reflects the SUM path).
- Update budget tests to write a `recordCostEntry` before `finishRun`
  to simulate the per-message cost path; drop `costUsd` from cross-
  subsystem, edge-case-budget, database, and related fixtures.

One writer, one reader, one source of truth. No drift.
2026-04-17 15:04:22 -04:00
ZacharyZhang-NY 77f81a7c5a fix(runtime): revive AI Co-Founder tool-call gating via pi beforeToolCall
An earlier cleanup deleted HumanApprovalHook wholesale on the rationale
that the body always returned {}. That was the stub implementation, not
the feature: 版本更新.md specifies AI Co-Founder mode as "human-reviewed
execution that pauses until human review arrives". With no beforeToolCall
attached, Co-Founder and CEO modes behaved identically — a silent
regression of the product's core differentiator.

Bring the hook back with real gating logic and split the Agent
construction path by execution mode so CEO runs pay zero IPC overhead.

- HumanApprovalHook.before(): create a `dangerous_command` approval row
  for non-whitelisted tool calls, emit approval-updated, return a promise
  that resolves when a human decides.
- AUTO_APPROVED_TOOLS whitelist: read/ls/glob/grep/find/tree. Anything
  else (bash, write, edit, …) waits for human approval.
- approvalHook.resolveApproval(): invoked from wire-api-server's
  onApprovalResolved *before* automation and thread follow-ups, so the
  pi Agent unblocks the instant the human decides.
- approvalHook.cancelAll(): release every pending wait on shutdown so the
  worker can drain cleanly; wired into service-graph-pi-runtime.release.
- worker-runner: attach beforeToolCall only when job.executionMode ===
  "ai_cofounder". CEO mode constructs Agent with no hook, no RPC hops.
- WorkerRpcClient.invoke: accept per-call timeoutMs override so approval
  RPCs can wait 30min instead of the 30s default.
- worker-types schema: restore beforeToolCall in the method enum.
- RuntimeBridge.handleRpc: restore beforeToolCall branch.

No changes to approval DB schema, approval lifecycle, or existing
automation/thread handling. CEO-mode runs retain zero-IPC-per-tool
behavior from the prior cleanup.
2026-04-17 14:53:22 -04:00
ZacharyZhang-NY b50716d4e8 refactor(messaging): project inter-agent messages into pi session transcript
Inter-agent messages previously entered the recipient's context as a
separate prompt block (recentMessages), rebuilt every turn. This double-
charged tokens and kept messages outside pi's native AgentMessage stream.

Now at prepareRun: pull the recipient's unread inbox, project each
AgentMessageRecord into a pi user-role AgentMessage, persist to
SqliteSessionStore, and mark delivered. The recipient reads messages
as natural conversation turns in the transcript.

- db: listAgentInboxUnread + markAgentInboxDelivered (transactional)
- agent-runtime/inbox-projection.ts: AgentMessageRecord -> pi AgentMessage
- runtime-bridge.prepareRun: inject + deliver before loadMessages
- prompt-builder: delete recentMessages context block + buildMessagesSection
- message-normalize: drop unused normalizeMessagesForPrompt
- delete prompt-builder/attachment-resolver.ts (orphan after section removal)

Send path (api-server/routes-messages, ipc, UI) unchanged — inbox table
remains the UI source. Transactional delivery marking prevents duplicate
injection across runs. Failed append -> no mark -> retry on next run.
2026-04-17 14:48:22 -04:00
ZacharyZhang-NY 5547c29491 update 2026-04-17 14:03:56 -04:00
ZacharyZhang-NY 806200b3d1 fix(runtime): correct pi AgentEvent projection + add Console diagnostics
karpathy-guidelines pass — never guess at types, read the source.

Root cause of the silent "等待日志输出" Console:

1. runtime-bridge's projectAgentEventToLogChunk switched on event types
   that never exist in pi. Real types per pi-mono-reference/packages/
   agent/src/types.ts `AgentEvent`:
     agent_start / agent_end / turn_start / turn_end
     message_start / message_update / message_end
     tool_execution_start / tool_execution_update / tool_execution_end
   (NOT the `assistant_message_delta` / `tool_call_start` / `error` I
   invented.) Every case therefore fell through to `default → null`,
   so zero log chunks were emitted even when the agent was streaming
   tokens.

   Rewrote the switch to match the real union. Streaming assistant
   text and thinking now route through `message_update` ->
   `assistantMessageEvent.text_delta` / `thinking_delta` (see
   pi-mono-reference/packages/ai/src/types.ts `AssistantMessageEvent`
   for the inner protocol). Tool activity routes through
   `tool_execution_start` / `tool_execution_end`.

2. worker-runner had no Console-visible breadcrumbs between
   `run-status: running` and eventual turn output, so when the agent
   stalled (credential fetch, model resolution, first API call) the
   user saw a bare "RUNNING" with no clue where time was being spent.
   Added 5 targeted `run-log` probes:
     [runner] starting run ...
     [runner] prepareRun ok, resolving model
     [runner] model=... provider=... — constructing Agent
     [pi-event] <type>        (inside agent.subscribe, one per event)
     [runner] calling agent.prompt (N chars)
     [runner] agent.prompt resolved
   These are structured runtime signals, not test/mockup data. They
   surface the exact phase a stuck run is in.

typecheck 0 / lint 0-0 / test:unit 508/508 / build:verify 0-0.
2026-04-17 10:35:23 -04:00
ZacharyZhang-NY 683dded501 fix(runtime): autonomous beforeToolCall + project agent-event to Console log
Two coupled user-visible defects were fixed.

1. Every tool call (shell / edit / write / read / ...) triggered a
   synchronous human-approval prompt under BOTH execution modes. The
   worker blocked inside pi's beforeToolCall waiting on a DB approval
   row, so runs never progressed — "等待日志输出" forever.

   The execution-mode contract is now enforced:
   - AI CEO       → fully autonomous, no framework gate.
   - AI Co-Founder → still fully autonomous at the pi layer. Human
     review is a model-driven decision (LLM calls an explicit
     `request_approval` tool when it judges review necessary), not
     a framework-level interrupt.

   HumanApprovalHook collapses to a no-op `before` / `after` /
   `cancelAll`. All machinery that queued `dangerous_command`
   approvals from the worker-side hook is removed. No more starving
   the agent on `node -e` self-call checks.

2. The Console panel only subscribed to `run-log` stdout/stderr
   chunks, so pi's agent-event stream (assistant_message_delta,
   tool_call_start/end, errors, turn_end) produced zero visible
   output. With bug (1) fixed, runs now execute — but the user
   would still see a blank Console.

   runtime-bridge.handleAgentEvent now projects each meaningful
   agent-event into a run-log chunk (assistant_message_delta → raw
   delta, tool_call_start → `[tool:bash] {args}`, tool_call_end →
   `[tool:done|error:name] {result}`, error → stderr). ConsoleView
   picks these up via the existing run-log DesktopEvent pipe with
   no renderer change.

tests/unit/approval-hook.test.ts rewritten to assert the new
autonomous contract (before always returns {}, no approval rows
written, cancelAll is a no-op). Tests 508/508 pass.

typecheck 0 / lint 0-0 / build:verify 0-0.
2026-04-17 10:23:50 -04:00
ZacharyZhang-NY e7e43bd0a6 fix(fireworks): route Fireworks models through resolveModel everywhere
pi-ai's getModel/getModels does not know about the Fireworks providers
(fireworks, fireworks-firepass); both live under our local model-resolver
because Fireworks speaks the Anthropic-compat endpoint with Bearer auth
at https://api.fireworks.ai/inference, not pi-ai's built-in catalog.

Three call sites were still hitting pi-ai directly and failing silently
for Fireworks:

1. connector-model-policy.ts — listProviderModels() caught the pi-ai
   throw and returned []. The "Fire Pass (CEO runtime)" card in the
   Team Models onboarding step therefore showed "暂无模型 / 已启用 0/0",
   blocking users on Fire Pass from continuing onboarding.

2. worker/worker-runner.ts — getModel() threw mid-run when the selected
   connector was Fireworks, so the worker could never actually execute
   an agent turn against Fire Pass or the curated Fireworks model set.
   Now uses resolveModel() + the Fireworks-specific Bearer header from
   resolveExtraStreamHeaders() so streamSimple hits the correct endpoint
   with the correct auth.

3. agent-runtime/cost-writer.ts — cost observation silently wrote
   amount_usd=null for Fireworks runs because getModel() threw inside
   the try/catch. Now resolves via resolveModel() so the Fireworks
   curated contextWindow/maxTokens are visible to calculateCost().

Verification: pnpm typecheck 0 / pnpm lint 0-0 / pnpm build:verify 0-0
/ pnpm test:unit 508/508.
2026-04-17 09:51:38 -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 892476bd36 refactor(runtime): wire pi-runtime services and extend RuntimeBridge
Bootstraps CredentialStore/Resolver, OAuthLoginService, and RuntimeBridge
inside service-graph via the existing pi-runtime factory, and exposes the
bridge to worker-service for agent-event + rpc-request routing. Adds a
`prepareRun` RPC that resolves credentials, loads prior messages, and
registers the active run context in a single call. Session rotation drops
the adapter nativeContextManagement branch; run payloads now carry the
taskSessionId and executionMode the worker needs to drive pi-agent-core.
2026-04-17 02:21:04 -04:00
ZacharyZhang-NY cad17b4d45 fix(credentials): close resolver switch and guard missing provider/auth
- credential-resolver.resolve: throw explicit CREDENTIAL_INVALID when
  the connector has no auth schema, and add a default branch so the
  switch has a definitive return path for every auth kind
- credential-resolver api_key branch: guard definition.provider being
  unset before calling pi-ai's getEnvApiKey
- cost-writer: only attempt cost lookup when definition.provider is
  defined; adapter-only connectors still write an unattributed row
2026-04-17 01:28:37 -04:00
ZacharyZhang-NY 3edb2fc6c1 refactor(main): extract getOrCreateStore in RuntimeBridge
P3-56: three call sites repeated the same 'lookup → fall back to
new → set if missing' dance for session stores. The duplication hid the
fact that the fallback allocation in the second and third sites could
be wasted when the entry already existed. Extract a single
getOrCreateStore helper that does one map lookup, one conditional
construction, and always returns the cached instance.
2026-04-16 23:27:57 -04:00