- New bin/dm-sign.sh: produce signed DMs (ssh-keygen -Y sign, namespace
dm) in the [from:X] [id:Y] wire format that dm.py verify-sig checks.
dm.py referenced it in usage but it never existed.
- dm.py: rewrite stale docstring/argparse (claimed verification was
removed / delivery unconfirmed — it does recipient-side read-back with
3 retries); unify all attribution on [from:X]/[id:Y] (was three
formats: [from X], [X], [from:X]); fix dm_thread double-attribution;
--no-verify kept as a documented no-op; raw mode sends verbatim and
reuses the embedded [id:Y] for the audit log; navigation/send/park
now all target the recipient browser (fixes cross-operator side-chat
sends); drop dead --from-sender flag.
- Register dm-signers/operator-main.pub.
- Round-trip verified: sign (operator-main) -> send --raw via opm
loopback -> read-back SENT+VERIFIED -> verify-sig GOOD.
chat-state-check.py: polls each node Chromium via CDP, captures main
chat + up to 5 side chats (last 10 DOM-visible messages each, 500
chars). Structural React selectors; picks the most common non-empty
list across repeated renders.
chat-state-report.py: signs and POSTs the report to the board
/api/box/chat-state/report ingest (namespace health, 5-min timer).
Session: sidechat/box-console
Attach a file to the agent chat composer via DOM.setFileInputFiles.
--dry-run stages the attachment and verifies the preview chip without
sending; otherwise sends (optional --message caption) and verifies via
read-back. Supports the box console upload flow (v0.2).
Session: sidechat/box-console
Root cause: agent-health.service runs Type=oneshot with the default
KillMode=control-group. restart_browser() spawned the replacement
chromium with nohup under the service, so systemd SIGKILLed it the
moment the service exited. Every 5-min tick: FAIL -> restart ->
RECOVERED -> SIGKILL at teardown. opm and pip were permanently dark
and dm.py read masked it (empty output, exit 0).
Fix: launch replacements via systemd-run --user --scope (backgrounded)
so the browser lives in a transient scope outside the service cgroup
and survives teardown. setsid does NOT escape either. Note:
systemd-run --scope waits for the scope even with --no-block
(verified 2026-10-03), hence the backgrounding. Same fix in
chromebox-watchdog.sh (timers currently off).
dm.py: dm_read() now uses run_full() and prints a WARNING to stderr
with rc + last error line instead of failing silently on empty reads.
Trailers: Session: sidechat/opm-blind-fix
onboard-driver.py accepts --account-name and forwards to muse-signin.py.
muse-signin.py detects Meta multi-account selection after OTP submit:
with a hint it clicks the matching display-name button (never the +1
collapsed entry); without a hint (or no match) it exits 3 so the queue
marks the job needs_human instead of stalling.
New bin/netvm-registry.py parses NODES.md (node -> cdp_port); every
consumer reads from it instead of hardcoding:
- onboard-driver.py: CDP_PORTS dict -> registry lookup (new nodes work)
- muse-signin.py: hardcoded 9410 -> --node/--cdp-port args
- muse-chat-api.py: hardcoded ACCOUNTS -> registry-built
- agent-health.sh: hardcoded muse/pip blocks -> loop over all active
nodes (646 and opm now get health coverage too)
NODES.md: record opm node (9440).
One command provisions a full client node: Warp identity (operator-
authorized 2026-10-03), netns + tunnel, chrome-box profile, NODES.md
registry entry with next-free 94x0 CDP port. Idempotent per stage.
netvm-names.sh gains CDP_PORT_OVERRIDE (backwards compatible) so the
CDP relay and the browser share the assigned port.