fix: run verb #66

Merged
weiwen merged 1 commit from issue-59 into main 2026-07-12 01:01:59 +08:00
Owner

Run Verb — daemon-spawned scripts with EVIE_* env, fire-and-forget

Closes #59 (parent #55). Adds the run delivery Verb: the Daemon spawns a local script/command with evie's env context populated and does not deliver its output anywhere. Because the daemon (not the CLI) spawns the process, immediate and (later) scheduled run will behave identically, matching the argv-wrap-replay design in ADR 0006 and leaving EVIE_SCHEDULE_NAME/EVIE_SCHEDULE_TIME for the scheduling slice.

What changed

Control plane (src/control_plane.rs)

  • New ControlRequest::Run { chat_id, argv } in the JSON-lines enum. argv[0] is the script path; the rest are its arguments. chat_id is optional and skip_serializing_if = "Option::is_none".
  • ControlPlane now owns its bound socket_path (injected as EVIE_SOCKET), so run(&socket_path) became run(); new/new_with_deliverer take socket_path. All existing tests and main.rs updated to the new signatures.
  • handle_run: resolves the target chat (any resolved chat accepted; api- targets just name the env context), spawns the child synchronously so a missing executable is reported to the caller (run failed to spawn: …), then detaches await_and_log_run and returns ok immediately — fire-and-forget, nothing delivered to the chat.
  • spawn_run_child: tokio::process::Command with EVIE_CHAT_ID + EVIE_SOCKET env, null stdin, piped stdout/stderr, kill_on_drop(true) so a detached run never orphans a child.
  • Logging: stdout→debug, stderr→warn, exit 0→info, non-zero/signal→warn, wait-failure→warn.
  • run_script (#[cfg(test)]): the transport-agnostic, awaitable dispatch-core seam returning ExitStatus; handle_run wraps it with chat resolution + fire-and-forget semantics.

CLI (src/main.rs)

  • evie run <script> [args…] with --chat <CHAT_ID>; builds argv (script + args) and sends ControlRequest::Run over the socket via the existing run_cli_verb path. The --chat help documents the $EVIE_CHAT_ID/sole-chat fallback and that the script calls evie send itself to message back.

Tests (11 new)

Deserialization (with/without chat_id, argv shape); env injection (EVIE_CHAT_ID/EVIE_SOCKET reach the script); argv passing (args after the script path reach $*); zero exit returns success status; non-zero exit returns the status (not an error — fire-and-forget); missing executable → NotFound spawn error; over-the-socket verb returns ok with no response; empty argv rejected; missing executable reported to caller; and an end-to-end callback test where a run script's EVIE_SOCKET equals the daemon's bound socket and a concurrent Send over that path is delivered to the chat — proving evie send "$EVIE_CHAT_ID" … round-trips through the injected env with no config.

Acceptance criteria

  • evie run <script> [args…] spawned by the daemon with EVIE_CHAT_ID + EVIE_SOCKET.
  • Fire-and-forget: no stdout delivered to the chat.
  • Logging stdout→debug, exit code→info, errors→warn.
  • A run script can call back into evie (evie send) via the injected env.
  • Dispatch-core seam tests spawn a temp script and assert injected env + exit-code handling.
  • Uses CONTEXT.md vocabulary (Run, Verb); respects ADR 0006 (daemon-owned spawn, single evie interface, fire-and-forget delivery verb).

Verification

cargo test is green (165 passed, 0 failed across repeated full-suite runs); cargo test run_ covers the new verb.

Known caveat

The write-then-exec tests occasionally hit ETXTBSY ("Text file busy") on the freshly written script under heavy parallel load (seen once, then 3/3 clean full-suite runs; passes reliably in isolation). spawn_run_child does not retry on ETXTBSY. The product path is unaffected — real scripts are not written-then-exec'd in the same syscall window — but a retry-on-ETXTBSY (or a sync/small delay in the test helper) would harden the suite.

Closes #59

## Run Verb — daemon-spawned scripts with `EVIE_*` env, fire-and-forget Closes #59 (parent #55). Adds the `run` delivery Verb: the **Daemon** spawns a local script/command with evie's env context populated and does not deliver its output anywhere. Because the daemon (not the CLI) spawns the process, immediate and (later) scheduled `run` will behave identically, matching the argv-wrap-replay design in ADR 0006 and leaving `EVIE_SCHEDULE_NAME`/`EVIE_SCHEDULE_TIME` for the scheduling slice. ### What changed **Control plane (`src/control_plane.rs`)** - New `ControlRequest::Run { chat_id, argv }` in the JSON-lines enum. `argv[0]` is the script path; the rest are its arguments. `chat_id` is optional and `skip_serializing_if = "Option::is_none"`. - `ControlPlane` now owns its bound `socket_path` (injected as `EVIE_SOCKET`), so `run(&socket_path)` became `run()`; `new`/`new_with_deliverer` take `socket_path`. All existing tests and `main.rs` updated to the new signatures. - `handle_run`: resolves the target chat (any resolved chat accepted; `api-` targets just name the env context), spawns the child **synchronously** so a missing executable is reported to the caller (`run failed to spawn: …`), then detaches `await_and_log_run` and returns `ok` immediately — fire-and-forget, nothing delivered to the chat. - `spawn_run_child`: `tokio::process::Command` with `EVIE_CHAT_ID` + `EVIE_SOCKET` env, null stdin, piped stdout/stderr, `kill_on_drop(true)` so a detached run never orphans a child. - Logging: stdout→debug, stderr→warn, exit 0→info, non-zero/signal→warn, wait-failure→warn. - `run_script` (`#[cfg(test)]`): the transport-agnostic, awaitable dispatch-core seam returning `ExitStatus`; `handle_run` wraps it with chat resolution + fire-and-forget semantics. **CLI (`src/main.rs`)** - `evie run <script> [args…]` with `--chat <CHAT_ID>`; builds `argv` (`script` + `args`) and sends `ControlRequest::Run` over the socket via the existing `run_cli_verb` path. The `--chat` help documents the `$EVIE_CHAT_ID`/sole-chat fallback and that the script calls `evie send` itself to message back. ### Tests (11 new) Deserialization (with/without `chat_id`, argv shape); env injection (`EVIE_CHAT_ID`/`EVIE_SOCKET` reach the script); argv passing (args after the script path reach `$*`); zero exit returns success status; non-zero exit returns the status (not an error — fire-and-forget); missing executable → `NotFound` spawn error; over-the-socket verb returns `ok` with no response; empty `argv` rejected; missing executable reported to caller; and an end-to-end **callback** test where a run script's `EVIE_SOCKET` equals the daemon's bound socket and a concurrent `Send` over that path is delivered to the chat — proving `evie send "$EVIE_CHAT_ID" …` round-trips through the injected env with no config. ### Acceptance criteria - [x] `evie run <script> [args…]` spawned by the daemon with `EVIE_CHAT_ID` + `EVIE_SOCKET`. - [x] Fire-and-forget: no stdout delivered to the chat. - [x] Logging stdout→debug, exit code→info, errors→warn. - [x] A run script can call back into evie (`evie send`) via the injected env. - [x] Dispatch-core seam tests spawn a temp script and assert injected env + exit-code handling. - [x] Uses CONTEXT.md vocabulary (Run, Verb); respects ADR 0006 (daemon-owned spawn, single `evie` interface, fire-and-forget delivery verb). ### Verification `cargo test` is green (165 passed, 0 failed across repeated full-suite runs); `cargo test run_` covers the new verb. ### Known caveat The write-then-exec tests occasionally hit `ETXTBSY` ("Text file busy") on the freshly written script under heavy parallel load (seen once, then 3/3 clean full-suite runs; passes reliably in isolation). `spawn_run_child` does not retry on `ETXTBSY`. The product path is unaffected — real scripts are not written-then-exec'd in the same syscall window — but a retry-on-`ETXTBSY` (or a `sync`/small delay in the test helper) would harden the suite. Closes #59
feat(control-plane): run verb (spawn script with EVIE_* env, fire-and-forget)
All checks were successful
CI / check (pull_request) Successful in 1m29s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 2s
04c77a36bd
weiwen merged commit ddd7cc3cb9 into main 2026-07-12 01:01:59 +08:00
weiwen deleted branch issue-59 2026-07-12 01:01:59 +08:00
weiwen referenced this pull request from a commit 2026-07-12 01:02:00 +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!66
No description provided.