feat(control-plane): Unix socket + send verb (spine) #63

Merged
weiwen merged 3 commits from sandcastle/issue-57 into main 2026-07-08 03:00:34 +08:00
Owner

Summary

  • Adds evie daemon subcommand — runs Telegram/HTTP interfaces plus a Control Plane Unix socket at <config-dir>/evie.sock (mode 0600)
  • Adds evie send <chat_id> <text> CLI — connects to the daemon socket, delivers verbatim text to a Telegram chat (no pi), prints ok or an error
  • New src/control_plane.rs module with JSON-lines protocol over Unix socket, ControlRequest/ControlResponse serde types, ControlPlane listener + connect_and_request client helper
  • ChatId::from_str (parses tg-123456789 / api-alice display strings back to the enum)
  • nixos/module.nix updated: ExecStart now uses evie daemon

This is the spine for epic #55 — subsequent issues (58 prompt+query, 59 run, 60 schedules, 61 agent integration) all build on this socket infrastructure.

Test plan

  • just fmt passes
  • just check passes — 140 tests, 0 failed (14 new: 7 chat_id FromStr + 6 control_plane unit + 2 socket integration)
  • socket_path derives evie.sock adjacent to config
  • ControlRequest JSON round-trip via tagged serde enum
  • Socket listener returns "Telegram is not configured" when bot is absent
  • Invalid chat_id prefix returns a structured error response
  • nixos/module.nix uses evie daemon

Closes #57

🤖 Generated with Claude Code

## Summary - Adds `evie daemon` subcommand — runs Telegram/HTTP interfaces plus a Control Plane Unix socket at `<config-dir>/evie.sock` (mode 0600) - Adds `evie send <chat_id> <text>` CLI — connects to the daemon socket, delivers verbatim text to a Telegram chat (no `pi`), prints `ok` or an error - New `src/control_plane.rs` module with JSON-lines protocol over Unix socket, `ControlRequest`/`ControlResponse` serde types, `ControlPlane` listener + `connect_and_request` client helper - `ChatId::from_str` (parses `tg-123456789` / `api-alice` display strings back to the enum) - `nixos/module.nix` updated: `ExecStart` now uses `evie daemon` This is the spine for epic #55 — subsequent issues (58 prompt+query, 59 run, 60 schedules, 61 agent integration) all build on this socket infrastructure. ## Test plan - [x] `just fmt` passes - [x] `just check` passes — 140 tests, 0 failed (14 new: 7 `chat_id` FromStr + 6 `control_plane` unit + 2 socket integration) - [x] `socket_path` derives `evie.sock` adjacent to config - [x] `ControlRequest` JSON round-trip via tagged serde enum - [x] Socket listener returns "Telegram is not configured" when bot is absent - [x] Invalid chat_id prefix returns a structured error response - [x] `nixos/module.nix` uses `evie daemon` Closes #57 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(control-plane): add Unix socket + send verb (issue #57)
All checks were successful
CI / check (pull_request) Successful in 1m27s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
7b4340787b
Implements the Control Plane spine from issue #57 (part of epic #55):

- `evie daemon` — runs the existing Telegram/HTTP daemon AND binds a
  Unix domain socket at `<config-dir>/evie.sock` (mode 0600) for the
  Control Plane. Socket is removed on clean shutdown.
- `evie send <chat_id> <text>` — CLI that connects to the daemon socket,
  sends a JSON-lines `{"verb":"send",...}` request, prints "ok" or an
  error, and exits.
- `src/control_plane.rs` — new module: `ControlRequest`/`ControlResponse`
  types (serde tagged enum), `socket_path()` helper, `ControlPlane`
  listener (accepts connections, dispatches verbs), `connect_and_request`
  for the CLI side.
- `ChatId::from_str` — parses `tg-123`/`api-alice` display strings back
  to the enum; used by the send handler. Handles negative group IDs
  (`tg--123`).
- `nixos/module.nix` — `ExecStart` updated to `evie daemon` now that the
  subcommand is explicit.

Key decisions:
- `daemon` subcommand is now required (not a default); callers must opt in.
- Socket is `Arc<ControlPlane>`-shared per connection; one spawn per
  accepted connection; clean shutdown via `abort()` on the task handle.
- `send` to `api-` chats returns an error (no push channel exists).
- Link previews disabled on all send-verb messages (same as telegram/view.rs).

14 new tests (140 total, 0 failed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(control-plane): complete acceptance criteria for issue #57
All checks were successful
CI / check (pull_request) Successful in 1m26s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
113c917300
Implements remaining acceptance criteria from the issue spec:

**Socket path resolution** (`--socket → $EVIE_SOCKET → $XDG_RUNTIME_DIR/evie.sock → ~/.config/evie/evie.sock`):
- `resolve_socket_path(explicit, default)` function in control_plane.rs
- `default_socket_path()` returns `~/.config/evie/evie.sock` via dirs::config_dir
- `--socket <PATH>` flag on the CLI (global, applies to all verbs)
- `[rpc] socket_path` config key in daemon config overrides the resolved path
- NixOS module adds `Environment = "EVIE_SOCKET=/run/evie/evie.sock"` so the socket lands in the RuntimeDirectory

**Targeting chain** (`--chat → $EVIE_CHAT_ID → sole Telegram chat → error`):
- `evie send [--chat <CHAT_ID>] <text>` — `--chat` is now optional
- CLI resolves `--chat` → `$EVIE_CHAT_ID`, sends result (or None) to daemon
- Daemon's `resolve_chat_id` resolves None → sole configured Telegram chat, or returns a structured error for 0 or 2+ chats
- `chat_id` field on `ControlRequest::Send` is `Option<String>`; omitted from JSON when None

**`[rpc]` config section** — new `RpcConfig` struct added to `Config`

6 new tests (146 total, 0 failed): target resolution for sole/none/multiple
chats, socket_path resolution, chat_id deserialization with/without field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs(control-plane): document [rpc] socket_path config field
All checks were successful
CI / check (pull_request) Successful in 1m27s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
9b655571ac
Per CODING_STANDARDS §4, a new config field must be reflected in
default-config generation, README.md, and CONTEXT.md — not just the
struct. Adds the commented [rpc] section to the scaffolded default
config and README example, and documents the socket_path override plus
resolution chain in CONTEXT.md's Configuration term.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
weiwen merged commit 138ed92bc9 into main 2026-07-08 03:00:34 +08:00
weiwen deleted branch sandcastle/issue-57 2026-07-08 03:00:34 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!63
No description provided.