Skip to content

refactor(cli): define server connection boundary#37133

Merged
kitlangton merged 5 commits into
v2from
server-effect-boundary
Jul 15, 2026
Merged

refactor(cli): define server connection boundary#37133
kitlangton merged 5 commits into
v2from
server-effect-boundary

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

What

Keep managed background-service lifecycle operations Effect-native until the actual CLI-to-TUI seam.

  • Rename the stateless resolver from Server to ServerConnection.
  • Group managed-only reconnect and restart capabilities under server.service.
  • Interpret those Effects once with the existing CLI runtime instead of creating nested runtimes with NodeFileSystem.layer.
  • Replace the TUI command vocabulary outright: server.reload and /reload become service.restart and /restart, with no compatibility alias.
  • Add a concise architecture map to the canonical service lifecycle design.

Before / After

Before

Server.resolve() mixed connection policy with runtime interpretation. Its delayed reconnect and reload callbacks called Effect.runPromise() themselves and installed a fresh NodeFileSystem.layer, even though the outer CLI runtime already provided FileSystem.

The resolved shape also exposed two independent optional callbacks and called stop-plus-start “reload”:

{
  endpoint,
  reconnect?: (...) => Promise<Endpoint>,
  reload?: () => Promise<void>,
}

After

ServerConnection.resolve() returns endpoint policy plus one optional managed-service capability whose methods return Effects:

{
  endpoint,
  service?: {
    reconnect(onStatus): Effect<Endpoint, ...>,
    restart(): Effect<void, ...>,
  },
}

The CLI default handler is the single adapter. It uses Effect.runPromiseWith over the existing outer FileSystem context and passes grouped Promise callbacks into the TUI.

How

  • CLI resolver: packages/cli/src/services/server-connection.ts chooses explicit, standalone, or managed connectivity and returns Effect-native managed lifecycle capabilities.
  • CLI/TUI seam: packages/cli/src/commands/handlers/default.ts converts lifecycle Effects to Promises once. Mini, run, and API handlers continue to consume only the endpoint.
  • TUI: packages/tui/src/app.tsx and packages/tui/src/context/client.tsx preserve the Promise seam and use canonical restart vocabulary.
  • Documentation: both troubleshooting surfaces now use /restart; docs/design/service-lifecycle.md records package ownership and the Effect seam with Merman-rendered terminal diagrams.
  • Tests: packages/cli/test/server-connection.test.ts uses a real local health endpoint to verify managed resolution, grouped capabilities, and Effect-native reconnect/restart methods.

Scope

  • Restart remains the existing behavior-preserving composition of Service.stop(options) followed by Service.start(options).
  • This does not add Service.restart() to the client lifecycle module.
  • This does not change process signaling, stop rejection, retries, election, or unresponsive-owner recovery.
  • Restart remains available only for the shared CLI-managed background service, not explicit --server or --standalone connections.
  • /reload and server.reload are intentionally removed without aliases. Current V2 users must use /restart and service.restart.
  • ServerConnection remains a stateless module; this PR does not add an Effect Context service or Layer.

Testing

  • bun run test in packages/cli: 32 passed.
  • bun run test in packages/tui: 249 passed, 1 skipped.
  • Focused resolver test: 1 passed.
  • Focused TUI lifecycle/reconnect tests: 3 passed.
  • bun typecheck in packages/cli: passed.
  • bun typecheck in packages/tui: passed.
  • bun run typecheck at workspace root: all 32 tasks passed.
  • Prettier check on changed files: passed.
  • Targeted Effect-pattern scan: passed.
  • Targeted Oxlint: no errors.
  • git diff --check: passed.

The repository-wide Effect-pattern scan still reports seven unrelated pre-existing findings in packages/core and packages/cli/src/mini/runtime.stdin.ts.

Flow

                       ╭───────────────────╮
                       │ CLI ServiceConfig │
                       ╰─────────┬─────────╯
                                 │
                                 ▼
                     ╭──────────────────────╮
                     │ CLI ServerConnection │
                     ╰───────────┬──────────╯
              ╭──────────────────╰───────────────────╮
              ▼                                      ▼
╭──────────────────────────╮            ╭─────────────────────────╮
│ Client Service lifecycle │            │ CLI runPromiseWith seam │
╰─────────────┬────────────╯            ╰─────────────┬───────────╯
              ╰─────╮                                 │
                    ▼                                 ▼
     ╭────────────────────────────╮            ╭─────────────╮
     │ Background service process │            │ TUI / Solid │
     ╰──────────────┬─────────────╯            ╰──────┬──────╯
                    │                                 │
                    ╰────────────◀────────────────────╯
                     ╭───────────────────────╮
                     │ Server HTTP transport │
                     ╰───────────┬───────────╯
                                 │
                                 ▼
                       ╭──────────────────╮
                       │ Core application │
                       ╰──────────────────╯

@kitlangton

Copy link
Copy Markdown
Contributor Author

Ran a native compiled lifecycle smoke on macOS using an isolated XDG home:

  • Started two real TUI processes against the same managed service; both shared service PID 16856.
  • Ran /restart from the first TUI. Registration moved to PID 17167; the old PID exited and exactly one daemon remained.
  • lsof confirmed both TUI processes reconnected to the replacement on 127.0.0.1:4098.
  • opencode2 service status returned the replacement endpoint.
  • Stopped both TUIs and the isolated service afterward.

The full Linux/Windows unit, E2E, typecheck, standards, and compliance matrix is also green.

@kitlangton
kitlangton merged commit f5dd181 into v2 Jul 15, 2026
7 checks passed
@kitlangton
kitlangton deleted the server-effect-boundary branch July 15, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant