- liveness falls back to psutil where there is no /proc (macOS) - GET /feed + /feed/file: the hub pulls transcripts it has no mount for - POST /pair trades a one-time code for the worker's bearer; /unpair drops it - /health reports worker identity + permission mode - worker/install-macos.sh: venv, launchd agent bound to the Tailscale IP, prints the pairing string (addr/code/claude login) - Dockerfile copies every sidecar module (claude_cli was missing, so the in-container runner could not import) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
46 lines
1.7 KiB
Python
46 lines
1.7 KiB
Python
"""Write the worker's launchd agent plist (called by install-macos.sh, which
|
|
passes everything through the environment).
|
|
|
|
KeepAlive restarts the worker if it dies — including at login, when Tailscale
|
|
may not be up yet and binding its address fails for the first few seconds.
|
|
AbandonProcessGroup keeps launchd from killing in-flight `claude -p` runs when
|
|
the worker restarts: they're daemons (their own session), and a restarted
|
|
worker re-discovers them from its pidfiles, exactly like the host sidecar
|
|
under systemd's KillMode=process."""
|
|
|
|
import os
|
|
import plistlib
|
|
|
|
e = os.environ
|
|
logs = e["LOGS"]
|
|
plist = {
|
|
"Label": e["LABEL"],
|
|
"ProgramArguments": [e["PY"], os.path.join(e["SIDECAR"], "sidecar.py")],
|
|
"WorkingDirectory": e["SIDECAR"],
|
|
"RunAtLoad": True,
|
|
"KeepAlive": True,
|
|
"ThrottleInterval": 10,
|
|
"AbandonProcessGroup": True,
|
|
"ProcessType": "Interactive",
|
|
"StandardOutPath": os.path.join(logs, "worker.log"),
|
|
"StandardErrorPath": os.path.join(logs, "worker.log"),
|
|
"EnvironmentVariables": {
|
|
"HOME": os.path.expanduser("~"),
|
|
"PATH": e["RUN_PATH"],
|
|
"SIDECAR_BIND": e["BIND"],
|
|
"SIDECAR_PORT": e["PORT"],
|
|
"SIDECAR_CWD": e["CWD"],
|
|
# This Mac's own `claude` login (Keychain) is the only account here;
|
|
# the id is what the hub calls it. Remote Control is org-disabled on
|
|
# the work seat, and a work run must never list itself there anyway.
|
|
"SIDECAR_DEFAULT_ACCOUNT": e["ACCOUNT"],
|
|
"SIDECAR_REMOTE_CONTROL": "0",
|
|
"SIDECAR_LOG_DIR": os.path.join(logs, "runs"),
|
|
"CLAUDE_BIN": e["CLAUDE_BIN"],
|
|
"WORKER_STATE_DIR": e["STATE"],
|
|
"PYTHONUNBUFFERED": "1",
|
|
},
|
|
}
|
|
with open(e["PLIST"], "wb") as f:
|
|
plistlib.dump(plist, f)
|