Description
Extend the TerminalSession protocol with a new side-channel rawStdout: AsyncStream<Data> that emits raw bytes before VT parsing. The Dialogue pipeline (Wave 2+) subscribes to this stream and feeds bytes into ClaudeStreamJSONParser without interfering with SwiftTerm rendering.
Spec: Epic #250 §4.1 (TerminalAbstraction changes), §4.3 (data flow).
Scope
1. TerminalAbstraction/TerminalSession.swift
Add to public protocol:
```swift
/// Raw stdout bytes emitted by the underlying PTY / gRPC stream, BEFORE VT
/// parsing. Subscribers use this to tap structured output (e.g., JSONL from
/// claude -p --output-format stream-json) in parallel with or instead of
/// SwiftTerm rendering. Multi-consumer safe.
var rawStdout: AsyncStream { get }
```
2. TerminalSwiftTerm/SwiftTermSession.swift
- In the PTY-master read loop: emit each received
Data chunk to both SwiftTerm.feed(byteArray:) and the rawStdout continuation.
- Back-pressure policy:
.bufferingNewest(256) — dropping older bytes if consumer lags. Log a warning when drops happen.
3. RemoteTerminal/RemoteTerminalSession.swift
- In the gRPC
ServerMessage inbound loop, for each .stdout_data(Data) case — emit to both the SwiftTerm view and the rawStdout continuation.
- Same back-pressure policy as local.
4. Stream lifecycle
- Continuation created in
init, finished in terminate() / deinit.
rawStdout usable across multiple for await consumers — if Swift's AsyncStream is single-consumer by default, wrap with a broadcast channel (MulticastAsyncSequence — simple actor-backed helper: subscribers array + emit-to-all).
Acceptance Criteria
Relationships
Description
Extend the
TerminalSessionprotocol with a new side-channelrawStdout: AsyncStream<Data>that emits raw bytes before VT parsing. The Dialogue pipeline (Wave 2+) subscribes to this stream and feeds bytes intoClaudeStreamJSONParserwithout interfering with SwiftTerm rendering.Spec: Epic #250 §4.1 (TerminalAbstraction changes), §4.3 (data flow).
Scope
1.
TerminalAbstraction/TerminalSession.swiftAdd to public protocol:
```swift
/// Raw stdout bytes emitted by the underlying PTY / gRPC stream, BEFORE VT
/// parsing. Subscribers use this to tap structured output (e.g., JSONL from
///
claude -p --output-format stream-json) in parallel with or instead of/// SwiftTerm rendering. Multi-consumer safe.
var rawStdout: AsyncStream { get }
```
2.
TerminalSwiftTerm/SwiftTermSession.swiftDatachunk to bothSwiftTerm.feed(byteArray:)and therawStdoutcontinuation..bufferingNewest(256)— dropping older bytes if consumer lags. Log a warning when drops happen.3.
RemoteTerminal/RemoteTerminalSession.swiftServerMessageinbound loop, for each.stdout_data(Data)case — emit to both the SwiftTerm view and therawStdoutcontinuation.4. Stream lifecycle
init, finished interminate()/ deinit.rawStdoutusable across multiplefor awaitconsumers — if Swift'sAsyncStreamis single-consumer by default, wrap with a broadcast channel (MulticastAsyncSequence — simple actor-backed helper: subscribers array + emit-to-all).Acceptance Criteria
TerminalSession.rawStdoutdeclared inTerminalAbstractionwith documentation comment.SwiftTermSessionimplementsrawStdout; a test verifies that bytes written to PTY emerge on the stream (plaintext loopback test).RemoteTerminalSessionimplementsrawStdout; unit test with mocked gRPC stream confirms emission..shell/.agentTerminalsessions (regression-verified via existing tests).terminate()— consumer'sfor awaitloop exits cleanly.Sendablewarnings.Relationships