Documentation
¶
Overview ¶
Package daemon manages a single persistent `clawtool serve --listen --mcp-http` process the operator's hosts (Codex / OpenCode / Gemini / Claude Code) all fan into. Per ADR-014 (recursive) and the operator's design call: every host that registers clawtool as an MCP server should connect to the SAME backend so BIAM identity, task store, and notify channels are shared. Stdio-spawning a child per host would create N independent identities and N independent BIAM stores — cross-host notify cannot work that way.
State lives at $XDG_CONFIG_HOME/clawtool/daemon.json (LF-delimited, 0600). Token file (bearer) lives at $XDG_CONFIG_HOME/clawtool/ listener-token. Ensure starts the daemon if missing, returns the existing state otherwise; Stop SIGTERMs and cleans up.
This package is the only place that knows the daemon's process lifecycle. Adapters (mcp_host.go) and CLI (`clawtool daemon …`) drive it through Ensure / Stop / Status — they don't touch the state file directly.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FormatStatus ¶
FormatStatus renders the daemon state as a multi-line human string for `clawtool daemon status`. Used by the CLI; tests assert on substrings not whole layout.
func IsRunning ¶
IsRunning returns true when the recorded PID is alive AND the port still answers /v1/health within a short timeout. Both checks matter: a stale state file from a crashed daemon must not look healthy, and a port that no longer belongs to us (recycled by some other process) must not look ours.
func PortFromEnv ¶
PortFromEnv lets tests override the port pick (used in adapter integration tests where we don't actually want to spawn).
func ReadToken ¶
ReadToken returns the bearer token contents (whitespace-trimmed). Empty string + nil error if the file is missing — Ensure ensures the file exists before exposing the token to callers.
func StatePath ¶
func StatePath() string
StatePath returns the file Ensure / Stop persist to. Honors $XDG_CONFIG_HOME, else ~/.config/clawtool/daemon.json.
Types ¶
type State ¶
type State struct {
Version int `json:"version"`
PID int `json:"pid"`
Port int `json:"port"`
StartedAt time.Time `json:"started_at"`
TokenFile string `json:"token_file"`
LogFile string `json:"log_file"`
}
State is the persisted snapshot of a running daemon.
func Ensure ¶
Ensure starts the daemon if it isn't already running and returns the live State. Idempotent: if the daemon is already healthy, the existing state is returned without spawning.
Spawn flow: pick a free port, ensure the bearer token, fork the detached process, write state, poll /v1/health for up to 5s.
Concurrency: two CLI invocations within the spawn window (read-state → IsRunning → spawn → write-state) would both see "no daemon" and both fork, leaving an orphan racing for the state file + ports. We bracket the whole sequence with an OS advisory lock on a sibling .lock file (flock on POSIX, LockFileEx on Windows via fileLockExclusive). The fast path — a healthy daemon already running — does not need the lock; we re-check IsRunning inside the lock so a concurrent winner's state is observed before we duplicate-spawn.
func ReadState ¶
ReadState returns the persisted state, or (nil, nil) if no daemon has been started yet. Parse errors are returned verbatim so callers can decide whether to wipe + retry.