swarph-cli 0.69.8__tar.gz → 0.69.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swarph_cli-0.69.8/src/swarph_cli.egg-info → swarph_cli-0.69.9}/PKG-INFO +1 -1
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/pyproject.toml +1 -1
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/monitor.py +32 -3
- swarph_cli-0.69.9/src/swarph_cli/scripts/wake_watchdog.py +385 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/SOURCES.txt +1 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_644_monitor_supervision.py +32 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_729_wake_watchdog.py +2 -0
- swarph_cli-0.69.9/tests/test_739_wake_writer.py +431 -0
- swarph_cli-0.69.8/src/swarph_cli/scripts/wake_watchdog.py +0 -209
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/LICENSE +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/README.md +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/setup.cfg +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_board_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_group_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_import_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_init_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_main.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_security.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_service_app.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_service_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_version_command.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.69.8 → swarph_cli-0.69.9}/tests/test_windows_waker_installer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.69.
|
|
3
|
+
Version: 0.69.9
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.69.
|
|
7
|
+
version = "0.69.9"
|
|
8
8
|
description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -326,6 +326,34 @@ def _read_cgroup(pid: int) -> "str | None":
|
|
|
326
326
|
return None
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
def _service_leaf(text: "str | None") -> "str | None":
|
|
330
|
+
"""The .service leaf of a cgroup, or None.
|
|
331
|
+
|
|
332
|
+
user@<uid>.service is the session manager, not a supervisor. The leaf
|
|
333
|
+
keeps its .service suffix so a later reader can name the unit after
|
|
334
|
+
this process is gone.
|
|
335
|
+
"""
|
|
336
|
+
if not text:
|
|
337
|
+
return None
|
|
338
|
+
for line in text.splitlines():
|
|
339
|
+
leaf = line.rsplit("/", 1)[-1].strip()
|
|
340
|
+
if leaf.endswith(".service") and not leaf.startswith("user@"):
|
|
341
|
+
return leaf
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _supervisor_for_pidfile(explicit: "str | None") -> "str | None":
|
|
346
|
+
"""What start writes into monitor.pid.
|
|
347
|
+
|
|
348
|
+
An explicit claim wins. Otherwise read this process's own cgroup now,
|
|
349
|
+
while the pid is still ours. A later status must not open /proc/<pid>
|
|
350
|
+
for a dead monitor: that pid may have been reused.
|
|
351
|
+
"""
|
|
352
|
+
if explicit:
|
|
353
|
+
return explicit
|
|
354
|
+
return _service_leaf(_read_cgroup(os.getpid()))
|
|
355
|
+
|
|
356
|
+
|
|
329
357
|
def _derive_systemd_unit(pid: "int | None") -> "str | None":
|
|
330
358
|
"""The supervisor Linux already knows (#344 review): Windows has no
|
|
331
359
|
pid→task map so the pidfile CLAIM is the whole answer there, but on
|
|
@@ -589,10 +617,11 @@ def _cmd_start(args: argparse.Namespace) -> int:
|
|
|
589
617
|
)
|
|
590
618
|
return 0
|
|
591
619
|
|
|
620
|
+
recorded = _supervisor_for_pidfile(supervisor)
|
|
592
621
|
if args.once:
|
|
593
622
|
mesh.write_pidfile(
|
|
594
623
|
pidfile, self_name=self_name, sinks=sinks, poll_s=args.poll_s,
|
|
595
|
-
supervisor=
|
|
624
|
+
supervisor=recorded,
|
|
596
625
|
)
|
|
597
626
|
mesh._monitor_iteration(state)
|
|
598
627
|
return 0
|
|
@@ -602,10 +631,10 @@ def _cmd_start(args: argparse.Namespace) -> int:
|
|
|
602
631
|
print("swarph monitor: no fork() on this platform — running in the "
|
|
603
632
|
"foreground", file=sys.stderr)
|
|
604
633
|
return _run_pinned(state, pidfile, self_name, sinks, args.poll_s,
|
|
605
|
-
supervisor=
|
|
634
|
+
supervisor=recorded)
|
|
606
635
|
|
|
607
636
|
return _start_detached(state, pidfile, self_name, sinks, args.poll_s,
|
|
608
|
-
supervisor=
|
|
637
|
+
supervisor=recorded)
|
|
609
638
|
|
|
610
639
|
|
|
611
640
|
def _run_pinned(state, pidfile: Path, self_name: str, sinks: list, poll_s: int,
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Card #729. Every pass, a cell with nobody reading its inbox is an outage.
|
|
2
|
+
|
|
3
|
+
One DM per outage. A live watcher clears it, so the next gap can alert again.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Iterable
|
|
15
|
+
|
|
16
|
+
GAP_SECONDS = 60
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def writer_verdict(*, reader_alive: bool, monitor_rc: int) -> str:
|
|
20
|
+
"""The heartbeat is the reader. The pull monitor is the writer of inbox.log.
|
|
21
|
+
|
|
22
|
+
A live reader with monitor status 2 is writer-down. A live monitor with a
|
|
23
|
+
fresh heartbeat is healthy. This does not start a process.
|
|
24
|
+
"""
|
|
25
|
+
if not reader_alive:
|
|
26
|
+
return "reader-down"
|
|
27
|
+
if monitor_rc == 2:
|
|
28
|
+
return "writer-down"
|
|
29
|
+
return "healthy"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def writer_transition(down: bool, rec: dict) -> bool:
|
|
33
|
+
"""True only on the edge into writer-down.
|
|
34
|
+
|
|
35
|
+
A healthy pass clears the flag. A down pass does not set it: the caller
|
|
36
|
+
marks the alert only after the DM returns 0, so a lost send is retried.
|
|
37
|
+
"""
|
|
38
|
+
if not down:
|
|
39
|
+
rec["writer_down"] = False
|
|
40
|
+
return False
|
|
41
|
+
return not bool(rec.get("writer_down"))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def supervisor_from_cgroup(text: str | None) -> str:
|
|
45
|
+
"""The unit leaf of a cgroup listing, the same rule monitor status uses.
|
|
46
|
+
|
|
47
|
+
A swarph-monitor service, including the non-template names
|
|
48
|
+
swarph-monitor.service and swarph-monitor-<cell>.service, is the
|
|
49
|
+
supervisor. user@<uid>.service is the session manager, not one.
|
|
50
|
+
Anything else is unsupervised.
|
|
51
|
+
"""
|
|
52
|
+
if not text:
|
|
53
|
+
return "unsupervised"
|
|
54
|
+
for line in text.splitlines():
|
|
55
|
+
leaf = line.rsplit("/", 1)[-1].strip()
|
|
56
|
+
if not leaf.endswith(".service") or leaf.startswith("user@"):
|
|
57
|
+
continue
|
|
58
|
+
if leaf.startswith("swarph-monitor"):
|
|
59
|
+
return leaf
|
|
60
|
+
return "unsupervised"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def writer_alert(cell: str, recorded: str | None, *, owned: bool = False) -> str:
|
|
64
|
+
"""Name the supervisor recorded at start, or the enabled template unit."""
|
|
65
|
+
who = _alert_supervisor(recorded)
|
|
66
|
+
if who == "unsupervised" and owned:
|
|
67
|
+
who = f"swarph-monitor@{cell}.service"
|
|
68
|
+
if who == "unsupervised":
|
|
69
|
+
return f"{cell}: writer-down, unsupervised"
|
|
70
|
+
return f"{cell}: writer-down, supervised by {who}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def systemd_owns_monitor(cell: str, run=None) -> bool:
|
|
74
|
+
"""True when either scope has an enabled swarph-monitor@<cell> unit.
|
|
75
|
+
|
|
76
|
+
That result is part of the writer-down alert when the cgroup names no
|
|
77
|
+
swarph-monitor leaf. This function does not start a process.
|
|
78
|
+
"""
|
|
79
|
+
unit = f"swarph-monitor@{cell}.service"
|
|
80
|
+
runner = run if run is not None else subprocess.run
|
|
81
|
+
owned = False
|
|
82
|
+
for argv in (
|
|
83
|
+
["systemctl", "is-enabled", unit],
|
|
84
|
+
["systemctl", "--user", "is-enabled", unit],
|
|
85
|
+
):
|
|
86
|
+
try:
|
|
87
|
+
proc = runner(argv, capture_output=True, text=True,
|
|
88
|
+
encoding="utf-8", errors="replace", check=False)
|
|
89
|
+
except FileNotFoundError:
|
|
90
|
+
continue
|
|
91
|
+
if proc.returncode == 0:
|
|
92
|
+
owned = True
|
|
93
|
+
return owned
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def enforce_writers(cells: dict[str, str], *, status, run=None,
|
|
97
|
+
state: dict | None = None) -> list[str]:
|
|
98
|
+
"""Cells that just transitioned to writer-down.
|
|
99
|
+
|
|
100
|
+
Both systemd scopes are still checked. Nothing here starts a monitor.
|
|
101
|
+
Supervision stays with the unit. One name is returned per down edge.
|
|
102
|
+
"""
|
|
103
|
+
alert = []
|
|
104
|
+
state = state if state is not None else {}
|
|
105
|
+
for name, inbox in cells.items():
|
|
106
|
+
if not _channel_alive(inbox):
|
|
107
|
+
continue
|
|
108
|
+
try:
|
|
109
|
+
rc = int(status(name))
|
|
110
|
+
except (TypeError, ValueError, OSError):
|
|
111
|
+
continue
|
|
112
|
+
rec_owned = systemd_owns_monitor(name, run=run)
|
|
113
|
+
verdict = writer_verdict(reader_alive=True, monitor_rc=rc)
|
|
114
|
+
rec = state.setdefault(name, {})
|
|
115
|
+
rec["systemd_owned"] = rec_owned
|
|
116
|
+
if writer_transition(verdict == "writer-down", rec):
|
|
117
|
+
alert.append(name)
|
|
118
|
+
return alert
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _channel_alive(inbox: str, now: float | None = None) -> bool:
|
|
122
|
+
"""A channel server writes channel_heartbeat.json beside the inbox every 60 s.
|
|
123
|
+
|
|
124
|
+
`swarph channel-serve` takes no inbox argument, so no command line names the
|
|
125
|
+
path; a fresh heartbeat is the only evidence the channel is reading.
|
|
126
|
+
"""
|
|
127
|
+
hb = Path(inbox).parent / "channel_heartbeat.json"
|
|
128
|
+
try:
|
|
129
|
+
ts = float(json.loads(hb.read_text(encoding="utf-8")).get("ts") or 0)
|
|
130
|
+
except (OSError, ValueError, TypeError, AttributeError):
|
|
131
|
+
return False
|
|
132
|
+
return (time.time() if now is None else now) - ts < 180
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _watching(inbox: str, cmdlines: Iterable[str]) -> bool:
|
|
136
|
+
"""A reader is tail or dm_notify_filter with the inbox path as its own argument,
|
|
137
|
+
or a channel server whose heartbeat is fresh.
|
|
138
|
+
|
|
139
|
+
A process that merely mentions the path (a prompt, a log line) is not a reader.
|
|
140
|
+
"""
|
|
141
|
+
if _channel_alive(inbox):
|
|
142
|
+
return True
|
|
143
|
+
for cmd in cmdlines:
|
|
144
|
+
parts = cmd.split()
|
|
145
|
+
if inbox not in parts:
|
|
146
|
+
continue
|
|
147
|
+
if any(os.path.basename(p) == "tail" for p in parts):
|
|
148
|
+
return True
|
|
149
|
+
if any(p.endswith("dm_notify_filter") or p.endswith("dm_notify_filter.py") for p in parts):
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _cron_watched(name: str, crontab: str) -> bool:
|
|
155
|
+
"""An active crontab line that runs dm_wake.py for this cell is its poller."""
|
|
156
|
+
for line in crontab.splitlines():
|
|
157
|
+
body = line.strip()
|
|
158
|
+
if not body or body.startswith("#"):
|
|
159
|
+
continue
|
|
160
|
+
if name in body and "dm_wake.py" in body:
|
|
161
|
+
return True
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _timer_watched(name: str, timers: Iterable[str]) -> bool:
|
|
166
|
+
needle = f"swarph-codex-waker@{name}.timer"
|
|
167
|
+
return any(needle in line for line in timers)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def scan(cells: dict[str, str], cmdlines: Iterable[str], state: dict,
|
|
171
|
+
now: float, *, gap: float = GAP_SECONDS,
|
|
172
|
+
timers: Iterable[str] = (), crontab: str = "") -> list[str]:
|
|
173
|
+
"""Return the cells that should get the one DM for this outage."""
|
|
174
|
+
alert = []
|
|
175
|
+
timers = list(timers)
|
|
176
|
+
for name, inbox in cells.items():
|
|
177
|
+
rec = state.setdefault(name, {})
|
|
178
|
+
if (_watching(inbox, cmdlines) or _timer_watched(name, timers)
|
|
179
|
+
or _cron_watched(name, crontab)):
|
|
180
|
+
rec["missing_since"] = None
|
|
181
|
+
rec["outage"] = False
|
|
182
|
+
continue
|
|
183
|
+
since = rec.get("missing_since")
|
|
184
|
+
if since is None:
|
|
185
|
+
rec["missing_since"] = now
|
|
186
|
+
continue
|
|
187
|
+
if now - since > gap and not rec.get("outage"):
|
|
188
|
+
alert.append(name)
|
|
189
|
+
return alert
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cells_under(root: Path) -> dict[str, str]:
|
|
193
|
+
found = {}
|
|
194
|
+
if not root.is_dir():
|
|
195
|
+
return found
|
|
196
|
+
for child in sorted(root.iterdir()):
|
|
197
|
+
inbox = child / "mesh-sidecar" / "inbox.log"
|
|
198
|
+
if inbox.is_file():
|
|
199
|
+
found[child.name] = str(inbox)
|
|
200
|
+
return found
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def load_state(path: Path) -> dict:
|
|
204
|
+
try:
|
|
205
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
206
|
+
except (OSError, ValueError):
|
|
207
|
+
return {}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def save_state(path: Path, state: dict) -> None:
|
|
211
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
212
|
+
path.write_text(json.dumps(state), encoding="utf-8")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _listing(argv: list[str]) -> str:
|
|
216
|
+
"""A missing binary is an empty listing, which is also a host with no timers."""
|
|
217
|
+
try:
|
|
218
|
+
listed = subprocess.run(
|
|
219
|
+
argv, capture_output=True, text=True,
|
|
220
|
+
encoding="utf-8", errors="replace", check=False)
|
|
221
|
+
except FileNotFoundError:
|
|
222
|
+
return ""
|
|
223
|
+
return listed.stdout or ""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _crontab() -> str:
|
|
227
|
+
return _listing(["crontab", "-l"])
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _timer_lines() -> list[str]:
|
|
231
|
+
return _listing(
|
|
232
|
+
["systemctl", "--user", "list-timers", "--all", "--no-legend"]).splitlines()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _alert_supervisor(recorded: str | None) -> str:
|
|
236
|
+
"""A unit name recorded at start, or a cgroup blob from an older test.
|
|
237
|
+
|
|
238
|
+
A plain unit name is trusted: the monitor wrote it from its own cgroup
|
|
239
|
+
while the pid was still its own. Cgroup text is parsed the same way.
|
|
240
|
+
"""
|
|
241
|
+
if not recorded or not str(recorded).strip():
|
|
242
|
+
return "unsupervised"
|
|
243
|
+
text = str(recorded).strip()
|
|
244
|
+
if "/" in text or "\n" in text:
|
|
245
|
+
return supervisor_from_cgroup(text)
|
|
246
|
+
if text.startswith("user@"):
|
|
247
|
+
return "unsupervised"
|
|
248
|
+
return text
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def recorded_supervisor(root: Path, cell: str) -> str | None:
|
|
252
|
+
"""The supervisor field of monitor.pid.
|
|
253
|
+
|
|
254
|
+
The pid in that file may be dead, and a dead pid can be reused by an
|
|
255
|
+
unrelated process. The field was written at start and still answers.
|
|
256
|
+
This function does not open a process listing.
|
|
257
|
+
"""
|
|
258
|
+
pidfile = root / cell / "mesh-sidecar" / "monitor.pid"
|
|
259
|
+
try:
|
|
260
|
+
rec = json.loads(pidfile.read_text(encoding="utf-8"))
|
|
261
|
+
except (OSError, ValueError):
|
|
262
|
+
return None
|
|
263
|
+
if not isinstance(rec, dict):
|
|
264
|
+
return None
|
|
265
|
+
who = rec.get("supervisor")
|
|
266
|
+
if isinstance(who, str) and who.strip():
|
|
267
|
+
return who.strip()
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def mesh_send(swarph: str, to: str, sender: str, content: str,
|
|
272
|
+
token_file: str = "") -> int:
|
|
273
|
+
"""Send one FYI. A self-send is refused and logged. The token is a path."""
|
|
274
|
+
if sender and to and sender == to:
|
|
275
|
+
print(f"wake_watchdog: refusing self-send to {to}", file=sys.stderr)
|
|
276
|
+
return 2
|
|
277
|
+
argv = [swarph, "mesh", "send", to, "--as", sender, "--kind", "fyi",
|
|
278
|
+
"--content", content]
|
|
279
|
+
if token_file:
|
|
280
|
+
argv.extend(["--token-file", token_file])
|
|
281
|
+
proc = subprocess.run(argv, check=False)
|
|
282
|
+
return proc.returncode
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _swarph_bin() -> str:
|
|
286
|
+
explicit = os.environ.get("SWARPH_BIN", "")
|
|
287
|
+
if explicit:
|
|
288
|
+
return explicit
|
|
289
|
+
home = os.path.expanduser("~/.local/bin/swarph")
|
|
290
|
+
if os.path.isfile(home):
|
|
291
|
+
return home
|
|
292
|
+
return "swarph"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main(argv: list[str] | None = None) -> int:
|
|
296
|
+
p = argparse.ArgumentParser(prog="wake_watchdog")
|
|
297
|
+
p.add_argument("--dry-run", action="store_true",
|
|
298
|
+
help="write state and list unwatched cells; do not send")
|
|
299
|
+
p.add_argument("--as", dest="sender", default="",
|
|
300
|
+
help="cell the alert is sent as; the unit sets this")
|
|
301
|
+
p.add_argument("--escalate",
|
|
302
|
+
default=os.environ.get("WAKE_WATCHDOG_ESCALATE", "drop-on-meta-edge"),
|
|
303
|
+
help="awake peer who also gets the outage DM (default: drop-on-meta-edge)")
|
|
304
|
+
p.add_argument("--token-file", default="",
|
|
305
|
+
help="bearer token file for the service identity; the path is passed through, never the value")
|
|
306
|
+
args = p.parse_args(argv)
|
|
307
|
+
root = Path(os.environ.get("SWARPH_STATE_ROOT", os.path.expanduser("~/swarph_state")))
|
|
308
|
+
state_path = Path(os.environ.get(
|
|
309
|
+
"WAKE_WATCHDOG_STATE",
|
|
310
|
+
os.path.expanduser("~/.local/state/wake-watchdog.json")))
|
|
311
|
+
import time
|
|
312
|
+
cmdlines = _listing(["ps", "-eo", "args"]).splitlines()
|
|
313
|
+
timers = _timer_lines()
|
|
314
|
+
crontab = _crontab()
|
|
315
|
+
cells = cells_under(root)
|
|
316
|
+
state = load_state(state_path)
|
|
317
|
+
alert = scan(cells, cmdlines, state, time.time(), timers=timers, crontab=crontab)
|
|
318
|
+
sender = args.sender.strip()
|
|
319
|
+
escalate = (args.escalate or "").strip()
|
|
320
|
+
if not args.dry_run and escalate and sender and escalate == sender:
|
|
321
|
+
print("wake_watchdog: escalation peer equals the sender; refusing to send",
|
|
322
|
+
file=sys.stderr)
|
|
323
|
+
return 2
|
|
324
|
+
swarph = _swarph_bin()
|
|
325
|
+
|
|
326
|
+
def _status(name: str, _sw: str = swarph) -> int:
|
|
327
|
+
proc = subprocess.run(
|
|
328
|
+
[_sw, "monitor", "status", "--as", name],
|
|
329
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
330
|
+
check=False)
|
|
331
|
+
return proc.returncode
|
|
332
|
+
|
|
333
|
+
reported = enforce_writers(cells, status=_status, state=state)
|
|
334
|
+
save_state(state_path, state)
|
|
335
|
+
token_file = (args.token_file or "").strip()
|
|
336
|
+
for name in reported:
|
|
337
|
+
print(f"writer-down {name}", flush=True)
|
|
338
|
+
if args.dry_run or not sender:
|
|
339
|
+
continue
|
|
340
|
+
owned = bool(state.get(name, {}).get("systemd_owned"))
|
|
341
|
+
sent = mesh_send(
|
|
342
|
+
swarph, escalate or "", sender,
|
|
343
|
+
writer_alert(name, recorded_supervisor(root, name), owned=owned),
|
|
344
|
+
"")
|
|
345
|
+
if sent == 0:
|
|
346
|
+
state.setdefault(name, {})["writer_down"] = True
|
|
347
|
+
save_state(state_path, state)
|
|
348
|
+
if args.dry_run:
|
|
349
|
+
for name, inbox in cells.items():
|
|
350
|
+
if not (_watching(inbox, cmdlines) or _timer_watched(name, timers)
|
|
351
|
+
or _cron_watched(name, crontab)):
|
|
352
|
+
print(name, flush=True)
|
|
353
|
+
return 0
|
|
354
|
+
sender = args.sender.strip()
|
|
355
|
+
if not sender:
|
|
356
|
+
print("wake_watchdog: --as is empty; not marking any cell alerted", file=sys.stderr)
|
|
357
|
+
return 2
|
|
358
|
+
swarph = _swarph_bin()
|
|
359
|
+
rc = 0
|
|
360
|
+
for name in alert:
|
|
361
|
+
print(f"outage {name}", flush=True)
|
|
362
|
+
dm_rc = mesh_send(
|
|
363
|
+
swarph, name, sender, "your DM wake is dead, re-arm", token_file)
|
|
364
|
+
dm_ok = dm_rc == 0
|
|
365
|
+
card_argv = [swarph, "board", "cards", "say", "729", "--as", sender,
|
|
366
|
+
"--to", name, "--content", f"{name}: DM wake is dead, re-arm"]
|
|
367
|
+
if token_file:
|
|
368
|
+
card_argv.extend(["--token-file", token_file])
|
|
369
|
+
card = subprocess.run(card_argv, check=False)
|
|
370
|
+
esc_ok = True
|
|
371
|
+
if escalate and escalate != name:
|
|
372
|
+
esc_rc = mesh_send(
|
|
373
|
+
swarph, escalate, sender, f"{name}: DM wake is dead, re-arm",
|
|
374
|
+
token_file)
|
|
375
|
+
esc_ok = esc_rc == 0
|
|
376
|
+
if dm_ok and card.returncode == 0 and esc_ok:
|
|
377
|
+
state[name]["outage"] = True
|
|
378
|
+
save_state(state_path, state)
|
|
379
|
+
else:
|
|
380
|
+
rc = 1
|
|
381
|
+
return rc
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
raise SystemExit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.69.
|
|
3
|
+
Version: 0.69.9
|
|
4
4
|
Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -230,6 +230,7 @@ tests/test_729_dm_once.py
|
|
|
230
230
|
tests/test_729_wake_watchdog.py
|
|
231
231
|
tests/test_734_dreaming_enrich_degrade.py
|
|
232
232
|
tests/test_735_dm_mcp_tools.py
|
|
233
|
+
tests/test_739_wake_writer.py
|
|
233
234
|
tests/test_740_edit_project.py
|
|
234
235
|
tests/test_762_cards_edit_priority.py
|
|
235
236
|
tests/test_764_enrich_collapse.py
|
|
@@ -76,6 +76,38 @@ def test_pidfile_records_supervisor_when_given(tmp_path):
|
|
|
76
76
|
assert rec["supervisor"] == "task:Swarph cursor-win Monitor"
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def test_start_records_the_cgroup_unit_and_keeps_it_after_the_process(monkeypatch, tmp_path):
|
|
80
|
+
"""A scratch transient unit is written at start from this process's cgroup.
|
|
81
|
+
|
|
82
|
+
The pidfile is what a later watchdog reads. The key stays when the
|
|
83
|
+
process is gone; this test does not open another process's listing.
|
|
84
|
+
"""
|
|
85
|
+
_env(monkeypatch)
|
|
86
|
+
monkeypatch.setattr(monitor, "_verify_self_is_registered",
|
|
87
|
+
lambda *a, **k: (True, "registered"))
|
|
88
|
+
monkeypatch.setattr(mesh, "_monitor_iteration", lambda state: None)
|
|
89
|
+
monkeypatch.setattr(
|
|
90
|
+
monitor, "_read_cgroup",
|
|
91
|
+
lambda pid: "0::/user.slice/scratch-761.service\n")
|
|
92
|
+
assert _run(["start", "--once"], tmp_path) == 0
|
|
93
|
+
rec = json.loads((tmp_path / "monitor.pid").read_text(encoding="utf-8"))
|
|
94
|
+
assert rec["supervisor"] == "scratch-761.service"
|
|
95
|
+
assert rec["pid"] == os.getpid()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_start_does_not_record_the_session_manager(monkeypatch, tmp_path):
|
|
99
|
+
_env(monkeypatch)
|
|
100
|
+
monkeypatch.setattr(monitor, "_verify_self_is_registered",
|
|
101
|
+
lambda *a, **k: (True, "registered"))
|
|
102
|
+
monkeypatch.setattr(mesh, "_monitor_iteration", lambda state: None)
|
|
103
|
+
monkeypatch.setattr(
|
|
104
|
+
monitor, "_read_cgroup",
|
|
105
|
+
lambda pid: "0::/user.slice/user@1000.service\n")
|
|
106
|
+
assert _run(["start", "--once"], tmp_path) == 0
|
|
107
|
+
rec = json.loads((tmp_path / "monitor.pid").read_text(encoding="utf-8"))
|
|
108
|
+
assert "supervisor" not in rec
|
|
109
|
+
|
|
110
|
+
|
|
79
111
|
def test_pidfile_omits_supervisor_key_when_not_given(tmp_path):
|
|
80
112
|
"""Absent means ORPHAN — a MISSING key, not an empty string, so a reader
|
|
81
113
|
never has to guess whether '' is a claim."""
|
|
@@ -78,6 +78,8 @@ def test_main_guard_is_required():
|
|
|
78
78
|
unit = Path("deploy/wake-watchdog.service").read_text(encoding="utf-8")
|
|
79
79
|
assert "PYTHONPATH" not in unit
|
|
80
80
|
assert "--as lab-ovh" in unit
|
|
81
|
+
assert "--escalate drop-on-meta-edge" in unit
|
|
82
|
+
assert "token-file" not in unit
|
|
81
83
|
assert "Environment=MESH_GATEWAY_URL=http://100.64.189.91:8788" in unit
|
|
82
84
|
|
|
83
85
|
|