Skip to content

Phase 2: Architecture & maintainability improvements#4

Merged
schovi merged 6 commits intomainfrom
refactor/phase2-architecture
Feb 18, 2026
Merged

Phase 2: Architecture & maintainability improvements#4
schovi merged 6 commits intomainfrom
refactor/phase2-architecture

Conversation

@schovi
Copy link
Owner

@schovi schovi commented Feb 18, 2026

Enhancement

The daemon server manages per-session state across 6 parallel maps that must stay in sync, duplicates session metadata between Session and SessionMeta structs, and requires 3 coordinated edits to add a new MCP tool. This PR reduces the internal bug surface area and makes the codebase easier to extend.

Solution

All per-session state now lives in a single sessionHandle struct stored in one map, eliminating the class of bugs where one map is updated but another is forgotten. The intermediate Session struct is removed entirely, with its fields promoted into sessionHandle. A Client.Exec method encapsulates the send+wait+read pattern, and the MCP tool registry uses a registration-based pattern where each tool is a single register() call.

Changes

Daemon server (internal/daemon/server.go):

  • Consolidate sessions, ptys, cmds, doneChans, frameDetectors, responders maps into single handles map[string]*sessionHandle
  • Remove Session struct, promote fields (name, pid, command, state, createdAt, stoppedAt) directly into sessionHandle
  • Extract waitForSettle helper from handleSnapshot, replacing two near-identical settle polling loops

Client (internal/daemon/client.go):

  • Add Client.Exec method with ExecOptions/ExecResult types, consolidating the send+wait+read sequence
  • Add protocol version handshake (ProtocolVersion=1) on every request

Protocol (internal/daemon/constants.go, server.go):

  • Daemon rejects version-mismatched clients with actionable error message
  • Version 0 (omitted) accepted for backward compatibility

MCP tools (internal/mcp/tools.go):

  • Replace parallel List() schema + Call() switch with register() pattern
  • Tool schemas extracted to package-level variables
  • List() and Call() are now generic iterators over registered entries

CLI (cmd/exec.go):

  • runExec delegates to client.Exec() instead of manual orchestration

Replaces sessions, ptys, cmds, doneChans, frameDetectors, and
responders maps with a single handles map[string]*sessionHandle.
Eliminates the risk of map synchronization drift across handlers.
Eliminates the intermediate Session struct that duplicated SessionMeta
fields. All session state now lives directly in sessionHandle, removing
one layer of indirection and a source of state drift.
Deduplicates the two near-identical settle polling loops in
handleSnapshot into a shared helper function.
Extracts the duplicated "send input, wait for output, return new
content" pattern from cmd/exec.go and mcp/tools.go into a single
Client.Exec method. Callers now use daemon.ExecOptions instead of
manually orchestrating Read/Send/ForOutput.

Also refactors MCP ToolRegistry to use registration-based pattern:
adding a tool is now a single register() call instead of coordinated
edits to List() schema, Call() switch, and handler method.
Client sends ProtocolVersion=1 with every request. Daemon rejects
mismatched versions with a clear error message telling the user to
restart the daemon. Version 0 (omitted) is accepted for backward
compatibility during rollout.
@schovi schovi marked this pull request as ready for review February 18, 2026 03:34
Daemon previously ran with stdout/stderr discarded, making crash
debugging impossible. Now supports --log-file to capture logs and
redirect stderr. Added panic recovery in handleConn and captureOutput
goroutines to log stack traces instead of crashing silently.

Also fixes unparam lint: waitForSettle return value was unused.
@schovi schovi merged commit 8813873 into main Feb 18, 2026
6 checks passed
@schovi schovi deleted the refactor/phase2-architecture branch February 18, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant