swarph-cli 0.69.7__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.7/src/swarph_cli.egg-info → swarph_cli-0.69.9}/PKG-INFO +1 -1
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/pyproject.toml +1 -1
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/monitor.py +32 -3
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/ensure_monitor.sh +9 -61
- swarph_cli-0.69.9/src/swarph_cli/scripts/wake_watchdog.py +385 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/SOURCES.txt +2 -0
- swarph_cli-0.69.9/tests/test_1006_session_board.py +291 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_644_monitor_supervision.py +32 -0
- {swarph_cli-0.69.7 → 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.7 → swarph_cli-0.69.9}/tests/test_960_ensure_monitor_heartbeat.py +47 -16
- swarph_cli-0.69.7/src/swarph_cli/scripts/wake_watchdog.py +0 -209
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/LICENSE +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/README.md +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/setup.cfg +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_402_one_identity_resolver.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_group_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_import_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_init_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_main.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_security.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_app.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_version_command.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_stale_peers.py +0 -0
- {swarph_cli-0.69.7 → 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,
|
|
@@ -64,71 +64,19 @@ STATE_DIR="${1:-${SWARPH_STATE_DIR:-}}"
|
|
|
64
64
|
SD=()
|
|
65
65
|
[ -n "$STATE_DIR" ] && SD=(--state-dir "$STATE_DIR")
|
|
66
66
|
|
|
67
|
-
# >>> #960:
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
local f="$1"
|
|
75
|
-
[ -n "$f" ] && [ -f "$f" ] || return 1
|
|
76
|
-
command -v python3 >/dev/null 2>&1 || return 1
|
|
77
|
-
python3 -c 'import json,os,sys,time
|
|
78
|
-
try:
|
|
79
|
-
data=json.load(open(sys.argv[1],encoding="utf-8"))
|
|
80
|
-
ts=float(data.get("ts") or 0)
|
|
81
|
-
except Exception:
|
|
82
|
-
raise SystemExit(1)
|
|
83
|
-
age=time.time()-ts
|
|
84
|
-
# A clock a few seconds ahead is still a live writer. An hour ahead is not.
|
|
85
|
-
if age < -30 or age >= 180:
|
|
86
|
-
raise SystemExit(1)
|
|
87
|
-
pid=data.get("pid")
|
|
88
|
-
if pid is None:
|
|
89
|
-
raise SystemExit(0)
|
|
90
|
-
try:
|
|
91
|
-
pid=int(pid)
|
|
92
|
-
except (TypeError, ValueError):
|
|
93
|
-
raise SystemExit(1)
|
|
94
|
-
# pid 0 is this process group and pid -1 is every process we can signal.
|
|
95
|
-
# Neither is the channel server. os.kill would report both as alive.
|
|
96
|
-
if pid <= 0:
|
|
97
|
-
raise SystemExit(1)
|
|
98
|
-
try:
|
|
99
|
-
os.kill(pid, 0)
|
|
100
|
-
except PermissionError:
|
|
101
|
-
pass
|
|
102
|
-
except OSError:
|
|
103
|
-
raise SystemExit(1)
|
|
104
|
-
try:
|
|
105
|
-
raw=open("/proc/%s/cmdline" % pid, "rb").read()
|
|
106
|
-
except OSError:
|
|
107
|
-
# No command line to read (macOS, or a pid we may not inspect).
|
|
108
|
-
# A live pid still counts, which is the check this host can make.
|
|
109
|
-
raise SystemExit(0)
|
|
110
|
-
text=raw.replace(b"\x00", b" ").decode("utf-8", "replace")
|
|
111
|
-
if "channel-serve" in text or "swarph_cli.channel" in text:
|
|
112
|
-
raise SystemExit(0)
|
|
113
|
-
raise SystemExit(1)' "$f"
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
HB_ROOT="${STATE_DIR:-$HOME/swarph_state/$SELF}"
|
|
117
|
-
HB_FILE=""
|
|
118
|
-
if [ -f "$HB_ROOT/channel_heartbeat.json" ]; then
|
|
119
|
-
HB_FILE="$HB_ROOT/channel_heartbeat.json"
|
|
120
|
-
elif [ -f "$HB_ROOT/mesh-sidecar/channel_heartbeat.json" ]; then
|
|
121
|
-
HB_FILE="$HB_ROOT/mesh-sidecar/channel_heartbeat.json"
|
|
122
|
-
fi
|
|
123
|
-
|
|
67
|
+
# >>> #960: the heartbeat is the READER, the monitor is the WRITER. <<<
|
|
68
|
+
# channel-serve writes channel_heartbeat.json every 60s and only reads
|
|
69
|
+
# inbox.log. The pull monitor is the only writer of that log. A fresh
|
|
70
|
+
# heartbeat means the reader is up, not the writer. `monitor status` is
|
|
71
|
+
# the check for the writer. Not running: start one, even when the
|
|
72
|
+
# heartbeat is fresh. Running (status 0 or 1): start nothing. A second
|
|
73
|
+
# monitor is the duplicate. The heartbeat file is not that check.
|
|
124
74
|
"$SW" monitor status --as "$SELF" ${SD[@]+"${SD[@]}"} >/dev/null 2>&1
|
|
125
75
|
case $? in
|
|
126
76
|
0) ;; # running, nothing pending
|
|
127
77
|
1) ;; # running, DMs pending — reported below
|
|
128
|
-
*) # not running (2) or unknown.
|
|
129
|
-
if
|
|
130
|
-
echo "[monitor] channel heartbeat is under 180s old — not starting a monitor"
|
|
131
|
-
elif "$SW" monitor start --as "$SELF" --deliver pull ${SD[@]+"${SD[@]}"} >/tmp/.swarph-monitor-start.$$ 2>&1; then
|
|
78
|
+
*) # not running (2) or unknown. The heartbeat does not cover this.
|
|
79
|
+
if "$SW" monitor start --as "$SELF" --deliver pull ${SD[@]+"${SD[@]}"} >/tmp/.swarph-monitor-start.$$ 2>&1; then
|
|
132
80
|
echo "[monitor] started (--deliver pull)"
|
|
133
81
|
else
|
|
134
82
|
# LOUD, not silent: an auto-start that fails quietly rebuilds the exact
|
|
@@ -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
|
|
@@ -178,6 +178,7 @@ src/swarph_cli/systemd/swarph-peer-reply-drain@.timer
|
|
|
178
178
|
src/swarph_cli/systemd/swarph-watchdog.default
|
|
179
179
|
src/swarph_cli/systemd/swarph-watchdog.service
|
|
180
180
|
src/swarph_cli/systemd/swarph-watchdog.timer
|
|
181
|
+
tests/test_1006_session_board.py
|
|
181
182
|
tests/test_124_register_merge.py
|
|
182
183
|
tests/test_126_pending_from_complete.py
|
|
183
184
|
tests/test_132_ensure_monitor_state_dir.py
|
|
@@ -229,6 +230,7 @@ tests/test_729_dm_once.py
|
|
|
229
230
|
tests/test_729_wake_watchdog.py
|
|
230
231
|
tests/test_734_dreaming_enrich_degrade.py
|
|
231
232
|
tests/test_735_dm_mcp_tools.py
|
|
233
|
+
tests/test_739_wake_writer.py
|
|
232
234
|
tests/test_740_edit_project.py
|
|
233
235
|
tests/test_762_cards_edit_priority.py
|
|
234
236
|
tests/test_764_enrich_collapse.py
|