diff --git a/docs/superpowers/specs/2026-04-14-claude-acp-raw-sdk-message-support-design.md b/docs/superpowers/specs/2026-04-14-claude-acp-raw-sdk-message-support-design.md new file mode 100644 index 0000000..91b3c23 --- /dev/null +++ b/docs/superpowers/specs/2026-04-14-claude-acp-raw-sdk-message-support-design.md @@ -0,0 +1,160 @@ +# Claude ACP Raw SDK Message Support Design + +- Date: 2026-04-14 +- Status: Approved (implementation pending) +- Scope: Protocol support only (backend receives and forwards, frontend event layer receives) + +## 1. Background + +Upstream PR `agentclientprotocol/claude-agent-acp#527` was merged on **2026-04-13**. +It introduces opt-in emission of Claude Code SDK raw stream messages via ACP extension notification: + +- Method: `"_claude/sdkMessage"` +- Params shape: + - `sessionId: string` + - `message: object` (raw SDK message) + +Opt-in is configured via session `_meta`: + +```json +{ + "claudeCode": { + "emitRawSDKMessages": true + } +} +``` + +User decision for this project: + +- Keep npm/registry at current version (`0.27.0`) +- Implement **protocol support only** +- Enable raw SDK messages for Claude by default with `true` + +## 2. Goals and Non-Goals + +### Goals + +1. For `AgentType::ClaudeCode`, send `_meta.claudeCode.emitRawSDKMessages = true` in session setup. +2. Receive `"_claude/sdkMessage"` notifications in backend connection loop. +3. Convert and forward notifications to frontend via existing `acp://event` event bridge. +4. Extend frontend `AcpEvent` typing to accept this event without changing UI behavior. + +### Non-Goals + +1. No UI visualization for raw SDK messages in this change. +2. No DB persistence for raw SDK messages. +3. No behavior changes for non-Claude agents. +4. No protocol generalization for all extension notifications in this iteration. + +## 3. Selected Approach + +Selected approach: **Claude-specific minimal integration**. + +Why: + +- Minimal risk and smallest change surface. +- Matches explicit scope: protocol plumbing only. +- Preserves current runtime and rendering behavior. + +Rejected for now: + +- Global extension-notification bus (larger scope, more noise, higher maintenance). +- Persistence/debug history (extra storage and lifecycle complexity). + +## 4. Architecture Changes + +## 4.1 Backend session meta injection + +File: `src-tauri/src/acp/connection.rs` + +When constructing `NewSessionRequest` and `LoadSessionRequest`: + +- If `agent_type == AgentType::ClaudeCode`, set request `meta` with: + - `claudeCode.emitRawSDKMessages = true` + +This preserves opt-in semantics upstream while default-enabling for Claude in Codeg. + +## 4.2 Backend extension notification handling + +File: `src-tauri/src/acp/connection.rs` + +Current code mainly consumes typed `SessionNotification` updates. This change adds a path for untyped notifications: + +- Match dispatch notification method. +- If method is `"_claude/sdkMessage"`, parse params: + - `sessionId` + - `message` +- Emit new app event through `acp://event`. +- Ignore parse failures and continue session loop. + +## 4.3 Backend event model extension + +File: `src-tauri/src/acp/types.rs` + +Add a new `AcpEvent` variant: + +- `ClaudeSdkMessage` + - `connection_id: String` + - `session_id: String` + - `message: serde_json::Value` + +This keeps payload raw and unopinionated. + +## 4.4 Frontend type and event switch support + +Files: + +- `src/lib/types.ts` +- `src/contexts/acp-connections-context.tsx` + +Changes: + +- Extend TS `AcpEvent` union with: + - `type: "claude_sdk_message"` + - `connection_id: string` + - `session_id: string` + - `message: unknown` +- Add a no-op `case "claude_sdk_message"` in event handling. + +No UI state mutation is required in this iteration. + +## 5. Data Flow + +1. Codeg starts Claude ACP session. +2. Codeg sends `session/new` or `session/load` with `_meta.claudeCode.emitRawSDKMessages=true`. +3. `claude-agent-acp` emits extension notifications `"_claude/sdkMessage"`. +4. Codeg backend parses and maps to `AcpEvent::claude_sdk_message`. +5. Event is pushed via existing `acp://event` channel. +6. Frontend receives typed event and safely ignores it (for now). + +## 6. Error Handling and Compatibility + +1. If upstream ignores meta, session still works; feature simply produces no raw SDK events. +2. If `"_claude/sdkMessage"` payload is malformed, log and ignore that notification. +3. Do not fail prompt/session loops due to extension-message parse errors. +4. Keep all existing typed `SessionUpdate` flows unchanged. +5. Restrict meta injection to Claude only. + +## 7. Validation Plan + +Manual/protocol validation: + +1. Confirm session setup request includes `_meta.claudeCode.emitRawSDKMessages=true` for Claude. +2. Confirm `"_claude/sdkMessage"` notifications are received and forwarded as `claude_sdk_message` events. +3. Confirm malformed ext payloads do not break turns. +4. Confirm non-Claude agents have unchanged behavior. + +Project checks: + +1. `pnpm eslint .` +2. `pnpm build` +3. `cd src-tauri && cargo check` +4. `cd src-tauri && cargo check --bin codeg-server --no-default-features` + +## 8. Acceptance Criteria + +1. Claude connections default-enable raw SDK emission via session meta. +2. Backend forwards raw SDK notifications into frontend event layer. +3. Frontend compiles and receives new event type without UI regressions. +4. No persistence and no visual rendering changes. +5. No regressions on existing ACP message paths.