add pane subsystem to the daemon + IPC event broadcast

Implements the Tier-1 pane subsystem from D-006: spawn / list / focus /
close / write / resize / tail commands, plus pane.spawned / output /
exit / resized / focused / closed events. PaneRegistry owns per-pane
PtySession lifecycles and id generation (p_N); a DaemonEventSink seam
keeps pane code decoupled from the IPC server package.

DaemonServer.broadcast() fans events out to every connected client.
Per-client subsystem/id filtering (`tail --filter pane:p_7`) is
deferred — Tier 1 broadcasts everything and the subscriber discards.

Panes carry a `kind:` field (terminal | claude). Step 7 (builtin.claude)
adds the claude-specific pane flow on top of this generic substrate —
the subsystem itself stays neutral.

14 new core tests: registry unit coverage (spawn → pane.spawned event,
output → base64 events, write/resize/close round-trips, idempotent
close, claude kind on the wire) plus dispatcher coverage (argv
validation, unknown-id → not-found, text vs bytes_b64, etc). All 37
core tests pass in ~3s under test-core.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-04-22 09:05:51 +02:00
co-authored by Claude
parent edd8a20e0d
commit 3715a2e191
10 changed files with 682 additions and 3 deletions
+16 -1
View File
@@ -67,10 +67,13 @@ Matches ADR 0006's exit-code contract:
Future<void> _runDaemon(List<String> args) async {
final socketPath = defaultSocketPath();
final dispatcher = DaemonDispatcher();
final server = DaemonServer(
late final DaemonServer server;
server = DaemonServer(
socketPath: socketPath,
dispatch: dispatcher.dispatch,
);
final registry = PaneRegistry(events: _ServerEventSink(server));
registerPaneCommands(dispatcher, registry);
final stopping = Completer<void>();
void shutdown(ProcessSignal sig) {
@@ -85,10 +88,22 @@ Future<void> _runDaemon(List<String> args) async {
await server.start();
await stopping.future;
await registry.shutdown();
await server.stop();
exit(0);
}
/// Thin adapter: the server doesn't `implement DaemonEventSink` itself
/// (that would tie ipc/ to panes/); instead the daemon entrypoint wraps
/// it at the seam where both are known.
class _ServerEventSink implements DaemonEventSink {
_ServerEventSink(this._server);
final DaemonServer _server;
@override
void emit(IpcEvent event) => _server.broadcast(event);
}
Future<void> _runCli(
String cmd,
List<String> args, {