feat(workspace): Axis 2 Phase B — client→server workspace handshake
Wires the workspace handshake end to end so a session's workspace is bound
from the client's cwd at connect time and is event-sourced for replay.
- Go TUI sends Hello{workingDir=os.Getwd()} as the first WS frame on connect
(protocol.go encoder + client.go connect path; golden test pins the wire
format against the Kotlin discriminator).
- Server adds ClientMessage.Hello, stashes the per-connection workingDir, and
on session start resolves it through WorkspaceResolver's trust pipeline,
emits SessionWorkspaceBoundEvent (invariant #9), and threads the resolved
workspace into OrchestrationConfig for the live run. A Hello after the first
StartSession is ignored (warn); a rejected path binds the resolver fallback.
- Replay derives the workspace from the recorded event: SessionState gains
boundWorkspace, DefaultSessionReducer fills it from SessionWorkspaceBoundEvent,
and ReplayOrchestrator uses it (Path.of only, no filesystem re-query —
invariant #8) with graceful fallback to config for pre-Phase-B logs.
Absent Hello / null resolver degrades to the prior config-workspace behavior.
This commit is contained in:
@@ -6,6 +6,7 @@ package ws
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/correx/tui-go/internal/protocol"
|
||||
@@ -102,6 +103,9 @@ func (c *Client) Run(ctx context.Context) {
|
||||
backoff = initialBackoff
|
||||
attempt = 0
|
||||
c.emit(Status{Connected: true})
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
_ = conn.WriteMessage(websocket.TextMessage, protocol.Hello(cwd))
|
||||
}
|
||||
c.pump(ctx, conn)
|
||||
c.emit(Status{Reconnecting: true})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user