feat(providers): add Pi coding agent - #6319
Conversation
Co-authored-by: codex <codex@users.noreply.github.com>
- Initialize provider as unchecked in a pending state - Update initial probe message to reflect session-local status
- Type the runtime effect with `Scope` - Build the ACP session runtime without wrapping it in `Effect.scoped`
- Use strict TurnId and ProviderItemId parsing in Codex session routing - Decode in-memory stdio chunks in streaming mode to avoid split UTF-8 corruption
- Transfer session-owned scopes into adapter state - Ensure runtime scopes close on stop and startup failure - Add regression coverage for scoped lifecycle cleanup
- Close the managed native event logger when the adapter layer tears down - Make session runtime close idempotent with an atomic closed flag - Add coverage for flushing thread native logs on shutdown
- Use codex app-server snapshots for auth, models, and skills - Remove legacy CLI/config discovery paths and related helpers - Update tests for the new provider status flow
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
- Document the target orchestration graph, IDs, lifecycles, and capability model - Add Codex app-server probe fixtures and update the probe test harness
- Introduce orchestration v2 service interfaces and error types - Add replay runtime, fixtures, and integration coverage - Update shared contracts and probe transcripts Co-authored-by: codex <codex@users.noreply.github.com>
- Add Codex adapter and replay harness wiring - Introduce in-memory orchestration projections and provider registry - Expand orchestration contracts for turn and runtime events
Co-authored-by: codex <codex@users.noreply.github.com>
- Add context transfer IDs, schemas, and projections - Support cheap fork creation and Codex native fork rollback - Cover fork idempotency and replay behavior in tests
- Track remaining projection, context transfer, rollback, capability, and subagent work - Clarify current V2 baseline and debugger-only follow-ups
- Map fork and merge-back turns into stored handoffs and transfer resolutions - Add shell snapshot projection support plus coverage tests - Update replay fixtures and web contracts for the new turn flow
Co-authored-by: codex <codex@users.noreply.github.com>
- Move Codex replay recording into `apps/server` - Add Claude Agent SDK replay fixtures and test harness - Update orchestration-v2 fixture scenarios and docs
- Move Claude provider runtime logic into its own module - Share the SDK query runner between live and replay paths - Add replay driver error wrapping for unexpected failures
- persist the selected model on run records - surface run model selection in the debug UI - update replay fixtures and contracts for the new field
- Record Claude SDK transcripts across multiple prompts and restart/query modes - Add approval and tool-call replay coverage for new orchestration fixtures - Update Claude adapter testkit to model open/prompt/permission frames
- Derive Claude SDK query options from runtime policy - Add read-only replay fixture and policy mapping tests - Reuse shared approval-policy fixtures across orchestrator tests Co-authored-by: codex <codex@users.noreply.github.com>
- add active steering and interrupt-restart replay fixtures - update Claude adapter/orchestrator turn handling for steering - refresh replay and integration test coverage
- add interrupt and mid-tool replay fixtures for Claude and Codex - log Claude Agent SDK protocol frames to native event traces - project Codex commandExecution start events into orchestration updates
- Map Cursor SDK agents and runs to V2 thread and turn lifecycles - Update MCP capability, tool, and testing guidance for SDK-based injection
Co-authored-by: codex <codex@users.noreply.github.com>
- Match progress button spacing and single-line height to static git actions
rerere replayed stale resolutions during the rebase and committed nested conflict markers in several files. Restore the branch-intended v2 shapes and re-graft main's compatible additions (pending-card opacity comments, theme-editor keybinding test, mobile scroll re-arm effects from pingdotgg#5566). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eysetIndex Main owns migration numbering: 037_ProjectionTurnsKeysetIndex landed on main (pingdotgg#5493), so the v2 migrations shift from 037-045 to 038-046. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native subagent observability (pingdotgg#5219), wired per its spec's v2 merge plan: - getWorkflowScript RPC re-homed onto the v2 WS surface (contracts, rpc group, ws handler, auth scope, client atom). - AgentsPanel fed by the spec's mapper swap: projectedSubagentsToRuntime maps orchestration-v2 subagent entities into the panel model; deriveAgentPanelModel's v2Projection leg is now live and the v1 fold never runs. Agents surface wired into ChatView + RightPanelTabs. Other ports and reconciliations: - Shell reconnect-loop fix (pingdotgg#5561) ported into the v2 shell sync (same-session resubscribes resume from the in-memory cursor), with the cursor-resume regression test adapted to v2 fixtures. - Mobile end-follow latch (pingdotgg#5566) ported onto the v2 ThreadFeed. - Claude ede_diagnostic interrupt classification (pingdotgg#5557) ported into ClaudeAdapterV2 (aborted_tools/aborted_streaming => interrupted; CLI telemetry never becomes the failure banner). pingdotgg#5559 needs no v2 port (unknown system subtypes are already ignored). - Plan sidebar removed from the v2 ChatView/ChatComposer per main's plans-fold-into-chat rework (pingdotgg#5558); rightPanelStore stays at main's surface set. - SettingsPanels rebuilt as main's refactored version plus the branch's composer-context setting; sidebar snooze respects the time format (pingdotgg#4438 follow-through). - v1-only leftovers deleted: zombie v1 adapters/ingestion/tests the v2 rewrite removes, the v1-bound transfer-budget CI harness (pingdotgg#5350, needs a v2 rebuild), and main's v1 client pagination machinery (pingdotgg#5493 client side; the 037 keyset migration is kept — server-side v2 windowing is a follow-up). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebase kept the LegendList 3.3.3 upgrade and patch from pingdotgg#5449 and the mobile end-follow latch from pingdotgg#5566, but the v2 MessagesTimeline/ChatView still carried the branch's blunt any-gesture-breaks-follow listeners. Port main's pingdotgg#5566 web mechanics onto the v2 follow architecture: - resolveTimelineIsAtEnd measures the 40px follow re-arm band from real geometry (contentLength/scroll/scrollLength minus the composer inset), keeping the isNearEnd fallback for older state shapes. - Follow now breaks only on gestures that can actually leave the live edge: upward wheel with overflowing content, touch drags that exited the end band, scrollbar drags vs content clicks, and keyboard navigation (PageUp/Home/ArrowUp) — previously keyboard scrolling never broke follow and the next stream chunk yanked the view back down. - Listener attach retries across frames so a thread switch cannot mount the list without its opt-out listeners. Deliberately not ported: pingdotgg#5449's shouldRestorePosition disclosure anchoring and follow-gated maintainScrollAtEnd — the v2 timeline keeps maintainScrollAtEnd={false} with its own follow scrolls and anchor system; flipping that core is a separate change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gdotgg#5449) Complete the pingdotgg#5449 architecture on the v2 timeline, following the LegendList author's direction to lean on the list's native mechanisms instead of app-side scroll layers: - maintainScrollAtEnd is enabled and owned by LegendList, gated off only while the user reads history (liveFollowEnabled), while a sent turn anchors near the top (anchoredEndSpace), or during the two-frame settle of a fold toggle. - maintainVisibleContentPosition compensates size changes natively ({data, size, shouldRestorePosition}); fold toggles anchor compensation to the toggled row via a disclosure anchor key, so the trigger stays under the pointer instead of the viewport chasing the end. - ChatView's hand-rolled streaming follow (double-rAF scrollToEnd on every data change) is gone; the app now only owns streaming adjustments during anchored-end-space mode, mirroring main. - timelineLiveFollowEnabled state mirrors the follow refs so the render-visible gate switches native follow off when a gesture breaks follow and back on when the viewport returns to the end band. Timeline tests updated to assert the native-ownership invariants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Keep success feedback visible in the Git action control for 10 seconds - Move the running elapsed timer into the panel menu slot
…s with v2 Post-rebase reconciliation sweep: - Sidebar: main's folded Sidebar.tsx/Sidebar.logic.ts adapted to v2 shells (latestRun/runtime naming, waiting status instead of monitoring), with subagent-thread filtering and main's pinned-reorder helpers re-exported - Pinned drag reorder (pingdotgg#5581) ported into v2: thread.pin orderKey + thread.pin.reorder command, thread.pin-reordered event, Orchestrator fold, ProjectionStore/Maintenance, client-runtime commands and shell mapping - Project favicon (pingdotgg#4849-era) and defaultThreadEnvMode flowed through v2 contracts (OrchestrationProjectShell, application event payloads) - ChatView: main's pingdotgg#5592 header props, pull-request right-panel surfaces, liveAgentCount badge (pingdotgg#5745) wired into the v2 panel layout - enableAssistantStreaming -> enableLegacyTokenStreaming rename applied to v2 RunExecutionService and replay testkit - Removed v1 zombies resurrected by the rebase (provider service/reaper/ ingestion + v1 layer tests, server.test.ts, integration harness) - routeTree: main's tree + branch's /settings/scheduled-tasks route - Misc marker-sweep syntax repairs (rpc.ts, entities.ts, localApi.test.ts, rightPanelStore.test.ts, GitManager.test.ts, mobile model menu helpers) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 038-040 Main released ProjectionThreadsPinOrderKey (038), ProjectionProjectsDefaultThreadEnvMode (039) and ProjectionProjectFaviconPath (040), so the branch-private v2 stack shifts up by three. Registry ids were already 41-49; this renames the files and identifiers to match and updates the ledger expectations and through-id boundaries in the migration tests (released boundary 37 -> 40). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a6c9b41 (agents open pasted images): ClaudeAdapterV2 now grants the attachments dir alongside cwd via additionalDirectories and appends '[Attached ... is saved at: path]' lines to the turn text so tools can dereference pasted images (pixels alone are not tool-readable). - 5bb8c03 (settle leaves monitors running): thread.settle now joins archive/delete in the provider-session detach set, so PR monitors, dev servers and subagent fleets stop when the user parks the thread. The settle guard already rejects active runs, and serialized dispatch closes the re-engage race the v1 fix handled with onlyIfSettled. - e70cdb4 (Claude resume handshakes) and 2c7267a (reaper vs live background subagents) are already covered structurally in v2: results are turn-scoped with explicit zero-turn handshake drops, and idle release is pinned while background work is pending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e panel-visibility merge The keep-both merge nested main's plan-surface migration test inside a branch popover test and dropped the threadPanelVisibilityByThreadKey key from the migration results. Restore main's test body and include the branch's (empty) visibility map in the expected persisted shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n text Follow-up to the pingdotgg#5757 port: start and steer turns now append the '[Attached ... is saved at: path]' line, so the adapter fixtures assert it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The round-3 reconciliation took main's ChatHeader wholesale and wired its full prop set, resurrecting the scripts/open-in/git-actions cluster the branch had deliberately relocated into the thread panel. Restore the 79-line slim header (project favicon + name + thread title) and its minimal ChatView call. pingdotgg#5592's header actions stay a documented v2 follow-up, as decided in round 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add ready, merge, and conflict-resolution actions to the PR row - Share pull request action and handoff logic with the detail panel - Fix thread details scrolling and row alignment
…reens Round-4 reconciliation of pingdotgg#5986/pingdotgg#5988 with the v2 cutover files: - PendingUserInputCard adopts main's collapsible overlay redesign with the v2 RuntimeRequestId/responseCapability plumbing (dead provider processes still read-only the card) - ThreadFeed adopts the thread-feed-live-follow transition model, the user-scroll settle window, momentum handoff, and env-scoped feed keys while keeping the v2 nearListEnd layout gating - ThreadDetailScreen hides (not unmounts) the composer while a user-input request owns the slot; multi-select answers flow as arrays end to end (threadActivity toggle/build helpers + tests, ThreadUserInputQuestion) - ThreadComposer keeps the v2 canStopThread stop gate under main's onEditorFocusChange rename; standalone stop reuses the shared renderer - Restored the branch's steer stop/send tests alongside main's composer test suite Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| status: "active", | ||
| firstRunOrdinal: state.providerThread.firstRunOrdinal ?? turnInput.runOrdinal, | ||
| }); | ||
| yield* updateSession("running"); |
There was a problem hiding this comment.
🟡 Medium Adapters/PiAdapterV2.ts:1019
After startTurn calls set_model, providerSession.model is never updated, so updateSession("running") keeps the model from openSession and providerSession.model misreports the model actually running the turn. Consider updating providerSession.model to turnInput.modelSelection.model before or during updateSession("running") so session snapshots reflect the switch.
yield* updateThread(state, {
status: "active",
firstRunOrdinal: state.providerThread.firstRunOrdinal ?? turnInput.runOrdinal,
});
- yield* updateSession("running");
+ yield* updateSession("running", null, turnInput.modelSelection.model);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts around line 1019:
After `startTurn` calls `set_model`, `providerSession.model` is never updated, so `updateSession("running")` keeps the model from `openSession` and `providerSession.model` misreports the model actually running the turn. Consider updating `providerSession.model` to `turnInput.modelSelection.model` before or during `updateSession("running")` so session snapshots reflect the switch.
| }); | ||
| } | ||
|
|
||
| const models = modelsFromSettings( |
There was a problem hiding this comment.
🟡 Medium Layers/PiProvider.ts:243
authenticated is derived from the merged models list, which includes settings.customModels via providerModelsFromSettings. When RPC discovery fails or returns no models but the user has configured custom models, the provider is incorrectly reported as ready and authenticated, suppressing the warning that no models are available. Authentication should be determined from the discovered RPC models before merging custom settings.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/PiProvider.ts around line 243:
`authenticated` is derived from the merged `models` list, which includes `settings.customModels` via `providerModelsFromSettings`. When RPC discovery fails or returns no models but the user has configured custom models, the provider is incorrectly reported as `ready` and `authenticated`, suppressing the warning that no models are available. Authentication should be determined from the discovered RPC models before merging custom settings.
| Effect.forkScoped, | ||
| ); | ||
|
|
||
| const request = Effect.fn("PiRpcTransport.request")(function* ( |
There was a problem hiding this comment.
🟡 Medium Adapters/PiRpcTransport.ts:225
If a request fiber is interrupted after it registers its deferred in pendingRequests but before it completes, the map entry is never removed. A later response for that orphaned id is silently consumed by the abandoned deferred instead of being exposed on messages, and repeated cancelled requests leak entries until transport shutdown. The cleanup at the end of request only runs on normal completion — interruption skips it. Wrap the body in Effect.ensuring(() => Effect.sync(() => pendingRequests.delete(id))) so the entry is removed regardless of how the fiber terminates.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiRpcTransport.ts around line 225:
If a `request` fiber is interrupted after it registers its deferred in `pendingRequests` but before it completes, the map entry is never removed. A later response for that orphaned `id` is silently consumed by the abandoned deferred instead of being exposed on `messages`, and repeated cancelled requests leak entries until transport shutdown. The cleanup at the end of `request` only runs on normal completion — interruption skips it. Wrap the body in `Effect.ensuring(() => Effect.sync(() => pendingRequests.delete(id)))` so the entry is removed regardless of how the fiber terminates.
| >, | ||
| ) { | ||
| const now = yield* DateTime.now; | ||
| const nativeItemId = projected.toolCallId; |
There was a problem hiding this comment.
🟡 Medium Adapters/PiAdapterV2.ts:572
On tool.completed, emitTool overwrites the tool call's input with {}, so the final turn_item.updated event drops the arguments that were emitted on tool.started/tool.updated. Persisted completed tool calls end up with empty input. This is because projected.args is absent in the completion projection, and the code uses projected.type === "tool.completed" ? {} : projected.args. Track the last emitted args per toolCallId and reuse it on completion instead of resetting to {}.
| const nativeItemId = projected.toolCallId; | |
| const nativeItemId = projected.toolCallId; | |
| const lastArgs = turn.toolArgs.get(nativeItemId) ?? projected.args ?? {}; | |
| if (projected.type !== "tool.completed") turn.toolArgs.set(nativeItemId, projected.args ?? {}); |
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts around line 572:
On `tool.completed`, `emitTool` overwrites the tool call's `input` with `{}`, so the final `turn_item.updated` event drops the arguments that were emitted on `tool.started`/`tool.updated`. Persisted completed tool calls end up with empty input. This is because `projected.args` is absent in the completion projection, and the code uses `projected.type === "tool.completed" ? {} : projected.args`. Track the last emitted `args` per `toolCallId` and reuse it on completion instead of resetting to `{}`.
| threadDisposition: "reusable", | ||
| }, | ||
| ); | ||
| delete state.activeTurn; |
There was a problem hiding this comment.
🟡 Medium Adapters/PiAdapterV2.ts:810
When a Pi turn reaches terminal status, any pending runtime requests (extension UI prompts) for that turn are left in pendingPrompts and state.runtimeRequests with status "pending" forever. The corresponding nodes and turn items also remain "waiting". This means an interrupted or failed turn leaves stale approval/user-input requests that never resolve, and respondToRuntimeRequest will still accept and send responses for them afterward.
In handleProjected for run.terminal, the code finalizes content and deletes state.activeTurn but never iterates pendingPrompts to cancel or resolve entries belonging to the terminal turn. Consider resolving all pending prompts for that turn (emitting terminal node/turn-item/runtime-request updates and sending extension_ui_response with cancelled: true to the transport) before clearing state.activeTurn.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts around line 810:
When a Pi turn reaches terminal status, any pending runtime requests (extension UI prompts) for that turn are left in `pendingPrompts` and `state.runtimeRequests` with status `"pending"` forever. The corresponding nodes and turn items also remain `"waiting"`. This means an interrupted or failed turn leaves stale approval/user-input requests that never resolve, and `respondToRuntimeRequest` will still accept and send responses for them afterward.
In `handleProjected` for `run.terminal`, the code finalizes content and deletes `state.activeTurn` but never iterates `pendingPrompts` to cancel or resolve entries belonging to the terminal turn. Consider resolving all pending prompts for that turn (emitting terminal node/turn-item/runtime-request updates and sending `extension_ui_response` with `cancelled: true` to the transport) before clearing `state.activeTurn`.
| yield* Stream.fromQueue(outgoing).pipe( | ||
| Stream.run(child.stdin), | ||
| Effect.mapError((cause) => new PiRpcTransportError({ operation: "write", cause })), | ||
| Effect.catch((error) => Queue.fail(messages, error)), | ||
| Effect.forkScoped, | ||
| ); |
There was a problem hiding this comment.
🟠 High Adapters/PiRpcTransport.ts:209
When Stream.run(child.stdin) fails, only messages is failed — closed is never completed and outgoing is never ended. As a result, subsequent send calls still enqueue data and report success even though nothing is writing to the child, and request calls block until their full timeout instead of detecting the broken transport. The write-failure path should call markClosed (or at least complete closed and end outgoing) so callers see the transport is dead.
yield* Stream.fromQueue(outgoing).pipe(
Stream.run(child.stdin),
Effect.mapError((cause) => new PiRpcTransportError({ operation: "write", cause })),
- Effect.catch((error) => Queue.fail(messages, error)),
+ Effect.catch((error) =>
+ Effect.gen(function* () {
yield* Queue.fail(messages, error);
yield* markClosed();
}),
),
Effect.forkScoped,
);🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiRpcTransport.ts around lines 209-214:
When `Stream.run(child.stdin)` fails, only `messages` is failed — `closed` is never completed and `outgoing` is never ended. As a result, subsequent `send` calls still enqueue data and report success even though nothing is writing to the child, and `request` calls block until their full timeout instead of detecting the broken transport. The write-failure path should call `markClosed` (or at least complete `closed` and end `outgoing`) so callers see the transport is dead.
| Schema.Struct({ | ||
| type: Schema.Literal("message_update"), | ||
| message: PiMessage, | ||
| assistantMessageEvent: PiAssistantMessageEvent, | ||
| }), |
There was a problem hiding this comment.
🟠 High Adapters/PiRpcProtocol.ts:103
The message_update schema requires a message field, but Pi's RPC protocol omits message from message_update events and sends only assistantMessageEvent. Every normal streaming update therefore fails decodeKnownEvent and is converted to PiRpcProtocolError, so text_delta and thinking_delta deltas are never projected. Making message optional (or removing it) aligns the schema with the actual protocol.
| Schema.Struct({ | |
| type: Schema.Literal("message_update"), | |
| message: PiMessage, | |
| assistantMessageEvent: PiAssistantMessageEvent, | |
| }), | |
| Schema.Struct({ | |
| type: Schema.Literal("message_update"), | |
| message: Schema.optional(PiMessage), | |
| assistantMessageEvent: PiAssistantMessageEvent, | |
| }), |
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiRpcProtocol.ts around lines 103-107:
The `message_update` schema requires a `message` field, but Pi's RPC protocol omits `message` from `message_update` events and sends only `assistantMessageEvent`. Every normal streaming update therefore fails `decodeKnownEvent` and is converted to `PiRpcProtocolError`, so `text_delta` and `thinking_delta` deltas are never projected. Making `message` optional (or removing it) aligns the schema with the actual protocol.
| ...(timeoutMs === undefined ? {} : { timeoutMs }), | ||
| }; | ||
| } | ||
| if (method === "editor") { |
There was a problem hiding this comment.
🟡 Medium Adapters/PiRpcProtocol.ts:467
projectPiExtensionUiRequest drops the validated timeout for editor requests, even though the user-input prompt type supports timeoutMs and the select and input branches preserve it. An editor request carrying a timeout is returned without its deadline, so the runtime can keep accepting input after Pi has already timed out the request. The editor branch is missing the conditional timeoutMs property that the other branches include.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiRpcProtocol.ts around line 467:
`projectPiExtensionUiRequest` drops the validated `timeout` for `editor` requests, even though the `user-input` prompt type supports `timeoutMs` and the `select` and `input` branches preserve it. An editor request carrying a timeout is returned without its deadline, so the runtime can keep accepting input after Pi has already timed out the request. The `editor` branch is missing the conditional `timeoutMs` property that the other branches include.
| completedAt: null, | ||
| }; | ||
| state.providerTurns.set(String(providerTurn.id), providerTurn); | ||
| state.activeTurn = { |
There was a problem hiding this comment.
🟠 High Adapters/PiAdapterV2.ts:1006
startTurn emits active thread, running session, and running provider-turn updates and installs state.activeTurn before sending the prompt RPC. When requestData for the prompt rejects or times out, the function fails with ProviderAdapterTurnStartError, but the already-emitted active/running state and state.activeTurn are never rolled back and no terminal event is emitted — leaving the session and thread stuck running indefinitely. Move the state mutations and emissions to after the prompt RPC succeeds, or roll back state.activeTurn, reset the thread/session status, and emit a terminal event in the error path.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiAdapterV2.ts around line 1006:
`startTurn` emits `active` thread, `running` session, and `running` provider-turn updates and installs `state.activeTurn` *before* sending the `prompt` RPC. When `requestData` for the `prompt` rejects or times out, the function fails with `ProviderAdapterTurnStartError`, but the already-emitted `active`/`running` state and `state.activeTurn` are never rolled back and no terminal event is emitted — leaving the session and thread stuck running indefinitely. Move the state mutations and emissions to after the `prompt` RPC succeeds, or roll back `state.activeTurn`, reset the thread/session status, and emit a terminal event in the error path.
| isError: event.isError, | ||
| }, | ||
| ]; | ||
| case "agent_end": { |
There was a problem hiding this comment.
🟠 High Adapters/PiRpcProtocol.ts:346
projectPiEvent emits run.terminal on agent_end when willRetry is false, but agent_end only signals the end of one low-level run — compaction retries or queued continuations can still follow. Because consumers finalize and delete the active turn on run.terminal, any subsequent compaction or follow-up continuation output is silently dropped and the turn is reported complete prematurely. The terminal projection should be deferred to agent_settled (which means no continuation remains), while still carrying the final stop/error state from the last assistant message.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/PiRpcProtocol.ts around line 346:
`projectPiEvent` emits `run.terminal` on `agent_end` when `willRetry` is false, but `agent_end` only signals the end of one low-level run — compaction retries or queued continuations can still follow. Because consumers finalize and delete the active turn on `run.terminal`, any subsequent compaction or follow-up continuation output is silently dropped and the turn is reported complete prematurely. The terminal projection should be deferred to `agent_settled` (which means no continuation remains), while still carrying the final stop/error state from the last assistant message.
There was a problem hiding this comment.
Effect service conventions review of the new Pi provider modules. Three mechanical convention issues found in PiAdapterV2.ts / PiAdapterV2.test.ts; the transport, protocol, driver, provider-snapshot, and text-generation modules otherwise follow the repo's Effect conventions (subpath namespace imports, Schema.TaggedErrorClass failures with structural attributes and preserved cause, dependencies acquired with yield* Service in PiAdapterV2Driver.create / PiDriver.create / makePiTextGeneration, no ManagedRuntime/runPromise outside test boundaries).
Posted via Macroscope — Effect Service Conventions
| }; | ||
| const events = yield* Queue.unbounded< | ||
| ProviderAdapterV2Event, | ||
| import("../ProviderAdapter.ts").ProviderAdapterV2Error | Cause.Done<void> |
There was a problem hiding this comment.
Inline import("...") type expressions bypass the module's import list. ../ProviderAdapter.ts and @t3tools/contracts are already imported at the top of this file, so it would be more consistent to add ProviderAdapterV2Error (here), ProviderAdapterV2EnsureThreadInput (line 846), ProviderAdapterV2RuntimePolicy, ThreadId, and ModelSelection (lines 928-930) to those existing type imports and reference them directly.
Posted via Macroscope — Effect Service Conventions
| readonly instanceId: ProviderInstanceId; | ||
| readonly settings: PiSettings; | ||
| readonly environment: NodeJS.ProcessEnv; | ||
| readonly idAllocator: IdAllocatorV2Shape; |
There was a problem hiding this comment.
IdAllocatorV2Shape is the legacy standalone shape type. Consider referring to the inferred service interface instead — readonly idAllocator: IdAllocatorV2["Service"]; (as GrokAdapterV2Options already does) — and dropping the then-unused type IdAllocatorV2Shape from the import on line 33.
Posted via Macroscope — Effect Service Conventions
| import * as Stream from "effect/Stream"; | ||
| import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; | ||
|
|
||
| import { IdAllocatorV2, layer as idAllocatorLayer } from "../IdAllocator.ts"; |
There was a problem hiding this comment.
layer as idAllocatorLayer erases the service module namespace. Consider import * as IdAllocator from "../IdAllocator.ts"; and using IdAllocator.IdAllocatorV2 / IdAllocator.layer at the two use sites instead of the renamed layer export.
Posted via Macroscope — Effect Service Conventions
96891a3 to
993407d
Compare
519c42a to
4c55679
Compare
Summary
Adds Pi as a first-class, disabled-by-default coding-agent provider for the Orchestrator V2 architecture.
This is a stacked draft targeting the head branch of #2829 (
t3code/codex-turn-mapping); it can be retargeted tomainafter that PR lands.switch_sessionprovider/modelslugs and exposes Pi's seven thinking levelsWhy
T3 currently has first-party Codex, Claude, Cursor, Grok, OpenCode, and ACP integrations, but no Orchestrator V2 driver for Pi. Pi's official RPC mode provides the session, streaming, model-selection, tool, and extension UI primitives needed for a native adapter without scraping terminal output.
The implementation intentionally stays inside T3 because the provider, orchestration, settings, and presentation contracts are currently internal. Repository coordination and Forgejo authentication remain outside this driver; Pi can invoke the shared
forgejo-bridgeCLI through its normal shell tools without duplicating API or credential logic.User impact
After enabling Pi in provider settings, users can select discovered Pi models, choose supported thinking levels, run persistent coding sessions, resume them after T3 runtime restarts, steer or interrupt active work, answer extension prompts, attach images, and use Pi for T3's generated Git metadata.
Pi remains disabled by default and is marked Early Access.
Intentional limitations
Validation
pnpm exec vp test run src/orchestration-v2/Adapters/PiRpcTransport.test.ts src/orchestration-v2/Adapters/PiRpcProtocol.test.ts src/provider/Layers/PiProvider.test.ts src/provider/Drivers/PiDriver.test.ts src/orchestration-v2/Adapters/PiAdapterV2.test.ts src/textGeneration/PiTextGeneration.test.ts— 20 tests passedpnpm exec vp test run --project unit src/components/settings/ProviderSettingsForm.test.ts— 13 tests passedpnpm exec vp test run src/settings.test.ts— 34 tests passedReferences
Note
Add Pi coding agent as a new provider with orchestration, text generation, and settings UI
piprovider driver backed by the@earendil-works/pi-coding-agentnpm package, disabled by default withbinaryPath: 'pi'.PiAdapterV2implementing the full orchestration V2 interface: streaming text/tool events, interrupt/steer, extension UI approvals, thread snapshots, and image attachments via base64 encoding.PiTextGenerationfor structured RPC-backed generation of thread titles, commit messages, PR content, and branch names.PiProviderwith CLI health probing, RPC-based model discovery, and thinking-level capability descriptors for reasoning models.📊 Macroscope summarized 5c2ce2b. 16 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.