| ... | @@ -1,549 +0,0 @@ |
| 1 | # Plan: Claude Code Integration (Orchestration Architecture) |
| 2 | |
| 3 | ## Why this plan was rewritten |
| 4 | |
| 5 | The previous plan targeted a pre-orchestration architecture (`ProviderManager`, provider-native WS event methods, and direct provider UI wiring). The current app now routes everything through: |
| 6 | |
| 7 | 1. `orchestration.dispatchCommand` (client intent) |
| 8 | 2. `OrchestrationEngine` (decide + persist + publish domain events) |
| 9 | 3. `ProviderCommandReactor` (domain intent -> `ProviderService`) |
| 10 | 4. `ProviderService` (adapter routing + canonical runtime stream) |
| 11 | 5. `ProviderRuntimeIngestion` (provider runtime -> internal orchestration commands) |
| 12 | 6. `orchestration.domainEvent` (single push channel consumed by web) |
| 13 | |
| 14 | Claude integration must plug into this path instead of reintroducing legacy provider-specific flows. |
| 15 | |
| 16 | --- |
| 17 | |
| 18 | ## Current constraints to design around (post-Stage 1) |
| 19 | |
| 20 | 1. Provider runtime ingestion expects canonical `ProviderRuntimeEvent` shapes, not provider-native payloads. |
| 21 | 2. Start input now uses typed `providerOptions` and generic `resumeCursor`; top-level provider-specific fields were removed. |
| 22 | 3. `resumeCursor` is intentionally opaque outside adapters and must never be synthesized from `providerThreadId`. |
| 23 | 4. `ProviderService` still requires adapter `startSession()` to return a `ProviderSession` with `threadId`. |
| 24 | 5. Checkpoint revert currently calls `providerService.rollbackConversation()`, so Claude adapter needs a rollback strategy compatible with current reactor behavior. |
| 25 | 6. Web currently marks Claude as unavailable (`"Claude Code (soon)"`) and model picker is Codex-only. |
| 26 | |
| 27 | --- |
| 28 | |
| 29 | ## Architecture target |
| 30 | |
| 31 | Add Claude as a first-class provider adapter that emits canonical runtime events and works with existing orchestration reactors without adding new WS channels or bypass paths. |
| 32 | |
| 33 | Key decisions: |
| 34 | |
| 35 | 1. Keep orchestration provider-agnostic; adapt Claude inside adapter/layer boundaries. |
| 36 | 2. Use the existing canonical runtime stream (`ProviderRuntimeEvent`) as the only ingestion contract. |
| 37 | 3. Keep provider session routing in `ProviderService` and `ProviderSessionDirectory`. |
| 38 | 4. Add explicit provider selection to turn-start intent so first turn can start Claude session intentionally. |
| 39 | |
| 40 | --- |
| 41 | |
| 42 | ## Phase 1: Contracts and command shape updates |
| 43 | |
| 44 | ### 1.1 Provider-aware model contract |
| 45 | |
| 46 | Update `packages/contracts/src/model.ts` so model resolution can be provider-aware instead of Codex-only. |
| 47 | |
| 48 | Expected outcomes: |
| 49 | |
| 50 | 1. Introduce provider-scoped model lists (Codex + Claude). |
| 51 | 2. Add helpers that resolve model by provider. |
| 52 | 3. Preserve backwards compatibility for existing Codex defaults. |
| 53 | |
| 54 | ### 1.2 Turn-start provider intent |
| 55 | |
| 56 | Update `packages/contracts/src/orchestration.ts`: |
| 57 | |
| 58 | 1. Add optional `provider: ProviderKind` to `ThreadTurnStartCommand`. |
| 59 | 2. Carry provider through `ThreadTurnStartRequestedPayload`. |
| 60 | 3. Keep existing command valid when provider is omitted. |
| 61 | |
| 62 | This removes the implicit “Codex unless session already exists” behavior as the only path. |
| 63 | |
| 64 | ### 1.3 Provider session start input for Claude runtime knobs (completed) |
| 65 | |
| 66 | Update `packages/contracts/src/provider.ts`: |
| 67 | |
| 68 | 1. Move provider-specific start fields into typed `providerOptions`: |
| 69 | - `providerOptions.codex` |
| 70 | - `providerOptions.claudeCode` |
| 71 | 2. Keep `resumeCursor` as the single cross-provider resume input in `ProviderSessionStartInput`. |
| 72 | 3. Deprecate/remove `resumeThreadId` from the generic start contract. |
| 73 | 4. Treat `resumeCursor` as adapter-owned opaque state. |
| 74 | |
| 75 | ### 1.4 Contract tests (completed) |
| 76 | |
| 77 | Update/add tests in `packages/contracts/src/*.test.ts` for: |
| 78 | |
| 79 | 1. New command payload shape. |
| 80 | 2. Provider-aware model resolution behavior. |
| 81 | 3. Breaking-change expectations for removed top-level provider fields. |
| 82 | |
| 83 | --- |
| 84 | |
| 85 | ## Phase 2: Claude adapter implementation |
| 86 | |
| 87 | ### 2.1 Add adapter service + layer |
| 88 | |
| 89 | Create: |
| 90 | |
| 91 | 1. `apps/server/src/provider/Services/ClaudeCodeAdapter.ts` |
| 92 | 2. `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts` |
| 93 | |
| 94 | Adapter must implement `ProviderAdapterShape<ProviderAdapterError>`. |
| 95 | |
| 96 | ### 2.1.a SDK dependency and baseline config |
| 97 | |
| 98 | Add server dependency: |
| 99 | |
| 100 | 1. `@anthropic-ai/claude-agent-sdk` |
| 101 | |
| 102 | Baseline adapter options to support from day one: |
| 103 | |
| 104 | 1. `cwd` |
| 105 | 2. `model` |
| 106 | 3. `pathToClaudeCodeExecutable` (from `providerOptions.claudeCode.binaryPath`) |
| 107 | 4. `permissionMode` (from `providerOptions.claudeCode.permissionMode`) |
| 108 | 5. `maxThinkingTokens` (from `providerOptions.claudeCode.maxThinkingTokens`) |
| 109 | 6. `resume` |
| 110 | 7. `resumeSessionAt` |
| 111 | 8. `includePartialMessages` |
| 112 | 9. `canUseTool` |
| 113 | 10. `hooks` |
| 114 | 11. `env` and `additionalDirectories` (if needed for sandbox/workspace parity) |
| 115 | |
| 116 | ### 2.2 Claude runtime bridge |
| 117 | |
| 118 | Implement a Claude runtime bridge (either directly in adapter layer or via dedicated manager file) that wraps Agent SDK query lifecycle. |
| 119 | |
| 120 | Required capabilities: |
| 121 | |
| 122 | 1. Long-lived session context per adapter session. |
| 123 | 2. Multi-turn input queue. |
| 124 | 3. Interrupt support. |
| 125 | 4. Approval request/response bridge. |
| 126 | 5. Resume support via opaque `resumeCursor` (parsed inside Claude adapter only). |
| 127 | |
| 128 | #### 2.2.a Agent SDK details to preserve |
| 129 | |
| 130 | The adapter should explicitly rely on these SDK capabilities: |
| 131 | |
| 132 | 1. `query()` returns an async iterable message stream and control methods (`interrupt`, `setModel`, `setPermissionMode`, `setMaxThinkingTokens`, account/status helpers). |
| 133 | 2. Multi-turn input is supported via async-iterable prompt input. |
| 134 | 3. Tool approval decisions are provided via `canUseTool`. |
| 135 | 4. Resume support uses `resume` and optional `resumeSessionAt`, both derived by parsing adapter-owned `resumeCursor`. |
| 136 | 5. Hooks can be used for lifecycle signals (`Stop`, `PostToolUse`, etc.) when we need adapter-originated checkpoint/runtime events. |
| 137 | |
| 138 | #### 2.2.b Effect-native session lifecycle skeleton |
| 139 | |
| 140 | ```ts |
| 141 | import { query } from "@anthropic-ai/claude-agent-sdk"; |
| 142 | import { Effect } from "effect"; |
| 143 | |
| 144 | const acquireSession = (input: ProviderSessionStartInput) => |
| 145 | Effect.acquireRelease( |
| 146 | Effect.tryPromise({ |
| 147 | try: async () => { |
| 148 | const claudeOptions = input.providerOptions?.claudeCode; |
| 149 | const resumeState = readClaudeResumeState(input.resumeCursor); |
| 150 | const abortController = new AbortController(); |
| 151 | const result = query({ |
| 152 | prompt: makePromptAsyncIterable(), |
| 153 | options: { |
| 154 | cwd: input.cwd, |
| 155 | model: input.model, |
| 156 | permissionMode: claudeOptions?.permissionMode, |
| 157 | maxThinkingTokens: claudeOptions?.maxThinkingTokens, |
| 158 | pathToClaudeCodeExecutable: claudeOptions?.binaryPath, |
| 159 | resume: resumeState?.threadId, |
| 160 | resumeSessionAt: resumeState?.sessionAt, |
| 161 | signal: abortController.signal, |
| 162 | includePartialMessages: true, |
| 163 | canUseTool: makeCanUseTool(), |
| 164 | hooks: makeClaudeHooks(), |
| 165 | }, |
| 166 | }); |
| 167 | return { abortController, result }; |
| 168 | }, |
| 169 | catch: (cause) => |
| 170 | new ProviderAdapterProcessError({ |
| 171 | provider: "claudeCode", |
| 172 | sessionId: "pending", |
| 173 | detail: "Failed to start Claude runtime session.", |
| 174 | cause, |
| 175 | }), |
| 176 | }), |
| 177 | ({ abortController }) => Effect.sync(() => abortController.abort()), |
| 178 | ); |
| 179 | ``` |
| 180 | |
| 181 | #### 2.2.c AsyncIterable -> Effect Stream integration |
| 182 | |
| 183 | Preferred when available in the pinned Effect version: |
| 184 | |
| 185 | ```ts |
| 186 | const sdkMessageStream = Stream.fromAsyncIterable( |
| 187 | session.result, |
| 188 | (cause) => |
| 189 | new ProviderAdapterProcessError({ |
| 190 | provider: "claudeCode", |
| 191 | sessionId, |
| 192 | detail: "Claude runtime stream failed.", |
| 193 | cause, |
| 194 | }), |
| 195 | ); |
| 196 | ``` |
| 197 | |
| 198 | Portable fallback (already aligned with current server patterns): |
| 199 | |
| 200 | ```ts |
| 201 | const queue = yield* Queue.unbounded<ClaudeSdkMessage>(); |
| 202 | yield* Effect.forkScoped( |
| 203 | Effect.tryPromise({ |
| 204 | try: async () => { |
| 205 | for await (const message of session.result) { |
| 206 | Queue.offerAllUnsafe(queue, [message]); |
| 207 | } |
| 208 | }, |
| 209 | catch: (cause) => |
| 210 | new ProviderAdapterProcessError({ |
| 211 | provider: "claudeCode", |
| 212 | sessionId, |
| 213 | detail: "Claude runtime stream pump failed.", |
| 214 | cause, |
| 215 | }), |
| 216 | }), |
| 217 | ); |
| 218 | const sdkMessageStream = Stream.fromQueue(queue); |
| 219 | ``` |
| 220 | |
| 221 | #### 2.2.d Multi-turn prompt queue pattern (Effect) |
| 222 | |
| 223 | Use an Effect queue as the single input boundary: |
| 224 | |
| 225 | ```ts |
| 226 | const promptQueue = yield* Queue.unbounded<ClaudePromptEnvelope>(); |
| 227 | |
| 228 | const prompt: AsyncIterable<ClaudePromptEnvelope> = { |
| 229 | [Symbol.asyncIterator]() { |
| 230 | return { |
| 231 | next: async () => { |
| 232 | const item = await Effect.runPromise(Queue.take(promptQueue)); |
| 233 | if (item.type === "terminate") { |
| 234 | return { done: true, value: undefined }; |
| 235 | } |
| 236 | return { done: false, value: item }; |
| 237 | }, |
| 238 | }; |
| 239 | }, |
| 240 | }; |
| 241 | ``` |
| 242 | |
| 243 | `sendTurn()` enqueues a user envelope, while `stopSession()` enqueues terminate and aborts. |
| 244 | |
| 245 | #### 2.2.e Approval bridge with `canUseTool` |
| 246 | |
| 247 | Map SDK approval checks to existing orchestration approval flows: |
| 248 | |
| 249 | ```ts |
| 250 | const canUseTool = async (toolName: string, toolInput: Record<string, unknown>) => { |
| 251 | if (approvalModeIsFullAccess(sessionId)) { |
| 252 | return { behavior: "allow", updatedInput: toolInput }; |
| 253 | } |
| 254 | |
| 255 | const requestId = ApprovalRequestId.makeUnsafe(crypto.randomUUID()); |
| 256 | emitRuntimeEvent({ |
| 257 | type: "approval.requested", |
| 258 | provider: "claudeCode", |
| 259 | sessionId, |
| 260 | requestId, |
| 261 | requestKind: classifyTool(toolName), |
| 262 | detail: summarizeToolRequest(toolName, toolInput), |
| 263 | // ... eventId/createdAt |
| 264 | }); |
| 265 | |
| 266 | const decision = await waitForApprovalDecision(requestId); |
| 267 | emitRuntimeEvent({ |
| 268 | type: "approval.resolved", |
| 269 | provider: "claudeCode", |
| 270 | sessionId, |
| 271 | requestId, |
| 272 | decision, |
| 273 | // ... eventId/createdAt |
| 274 | }); |
| 275 | |
| 276 | return decision === "accept" || decision === "acceptForSession" |
| 277 | ? { behavior: "allow", updatedInput: toolInput } |
| 278 | : { behavior: "deny", message: "User declined tool execution." }; |
| 279 | }; |
| 280 | ``` |
| 281 | |
| 282 | #### 2.2.f Hooks and checkpoint signals |
| 283 | |
| 284 | If Claude hooks provide cleaner turn boundaries, convert them into canonical runtime events (`turn.completed` and optionally `checkpoint.captured`) so `CheckpointReactor` remains unchanged: |
| 285 | |
| 286 | ```ts |
| 287 | hooks: { |
| 288 | Stop: [ |
| 289 | { |
| 290 | matcher: {}, |
| 291 | hooks: [ |
| 292 | async () => { |
| 293 | emitRuntimeEvent({ |
| 294 | type: "checkpoint.captured", |
| 295 | provider: "claudeCode", |
| 296 | sessionId, |
| 297 | threadId, |
| 298 | turnId: activeTurnId, |
| 299 | turnCount: nextTurnCount(), |
| 300 | // ... eventId/createdAt |
| 301 | }); |
| 302 | }, |
| 303 | ], |
| 304 | }, |
| 305 | ], |
| 306 | } |
| 307 | ``` |
| 308 | |
| 309 | #### 2.2.g Runtime control method wiring |
| 310 | |
| 311 | Adapter should expose SDK controls through existing service methods and runtime payload updates: |
| 312 | |
| 313 | 1. `interruptTurn()` -> `result.interrupt()` |
| 314 | 2. Model override on send turn -> `result.setModel(model)` before enqueuing prompt |
| 315 | 3. Runtime mode changes -> `result.setPermissionMode(mode)` if we support live mode switching |
| 316 | 4. Thinking budget updates -> `result.setMaxThinkingTokens(tokens)` when configured |
| 317 | |
| 318 | ### 2.3 Canonical event mapping |
| 319 | |
| 320 | Map Claude SDK events to `ProviderRuntimeEvent`: |
| 321 | |
| 322 | 1. `session.started` / `session.exited` |
| 323 | 2. `turn.started` / `turn.completed` |
| 324 | 3. `message.delta` / `message.completed` |
| 325 | 4. `tool.started` / `tool.completed` |
| 326 | 5. `approval.requested` / `approval.resolved` |
| 327 | 6. `runtime.error` |
| 328 | |
| 329 | No provider-native event methods should leak beyond the adapter boundary. |
| 330 | |
| 331 | Reference mapping table: |
| 332 | |
| 333 | | Claude SDK message/callback | Canonical runtime event(s) | Notes | |
| 334 | |---|---|---| |
| 335 | | assistant partial text | `message.delta` | Emit deltas only (do not resend full content each chunk). | |
| 336 | | assistant final message | `message.completed` | Must include stable `itemId` for dedupe/finalize behavior. | |
| 337 | | result (success) | `turn.completed` (`status: "completed"`) | This is the main turn boundary for checkpointing. | |
| 338 | | result (interrupted/cancelled) | `turn.completed` (`status: "interrupted"`/`"cancelled"`) | Preserve state semantics used by ingestion + UI. | |
| 339 | | result (error) | `runtime.error` and `turn.completed` (`status: "failed"`) | Include concise error message. | |
| 340 | | tool start | `tool.started` | Set `toolKind` using shared classifier (`command`, `file-change`, `other`). | |
| 341 | | tool result | `tool.completed` | Include detail summary used by activity feed. | |
| 342 | | canUseTool prompt | `approval.requested` | Source of request id shown in UI. | |
| 343 | | approval decision returned | `approval.resolved` | Must correlate using same request id. | |
| 344 | | session initialization | `session.started`, optional `thread.started` | Ensure `threadId` is emitted early and consistently. | |
| 345 | | process exit/close | `session.exited` | Required for session status projection cleanup. | |
| 346 | |
| 347 | ### 2.4 Session/thread/resume identifiers |
| 348 | |
| 349 | Define explicit adapter semantics: |
| 350 | |
| 351 | 1. `sessionId`: adapter-owned stable session id. |
| 352 | 2. `threadId`: Claude conversation/session identifier returned as `ProviderThreadId`. |
| 353 | 3. `resumeCursor`: provider-specific cursor (for example thread id + message cursor) needed for precise recovery/rollback. |
| 354 | 4. Orchestration/shared services persist and forward `resumeCursor` unchanged without provider-specific parsing. |
| 355 | |
| 356 | ### 2.5 Rollback/read strategy |
| 357 | |
| 358 | Implement `readThread()` and `rollbackThread()` in a way compatible with `CheckpointReactor`. |
| 359 | |
| 360 | Preferred: |
| 361 | |
| 362 | 1. Track per-turn resume cursors and reconstruct session state on rollback. |
| 363 | 2. Keep adapter `sessionId` stable across internal rehydration/restart. |
| 364 | |
| 365 | Fallback (explicitly documented if chosen): |
| 366 | |
| 367 | 1. Filesystem revert succeeds even when conversation rewind is partial. |
| 368 | 2. Reactor behavior is updated to surface a clear warning activity instead of silent drift. |
| 369 | |
| 370 | ### 2.6 Adapter unit tests |
| 371 | |
| 372 | Add tests mirroring Codex adapter coverage: |
| 373 | |
| 374 | 1. Provider validation (`provider === "claudeCode"`). |
| 375 | 2. Event mapping to canonical runtime events. |
| 376 | 3. Approval request lifecycle. |
| 377 | 4. Resume/recovery behavior. |
| 378 | 5. Rollback behavior. |
| 379 | |
| 380 | --- |
| 381 | |
| 382 | ## Phase 3: Register adapter in runtime composition |
| 383 | |
| 384 | ### 3.1 Adapter registry wiring |
| 385 | |
| 386 | Update: |
| 387 | |
| 388 | 1. `apps/server/src/provider/Layers/ProviderAdapterRegistry.ts` |
| 389 | |
| 390 | Register both Codex and Claude adapters. |
| 391 | |
| 392 | ### 3.2 Server layer composition |
| 393 | |
| 394 | Update: |
| 395 | |
| 396 | 1. `apps/server/src/serverLayers.ts` |
| 397 | |
| 398 | Provide `ClaudeCodeAdapterLive` alongside Codex in `makeServerProviderLayer()`. |
| 399 | |
| 400 | ### 3.3 Composition tests |
| 401 | |
| 402 | Update integration tests to ensure: |
| 403 | |
| 404 | 1. Registry exposes Claude provider. |
| 405 | 2. Provider service can route Claude sessions. |
| 406 | |
| 407 | --- |
| 408 | |
| 409 | ## Phase 4: Orchestration command/reactor updates |
| 410 | |
| 411 | ### 4.1 Decider propagation (completed) |
| 412 | |
| 413 | Update `apps/server/src/orchestration/decider.ts`: |
| 414 | |
| 415 | 1. Carry optional `provider` from `thread.turn.start` command into `thread.turn-start-requested` event payload. |
| 416 | 2. Keep this behavior provider-agnostic (no provider-specific runtime fields in the event payload). |
| 417 | |
| 418 | ### 4.2 ProviderCommandReactor provider selection |
| 419 | |
| 420 | Update `apps/server/src/orchestration/Layers/ProviderCommandReactor.ts`: |
| 421 | |
| 422 | 1. Prefer provider from turn-start event payload when starting a new session. |
| 423 | 2. Fallback to existing thread session provider when payload omitted. |
| 424 | 3. Fallback to default provider only when neither is present. |
| 425 | 4. On restart/rebind, forward the runtime session's persisted `resumeCursor` as-is (no reconstruction from `providerThreadId`). |
| 426 | |
| 427 | Switch behavior policy (explicit in implementation): |
| 428 | |
| 429 | 1. If active session provider differs from requested provider, stop and recreate session before sending turn. |
| 430 | 2. Keep current provider when request omits provider. |
| 431 | |
| 432 | ### 4.3 Reactor/invariant tests |
| 433 | |
| 434 | Update/add tests in: |
| 435 | |
| 436 | 1. `apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts` |
| 437 | 2. `apps/server/src/orchestration/*.test.ts` as needed |
| 438 | |
| 439 | Validate first-turn provider choice, provider switching semantics, and backward compatibility when provider is omitted. |
| 440 | |
| 441 | --- |
| 442 | |
| 443 | ## Phase 5: Web provider UX enablement |
| 444 | |
| 445 | ### 5.1 Enable Claude option |
| 446 | |
| 447 | Update `apps/web/src/session-logic.ts`: |
| 448 | |
| 449 | 1. Mark Claude provider option as available. |
| 450 | |
| 451 | ### 5.2 Provider selection in composer |
| 452 | |
| 453 | Update `apps/web/src/components/ChatView.tsx`: |
| 454 | |
| 455 | 1. Add provider picker near model/runtime controls. |
| 456 | 2. Include selected provider in `thread.turn.start` command payload. |
| 457 | 3. Keep selected provider sticky per thread in client state (or derive from active session provider when present). |
| 458 | |
| 459 | ### 5.3 Provider-aware model picker |
| 460 | |
| 461 | Update model picker usage so model options reflect selected provider. |
| 462 | |
| 463 | ### 5.4 Web tests |
| 464 | |
| 465 | Update/add tests for: |
| 466 | |
| 467 | 1. Provider picker interactions. |
| 468 | 2. Turn-start command includes provider. |
| 469 | 3. Model list switches by provider. |
| 470 | |
| 471 | --- |
| 472 | |
| 473 | ## Phase 6: Checkpoint and revert compatibility |
| 474 | |
| 475 | ### 6.1 Validate checkpoint reactor expectations |
| 476 | |
| 477 | Exercise `apps/server/src/orchestration/Layers/CheckpointReactor.ts` flows with Claude: |
| 478 | |
| 479 | 1. Turn baseline capture. |
| 480 | 2. Turn completion checkpoint capture. |
| 481 | 3. Revert + provider rollback coordination. |
| 482 | |
| 483 | ### 6.2 Policy for partial rollback support |
| 484 | |
| 485 | If Claude cannot support full conversation rewind at parity with Codex: |
| 486 | |
| 487 | 1. Document behavior clearly. |
| 488 | 2. Emit explicit thread activity for degraded rollback behavior. |
| 489 | 3. Ensure filesystem state remains correct and predictable. |
| 490 | |
| 491 | ### 6.3 Checkpoint tests |
| 492 | |
| 493 | Add/update tests in checkpoint + provider service integration suites for Claude revert behavior. |
| 494 | |
| 495 | --- |
| 496 | |
| 497 | ## Phase 7: End-to-end hardening and observability |
| 498 | |
| 499 | ### 7.1 Integration tests |
| 500 | |
| 501 | Add integration coverage for: |
| 502 | |
| 503 | 1. `thread.turn.start` with `provider: "claudeCode"` from fresh thread. |
| 504 | 2. Session recovery after restart (provider session alias/recovery path). |
| 505 | 3. Approval request/respond flow. |
| 506 | 4. Interrupt behavior. |
| 507 | 5. Revert flow with rollback. |
| 508 | |
| 509 | ### 7.2 WS behavior regression check |
| 510 | |
| 511 | Ensure no contract regressions: |
| 512 | |
| 513 | 1. Client still consumes only `orchestration.domainEvent`. |
| 514 | 2. No new provider-specific WS channels introduced. |
| 515 | |
| 516 | ### 7.3 Logging |
| 517 | |
| 518 | Confirm both native and canonical provider logs remain useful with multi-adapter setup: |
| 519 | |
| 520 | 1. `provider-native.ndjson` |
| 521 | 2. `provider-canonical.ndjson` |
| 522 | |
| 523 | --- |
| 524 | |
| 525 | ## File checklist |
| 526 | |
| 527 | Likely remaining files to touch: |
| 528 | |
| 529 | 1. `apps/server/src/provider/Services/ClaudeCodeAdapter.ts` (new) |
| 530 | 2. `apps/server/src/provider/Layers/ClaudeCodeAdapter.ts` (new) |
| 531 | 3. `apps/server/src/provider/Layers/ProviderAdapterRegistry.ts` |
| 532 | 4. `apps/server/src/serverLayers.ts` |
| 533 | 5. `apps/server/src/orchestration/Layers/ProviderCommandReactor.ts` |
| 534 | 6. `apps/web/src/session-logic.ts` |
| 535 | 7. `apps/web/src/components/ChatView.tsx` |
| 536 | 8. Related tests under `apps/server/src/provider/Layers`, `apps/server/src/orchestration/Layers`, `apps/server/integration`, and `apps/web/src`. |
| 537 | |
| 538 | --- |
| 539 | |
| 540 | ## Delivery order |
| 541 | |
| 542 | 1. Claude adapter + unit tests on top of the new `providerOptions`/opaque-cursor contracts. |
| 543 | 2. Registry/layer wiring. |
| 544 | 3. Remaining reactor updates for provider-aware session selection/switching invariants. |
| 545 | 4. Web provider picker + provider-aware models. |
| 546 | 5. Checkpoint/revert compatibility. |
| 547 | 6. End-to-end integration tests and stabilization. |
| 548 | |
| 549 | This order keeps risk isolated and maintains a working orchestrated path at each stage. |