Skip to content

D-5: TerminalSession — add rawStdout: AsyncStream<Data> hook #255

@kirich1409

Description

@kirich1409

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

  • TerminalSession.rawStdout declared in TerminalAbstraction with documentation comment.
  • SwiftTermSession implements rawStdout; a test verifies that bytes written to PTY emerge on the stream (plaintext loopback test).
  • RemoteTerminalSession implements rawStdout; unit test with mocked gRPC stream confirms emission.
  • Terminal rendering still works identically for existing .shell / .agentTerminal sessions (regression-verified via existing tests).
  • Back-pressure drops bytes only after 256-chunk buffer fills; drops logged.
  • Stream closes on terminate() — consumer's for await loop exits cleanly.
  • Swift 6 strict concurrency — no Sendable warnings.
  • No proto changes (runner unchanged).

Relationships

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions